6

私は現在、ウィキペディアのダンプファイルを分析しています。私はPythonを使用してそこから大量のデータを抽出し、それをPostgreSQLデータベースに永続化しています。このファイルは巨大(18GB)なので、私は常に物事をより速くするように努めています。PostgreSQLとのインターフェースのために、私はpsycopg2を使用していますが、このモジュールは他の多くのそのようなDBAPIを模倣しているようです。

とにかく、cursor.executemany(command、values);に関する質問があります。これらの500万個の値のそれぞれに対してcursor.execute(command%value)を呼び出すよりも、1000個の値ごとにexecutemanyを実行する方が良いように思えます(確認または修正してください!)。

しかし、ご覧のとおり、私はexecutemanyを使用して、UNIQUE整合性制約のあるテーブルに1000行を挿入しています。この制約はPythonで事前に検証されていません。これは、常にSELECTを実行する必要があるか(これは逆効果のようです)、3GBを超えるRAMを取得する必要があるためです。これはすべて、スクリプトがpsycopg2.DatabaseErrorをキャッチして既存の行を挿入しようとしたときに、Postgresが警告を発することを期待していることを意味します。

私のスクリプトがそのような非一意のINSERTを検出すると、connection.rollback()(毎回最大1000行になり、実行の多くが無価値になります)、次にすべての値を1つずつINSERTします。

psycopg2の文書化が不十分であるため(多くの優れたモジュールがそうであるように...)、効率的で効果的な回避策を見つけることができません。実行ごとの非一意のINSERTの可能性を減らすために、実行ごとにINSERTされる値の数を1000から100に減らしましたが、これらの値はpsycopg2にこれらの例外を無視するように、またはカーソルを押して実行を続行します。

基本的に、これは非常に簡単で人気のある解決策を持っている種類の問題のように思われるので、私にできることはそれについて学ぶために尋ねることだけです。

再度、感謝します!

4

4 に答える 4

8

psql \copy コマンドを使用してすべてのデータをスクラッチ テーブルにコピーするか、psycopg cursor.copy_in() メソッドを使用します。それで:

insert into mytable
select * from (
    select distinct * 
    from scratch
) uniq
where not exists (
    select 1 
    from mytable 
    where mytable.mykey = uniq.mykey
);

これにより重複除去が行われ、挿入のどの組み合わせよりもはるかに高速に実行されます。

-dg

于 2009-02-15T13:13:40.237 に答える
5

私は同じ問題を抱えていて、ここで何日も検索して、完全な解決策を形成するためのヒントをたくさん集めました. 質問が古かったとしても、これが他の人に役立つことを願っています。

1)インデックス/制約を削除して後で再作成することを忘れてください。メリットはわずかであるか、さらに悪いものです。

2) executemany は、prepare ステートメントを作成するため、execute よりも優れています。次のようなコマンドを使用して同じ結果を自分で取得し、300% の速度を得ることができます。

# To run only once:
sqlCmd = """PREPARE myInsert (int, timestamp, real, text) AS
   INSERT INTO myBigTable (idNumber, date_obs, result, user)
     SELECT $1, $2, $3, $4 WHERE NOT EXISTS
     (SELECT 1 FROM myBigTable WHERE (idNumber, date_obs, user)=($1, $2, $4));"""
curPG.execute(sqlCmd)
cptInsert = 0   # To let you commit from time to time

#... inside the big loop:
curPG.execute("EXECUTE myInsert(%s,%s,%s,%s);", myNewRecord)
allreadyExists = (curPG.rowcount < 1)
if not allreadyExists:
   cptInsert += 1
   if cptInsert % 10000 == 0:
      conPG.commit()

このダミー テーブルの例には、(idNumber、date_obs、user) に対する一意の制約があります。

3) 最善の解決策は、COPY_FROM と TRIGGER を使用して、INSERT の前に一意のキーを管理することです。これにより、36 倍の速度が得られました。500 レコード/秒の通常の挿入から始めました。「コピー」を使用すると、18,000 レコード/秒を超えました。Psycopg2 を使用した Python のサンプル コード:

ioResult = StringIO.StringIO() #To use a virtual file as a buffer
cptInsert = 0 # To let you commit from time to time - Memory has limitations
#... inside the big loop:
   print >> ioResult, "\t".join(map(str, myNewRecord))
   cptInsert += 1
   if cptInsert % 10000 == 0:
      ioResult = flushCopyBuffer(ioResult, curPG)
#... after the loop:
ioResult = flushCopyBuffer(ioResult, curPG)

def flushCopyBuffer(bufferFile, cursorObj):
   bufferFile.seek(0)   # Little detail where lures the deamon...
   cursorObj.copy_from(bufferFile, 'myBigTable',
      columns=('idNumber', 'date_obs', 'value', 'user'))
   cursorObj.connection.commit()
   bufferFile.close()
   bufferFile = StringIO.StringIO()
   return bufferFile

Python の部分は以上です。Postgresql トリガーで例外 psycopg2.IntegrityError が発生しないようになり、COPY コマンドのすべてのレコードが拒否されました。

CREATE OR REPLACE FUNCTION chk_exists()
  RETURNS trigger AS $BODY$
DECLARE
    curRec RECORD;
BEGIN
   -- Check if record's key already exists or is empty (file's last line is)
   IF NEW.idNumber IS NULL THEN
      RETURN NULL;
   END IF;
   SELECT INTO curRec * FROM myBigTable
      WHERE (idNumber, date_obs, user) = (NEW.idNumber, NEW.date_obs, NEW.user);
   IF NOT FOUND THEN -- OK keep it
      RETURN NEW;
   ELSE    
      RETURN NULL; -- Oups throw it or update the current record
   END IF;
END;
$BODY$ LANGUAGE plpgsql;

この関数をテーブルのトリガーにリンクします。

CREATE TRIGGER chk_exists_before_insert
   BEFORE INSERT ON myBigTable FOR EACH ROW EXECUTE PROCEDURE chk_exists();

これは大変な作業のように思えますが、SQL を何度も解釈する必要がない場合、Postgresql は非常に高速です。楽しむ。

于 2012-06-15T23:24:35.633 に答える
0

「私のスクリプトがそのような非 UNIQUE INSERT を検出すると、connection.rollback() (毎回最大 1000 行を作成し、executemany の価値をなくします) を実行し、すべての値を 1 つずつ INSERT します。」

質問はあまり意味がありません。

一意でない行が原因で、1,000 行のすべてのブロックが失敗しますか?

1,000 行の 1 つのブロックが (5,000 のブロックのうち) 失敗しますか? もしそうなら、多くの実行は5,000のうち4,999を助け、「価値がない」とはほど遠い.

このユニークではないインサートについて心配していますか? または、これが発生する回数に関する実際の統計はありますか?

1,000 行ブロックから 100 行ブロックに切り替えた場合、明らかに、1,000 行ブロック、100 行ブロック、および 1 行ブロックのパフォーマンス上の利点があるかどうかを判断できます。

実際のデータベースと異なるサイズのブロックで実際のプログラムを実際に実行し、数値を投稿してください。

于 2008-12-28T23:06:15.400 に答える
-1

INSERT ステートメントの代わりに MERGE ステートメントを使用すると、問題が解決します。

于 2009-03-24T01:32:07.117 に答える