7

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

这是否有可能更快,或者这是对语言/数据库的限制?

4

5 回答 5

10

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

三件事。

一。不要一遍又一遍地选择以符合日期、主机名和人员维度。一次将所有数据提取到 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(*)速度不够快(首先进行基准测试,然后再做任何蹩脚的事情),您可以将计数结果缓存在另一个表中。在所有负载之后。执行 aSELECT 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 语句。绝不。

于 2008-09-26T01:50:23.767 回答
3

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

准备并执行数据库操作(查询或命令)。参数可以作为序列或映射提供,并将绑定到操作中的变量。变量以特定于数据库的表示法指定(有关详细信息,请参阅模块的 paramstyle 属性)。[5]

游标将保留对该操作的引用。如果再次传入相同的操作对象,则游标可以优化其行为。这对于使用相同操作但绑定不同参数(很多次)的算法最有效。

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

于 2008-09-25T23:29:56.910 回答
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. db 服务器不必生成新的执行计划,因为它可以使用之前创建的执行计划。
  3. 您不必调用 santitize() 因为绑定变量中的特殊字符不会成为执行的 sql 语句的一部分。

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

其他优化:

  1. 每次循环迭代后,您都将使用“UPDATE people SET chatscount”进行递增。保留一个字典映射用户到chat_count,然后执行你看到的总数的语句。这将比在每条记录后点击数据库更快。
  2. 在所有查询中使用绑定变量。不只是插入语句,我选择它作为示例。
  3. 更改所有执行数据库查找的 find_*() 函数以缓存其结果,这样它们就不必每次都访问数据库。
  4. Psycho 优化执行大量数字运算的 python 程序。该脚本是 IO 昂贵的,而不是 CPU 昂贵的,所以如果有任何优化,我不希望给你太多。
于 2008-09-25T23:59:39.290 回答
2

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

打开事务(如果还没有)并在程序结束时执行一次提交。在需要提交所有数据之前,数据库不必将任何内容写入磁盘。如果您的程序遇到错误,则不会提交任何行,这样您就可以在问题得到纠正后简单地重新运行程序。

您的 log_hostname、log_person 和 log_date 函数正在对表进行不必要的 SELECT。使适当的表属性 PRIMARY KEY 或 UNIQUE。然后,不要在 INSERT 之前检查密钥是否存在,只需执行 INSERT。如果人员/日期/主机名已经存在,则 INSERT 将因违反约束而失败。(如果您使用带有单个提交的事务,这将不起作用,如上所述。)

或者,如果您知道您是唯一一个在程序运行时插入表中的人,则在执行插入时在内存中创建并行数据结构并在内存中维护它们。例如,在程序开始时将表中的所有主机名读入关联数组。当想知道是否进行 INSERT 时,只需进行数组查找即可。如果未找到条目,请执行 INSERT 并适当地更新数组。(此建议与事务和单次提交兼容,但需要更多编程。不过,它会快得多。)

于 2008-09-26T00:20:36.680 回答
1

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

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

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

于 2008-09-26T00:26:21.503 回答