データベースに対して一連の動的に構築されたクエリを実行し、最終的に結果がフェッチされる最終的なクエリを実行するコンポーネントを作成しました。このロジックはすべてジェネレーターにラップされています。他のコンポーネントは、このジェネレーターに接続して、結果をファイル (このテストの場合はタブ区切りのテキスト ファイル) にスプールできます。ジェネレーター関数のコードは次のとおりです。
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」で、文字列を Unicode に変換します。 複数の入力スキーマの出力を 1 つのストリームにチェーンする別のメソッドから呼び出されます。arraysize
outputtypehandler
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 秒で完了しました。これは、置き換えようとしている従来のスクリプトのパフォーマンスに基づいて、私が予想したこととほぼ同じです。
新しいツールは入力パラメーターとして複数のデータベース スキーマを使用できるため、同じスキーマを 6 回指定してテストも行いました。ログに記録された実行時間は、問題が最初の反復でのみ発生したことを示しています。
:: 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
カーソルから a を実行する古いスクリプトで同様のことが起こるのを見たことがありません。
よろしくお願いします。