你如何更快地使Python / PostgreSQL?

时间:2008-09-25 23:12:50

标签: python postgresql

现在我有一个日志解析器,读取515mb的纯文本文件(过去4年中每天的文件)。我的代码目前如下:http://gist.github.com/12978。我已经使用了psyco(如代码中所示),我也正在编译它并使用编译版本。它每0.3秒做约100行。该机器是标准的15“MacBook Pro(2.4ghz C2D,2GB RAM)

这可能会更快或者是对语言/数据库的限制吗?

5 个答案:

答案 0 :(得分:8)

不要浪费时间分析。时间总是在数据库操作中。做尽可能少。只需最少的插入次数。

三件事。

一。不要一遍又一遍地选择符合日期,主机名和人员维度。将所有数据ONCE获取到Python字典中并在内存中使用它。不要重复单身选择。使用Python。

两个。不要更新。

具体来说,不要这样做。这个代码很糟糕有两个原因。

cursor.execute("UPDATE people SET chats_count = chats_count + 1 WHERE id = '%s'" % person_id)

将其替换为简单的SELECT COUNT(*)FROM ....永远不要更新以增加计数。只需使用SELECT语句计算那里的行。 [如果你不能用一个简单的SELECT COUNT或SELECT COUNT(DISTINCT)来做这件事,你就会遗漏一些数据 - 你的数据模型应该总是提供正确的完整计数。永远不要更新。]

和。切勿使用字符串替换来构建SQL。完全愚蠢。

如果由于某种原因,SELECT COUNT(*)不够快(首先是基准测试,在做任何跛足之前),你可以将计数结果缓存到另一个表中。在所有负载之后。执行SELECT COUNT(*) FROM whatever GROUP BY whatever并将其插入计数表中。不要更新。如初。

三。使用绑定变量。总是

cursor.execute( "INSERT INTO ... VALUES( %(x)s, %(y)s, %(z)s )", {'x':person_id, 'y':time_to_string(time), 'z':channel,} )

SQL永远不会改变。这些值在更改中绑定,但SQL永远不会更改。这要快得多。永远不要动态构建SQL语句。决不。

答案 1 :(得分:3)

在sql语句中使用绑定变量而不是文字值,并为其创建游标 每个唯一的sql语句,以便下次使用时不需要重新声明该语句。来自python db api doc:

  

准备并执行数据库   操作(查询或命令)。   参数可以作为序列提供   或映射并将被绑定   操作中的变量。变量   在特定于数据库中指定   符号(参见模块的参数)   细节的属性)。 [5]

     

对操作的引用将是   由光标保留。如果相同   操作对象再次传入,   然后光标可以优化它   行为。这是最有效的   相同操作的算法   使用,但不同的参数   它很多次(很多次)。

始终始终使用绑定变量。

答案 2 :(得分:3)

在for循环中,您将重复插入“聊天”表,因此您只需要一个带有绑定变量的单个sql语句,以使用不同的值执行。所以你可以把它放在for循环之前:

insert_statement="""
    INSERT INTO chats(person_id, message_type, created_at, channel)
    VALUES(:person_id,:message_type,:created_at,:channel)
"""

然后代替执行的每个sql语句将其放置到位:

cursor.execute(insert_statement, person_id='person',message_type='msg',created_at=some_date, channel=3)

这会让事情变得更快,因为:

  1. 每次光标对象都不必重新分析语句
  2. 数据库服务器不必生成新的执行计划,因为它可以使用之前创建的计划。
  3. 您不必调用santitize(),因为绑定变量中的特殊字符不会成为执行的sql语句的一部分。
  4. 注意:我使用的绑定变量语法是Oracle特定的。您必须检查psycopg2库的文档以获取确切的语法。

    其他优化:

    1. 在每次循环迭代后,您将使用“UPDATE people SET chatscount”进行递增。保持字典映射用户到chat_count,然后执行您看到的总数的语句。这比在每条记录之后点击db更快。
    2. 对所有查询使用绑定变量。不只是insert语句,我选择它作为一个例子。
    3. 更改所有执行db look up的find _ *()函数以缓存其结果,这样他们就不必每次都访问db。
    4. psycho优化执行大量数字操作的python程序。该脚本是IO昂贵的,而不是CPU昂贵,所以如果有任何优化,我不希望给你太多。

答案 3 :(得分:2)

正如Mark建议的那样,使用绑定变量。数据库只需准备一次每个语句,然后为每次执行“填写空白”。作为一个很好的副作用,它会自动处理字符串引用问题(你的程序没有处理)。

打开事务(如果它们尚未存在)并在程序结束时执行单个提交。在需要提交所有数据之前,数据库不必向磁盘写入任何内容。如果您的程序遇到错误,则不会提交任何行,只需在问题得到纠正后重新运行该程序即可。

您的log_hostname,log_person和log_date函数正在对表执行不必要的SELECT。使适当的表属性为PRIMARY KEY或UNIQUE。然后,不要在INSERT之前检查是否存在密钥,而是执行INSERT。如果person / date / hostname已存在,则INSERT将从约束违规失败。 (如果您使用具有单个提交的事务,这将不起作用,如上所述。)

或者,如果您知道在程序运行时唯一一个INSERT到表中,那么在内存中创建并行数据结构并在执行INSERT时将它们保存在内存中。例如,将表中的所有主机名读入程序开头的关联数组。当想知道是否要执行INSERT时,只需进行数组查找。如果未找到任何条目,请执行INSERT并相应地更新阵列。 (这个建议与事务和单个提交兼容,但需要更多的编程。但是,它会更加快速。)

答案 4 :(得分:1)

除了@Mark Roddy给出的许多好建议外,请执行以下操作:

  • 不要使用readlines,您可以迭代文件对象
  • 尝试使用executemany而不是execute:尝试进行批量插入而不是单个插入,这往往更快,因为开销更少。它还减少了提交次数
  • str.rstrip可以正常工作,而不是使用正则表达式剥离换行符

批量插入将暂时使用更多内存,但是当您不将整个文件读入内存时,这应该没问题。