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