如何使Python / PostgreSQL更快?

时间:2020-03-06 14:45:22  来源:igfitidea点击:

现在,我有一个日志解析器可以读取515mb的纯文本文件(过去4年每天的文件)。我的代码目前是这样的:http://gist.github.com/12978. 我已经使用了psyco(如代码所示),并且还在对其进行编译并使用已编译的版本。每0.3秒大约处理100条线。该机器是标准的15英寸MacBook Pro(2.4ghz C2D,2GB RAM)

这可能更快吗,还是对语言/数据库的限制?

解决方案

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

Prepare and execute a database
  operation (query or command). 
  Parameters may be provided as sequence
  or mapping and will be bound to
  variables in the operation. Variables
  are specified in a database-specific
  notation (see the module's paramstyle
  attribute for details). [5]
  
  A reference to the operation will be
  retained by the cursor.  If the same
  operation object is passed in again,
  then the cursor can optimize its
  behavior.  This is most effective for
  algorithms where the same operation is
  used, but different parameters are
  bound to it (many times).

总是总是使用绑定变量。

在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)

这将使事情运行得更快,因为:

  • 游标对象不必每次都重新解析该语句
  • 数据库服务器不必生成新的执行计划,因为它可以使用之前创建的计划。
  • 我们无需调用santitize(),因为绑定变量中的特殊字符将不会成为要执行的sql语句的一部分。

注意:我使用的绑定变量语法是特定于Oracle的。我们必须检查psycopg2库的文档以获取确切的语法。

其他优化:

  • 每次循环迭代后,我们将使用" UPDATE people SET chatscount"进行递增。保留一个将词典映射用户映射到chat_count,然后执行所见总数的语句。这样会更快,然后在每条记录后命中数据库。
  • 在所有查询上使用绑定变量。我不仅仅选择插入语句,还选择了它作为示例。
  • 更改所有执行db查找的find _ *()函数以缓存其结果,这样就不必每次都访问数据库。
  • psycho优化了执行大量数字运算的python程序。该脚本是IO昂贵的,而不是CPU昂贵的,因此我不希望给我们太多优化。

正如Mark所建议的,请使用绑定变量。数据库只需准备每个语句一次,然后为每次执行"填充空白"。作为一个很好的副作用,它将自动处理字符串引用问题(程序未处理)。

打开事务(如果尚未打开),并在程序结束时进行一次提交。在不需要提交所有数据之前,数据库无需将任何内容写入磁盘。而且,如果程序遇到错误,则不会提交任何行,从而使我们可以在纠正问题后简单地重新运行该程序。

log_hostname,log_person和log_date函数正在对表执行不必要的SELECT。将适当的表属性设置为PRIMARY KEY或者UNIQUE。然后,不必在插入之前检查密钥是否存在,只需执行INSERT。如果个人/日期/主机名已经存在,则INSERT将因约束冲突而失败。 (如上所述,如果我们使用具有一次提交的事务,则将无法使用。)

另外,如果我们知道自己是程序运行时唯一向表中插入数据的一种,则在执行插入操作时在内存中创建并行数据结构并在内存中维护它们。例如,在程序开始时,从表中将所有主机名读入一个关联数组。当想知道是否要执行INSERT时,只需进行数组查找即可。如果没有找到条目,请执行INSERT并适当地更新阵列。 (此建议与事务和单个提交兼容,但需要更多编程。但是,它的速度会更快。)

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

  • 不要使用readlines,我们可以遍历文件对象
  • 尝试使用" executemany"而不是" execute":尝试批量插入而不是单次插入,由于开销较少,因此这样做通常会更快。它还减少了提交次数
  • str.rstrip可以正常工作,而不是用正则表达式剥离换行符

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

不要浪费时间进行分析。时间总是在数据库操作中。尽可能少做。仅插入的最小数量。

三件事。

一。不要反复选择"日期","主机名"和"人"维。将所有数据一次提取到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(*)不够快(首先进行基准测试,然后再做la脚),则可以将计数结果缓存在另一个表中。在所有负载之后。从任意GROUP BY进行" SELECT COUNT(*)"并将其插入到计数表中。不要更新。曾经。

三。使用绑定变量。总是。

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

SQL永不更改。值必然会更改,但SQL永远不会更改。这要快得多。切勿动态构建SQL语句。绝不。