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」で、文字列を Unicode に変換します。 複数の入力スキーマの出力を 1 つのストリームにチェーンする別のメソッドから呼び出されます。arraysizeoutputtypehandlercycle

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 を実行する古いスクリプトで同様のことが起こるのを見たことがありません。

よろしくお願いします。

4

1 に答える 1

0

これがあなたの問題に当てはまるかどうかはわかりませんが、昨日、カーソルに大きな配列サイズを設定すると、深刻なパフォーマンスの問題が発生しました。

cursor.fetchall() と cursor.fetchmany() を使用する 2 つの方法がありました。

cursor.fetchall() が完了するまでに 0.1 秒、cursor.fetchmany() が 1.5 秒かかりました。

いくつかのデバッグの後、fetch_many() の前に、cursor.arraysize を大きな値 (100.000) に設定していることに気付きました。これにより、cxOracle ドライバーは arraysize num 行のメモリ領域を割り当てます。多くの varchar2(4000) 列を含むクエリと組み合わせると、各呼び出しの前に x*100MB が割り当てられ、その後解放されます。

あなたの問題もあるかもしれません。

于 2013-09-30T10:25:05.103 に答える