如何提高Python / PostgreSQL的性能?

7 投票
5 回答
8676 浏览
提问于 2025-04-11 09:24

现在我有一个日志解析器,它正在读取515MB的纯文本文件(过去4年每天一个文件)。我的代码现在是这样的:http://gist.github.com/12978。我使用了psyco(在代码中可以看到),并且我也在编译它并使用编译后的版本。现在的速度大约是每0.3秒处理100行。我的机器是一台标准的15寸MacBook Pro(2.4GHz双核处理器,2GB内存)。

这样是否有可能更快,还是说这是语言或数据库的限制呢?

5 个回答

3

在SQL语句中使用绑定变量,而不是直接写死的值,并为每个独特的SQL语句创建一个游标,这样下次使用时就不需要重新解析这个语句了。根据Python数据库API文档的说明:

准备并执行一个数据库操作(查询或命令)。可以将参数作为序列或映射提供,这些参数会绑定到操作中的变量上。变量的表示方式是数据库特定的(具体细节可以查看模块的paramstyle属性)。

游标会保留对这个操作的引用。如果再次传入相同的操作对象,游标就可以优化它的行为。这对于那些使用相同操作但绑定不同参数的算法来说,效果最好(很多次)。

一定一定一定要使用绑定变量。

4

在这个for循环里,你一直在往'chats'表里插入数据,所以其实只需要一个带有绑定变量的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语句的一部分。

注意:我用的绑定变量语法是Oracle特有的。你需要查看psycopg2库的文档,了解确切的语法。

其他优化建议:

  1. 你在每次循环后都用"UPDATE people SET chatscount"来增加计数。可以先用一个字典把用户和聊天数量对应起来,然后在最后一次性执行更新语句。这样比每处理一条记录就去数据库更新要快。
  2. 在你所有的查询中都使用绑定变量。不仅仅是插入语句,我只是用这个作为例子。
  3. 把所有进行数据库查找的find_*()函数改成缓存它们的结果,这样就不需要每次都去数据库查了。
  4. psycho可以优化执行大量数字运算的Python程序。这个脚本在输入输出方面比较耗费资源,而不是在CPU方面,所以我不指望它能给你带来太多优化。
10

别浪费时间去分析性能。大部分时间都花在数据库操作上。尽量减少这些操作,做到最少的插入。

有三件事。

第一,不要反复去查询日期、主机名和人员这些信息。一次性把所有数据取到一个Python字典里,然后在内存中使用。别做重复的单个查询,直接用Python来处理。

第二,不要更新数据。

具体来说,千万不要这样做。这种做法有两个原因不好。

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

可以用简单的查询来替代,比如 SELECT COUNT(*) FROM ...。永远不要通过更新来增加计数。只需用查询语句来统计现有的行数。[如果你不能用简单的 SELECT COUNTSELECT 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语句,绝对不要。

撰写回答