循环遍历cx_Oracle游标停顿几分钟

时间:2013-04-14 10:13:55

标签: python oracle cx-oracle

我写了一个组件,它对数据库执行一系列动态构造的查询,并最终执行最终查询,其结果被获取。所有这些逻辑都包含在一个发生器中。其他组件可以连接到此生成器并将结果假脱机到文件,在此测试的情况下是制表符分隔的文本文件。这是生成器函数的代码:

def cycle(self, schema, matchprofile, fixedcond):

    cursor = self.cursor
    cursor.set_schema(schema)
    cursor.execute('TRUNCATE TABLE schema.table')
    metaatts = self.get_metaattributes(schema)

    for condset in matchprofile:
        condsql = self.dostuffwith(self, matchprofile, fixedcond)
        qry = self.qrybase.replace('<<condset>>', condset).replace('<<condsql>>', condsql)
        cursor.execute(qry)             # queries performing logic aganst database

    cursor.execute(self.finalqry)           # select query extracting results

    for row in cursor:
        yield Row(row, metaatts.copy())

    if self.counts:
        self.counter.addto(cursor.rowcount)

cursor是一个子类'cx_Oracle.Cursor',增加了arraysizeoutputtypehandler,可以将字符串转换为Unicode。从另一个方法调用cycle,该方法将多个输入模式的输出链接到一个流中。

cycles = itertools.imap(self.cycle, schemas, itertools.repeat(matchprofile), itertools.repeat(fixedcond))
rows = itertools.chain.from_iterable(cycles)

我在Python 2.6上运行它。

我已经运行了整个脚本几十次,在大多数情况下,在数据库架构上完成大约需要11到12分钟。这比预期的要多得多。不可预知的是,在一些尝试中,脚本在大约55秒内完成。这正是我所期望的基于我试图替换的遗留脚本的性能。

由于新工具可以将多个数据库模式作为输入参数,因此我还进行了六次相同模式的测试。记录的执行时间显示问题仅在第一次迭代中发生:

:: 1597 records in 11:33
:: 1597 records in 0:56
:: 1597 records in 0:55
:: 1597 records in 0:55
:: 1597 records in 0:55
:: 1597 records in 0:55

:: total 9582 records in 16:10

我还设法描述了产生合理的运行...

109707 function calls (109627 primitive calls) in 57.938 CPU seconds

Ordered by: internal time

ncalls  tottime  percall  cumtime  percall filename:lineno(function)
    12   56.154    4.679   56.154    4.680 {function execute at 0x010074B0}
     1    0.819    0.819    0.819    0.819 ora.py:194(__init__)
     1    0.387    0.387    0.387    0.387 {function parse at 0x010075B0}
  1598    0.331    0.000   56.543    0.035 DuplicateDetector.py:219(cycle)
  1598    0.118    0.000    0.118    0.000 {method 'writerow' of '_csv.writer' objects}
 30295    0.029    0.000    0.029    0.000 {_codecs.utf_8_decode}
  1598    0.025    0.000   56.720    0.035 dsv.py:146(generate)
 30310    0.022    0.000    0.029    0.000 {method 'encode' of 'unicode' objects}

......而且时间过长。

109707 function calls (109627 primitive calls) in 701.093 CPU seconds

Ordered by: internal time

ncalls  tottime  percall  cumtime  percall filename:lineno(function)
  1598  644.514    0.403  699.827    0.438 DuplicateDetector.py:219(cycle)
    12   55.247    4.604   55.248    4.604 {function execute at 0x010084B0}
     1    0.783    0.783    0.783    0.783 ora.py:194(__init__)
     1    0.283    0.283    0.283    0.283 {function parse at 0x010085B0}
  1598    0.121    0.000    0.121    0.000 {method 'write' of '_csv.writer' objects}
 30295    0.036    0.000    0.036    0.000 {_codecs.utf_8_decode}
  1598    0.025    0.000  700.006    0.438 dsv.py:146(generate)
 30310    0.022    0.000    0.028    0.000 {method 'encode' of 'unicode' objects}
 30295    0.021    0.000    0.057    0.000 utf_8.py:15(decode)

很明显,在第一种情况下,数据库操作占用大部分执行时间,而后者大部分时间都花在cycle生成器上。我使用Idle调试器逐步执行此操作,似乎行for row in cursor:负责执行大约10分钟。我还注意到python.exe进程的内存使用量在此期间不断增加。

现在,问题是该行中发生的情况是相同代码的执行时间如此不同(尽管可重复)?当Cursor用作迭代器时,cx_Oracle在内部执行什么样的操作?我可以在包装代码中犯这样的错误?不可否认,我从来没有见过类似的事情发生在没有使用类和生成器的旧脚本上,只是从光标执行fetchall

非常感谢提前。

1 个答案:

答案 0 :(得分:0)

不知道这是否适用于您的问题,但是我在昨天为光标设置一个大型数组时遇到了严重的性能问题。

我有两种使用cursor.fetchall()和cursor.fetchmany()的方法。

cursor.fetchall()需要0.1秒才能完成,cursor.fetchmany()需要1.5秒。

经过一些调试后,我注意到我在fetch_many()之前将cursor.arraysize设置为一个较大的值(100.000)。这会导致cxOracle驱动程序为arraysize num行分配mem空间。结合具有大量varchar2(4000)列的查询,总计在每次调用之前分配了一些x * 100MB,然后释放。

也可能是你的问题。