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