0

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

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',带有一个增加的arraysize和一个outputtypehandler将字符串转换为 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从光标处执行。

提前谢谢了。

4

1 回答 1

0

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

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

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

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

也可能是你的问题。

于 2013-09-30T10:25:05.103 回答