5

私は を使用しており、いくつかの(主キーが一致する場合は更新する) クエリをバッチでSQLAlchemy 1.0.0作成したいと考えています。UPDATE ONLY

いくつかの実験を行ったところ、一括更新は一括挿入または一括よりもはるかに遅く見えることがわかりましたupsert

なぜそれがとても遅いのか、またはそれを作るための別の方法/アイデアがあるのか​​ を指摘するのを手伝ってもらえますBULK UPDATE (not BULK UPSERT) with SQLAlchemyか?

以下は MYSQL のテーブルです。

CREATE TABLE `test` (
  `id` int(11) unsigned NOT NULL,
  `value` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

そしてテストコード:

from sqlalchemy import create_engine, text
import time

driver = 'mysql'
host = 'host'
user = 'user'
password = 'password'
database = 'database'
url = "{}://{}:{}@{}/{}?charset=utf8".format(driver, user, password, host, database)

engine = create_engine(url)
engine.connect()

engine.execute('TRUNCATE TABLE test')

num_of_rows = 1000

rows = []
for i in xrange(0, num_of_rows):
    rows.append({'id': i, 'value': i})

print '--------- test insert --------------'
sql = '''
    INSERT INTO test (id, value)
    VALUES (:id, :value)
'''
start = time.time()
engine.execute(text(sql), rows)
end = time.time()
print 'Cost {} seconds'.format(end - start)

print '--------- test upsert --------------'
for r in rows:
    r['value'] = r['id'] + 1

sql = '''
    INSERT INTO test (id, value)
    VALUES (:id, :value)
    ON DUPLICATE KEY UPDATE value = VALUES(value)
'''
start = time.time()
engine.execute(text(sql), rows)
end = time.time()
print 'Cost {} seconds'.format(end - start)

print '--------- test update --------------'
for r in rows:
    r['value'] = r['id'] * 10

sql = '''
    UPDATE test
    SET value = :value
    WHERE id = :id
'''
start = time.time()
engine.execute(text(sql), rows)
end = time.time()
print 'Cost {} seconds'.format(end - start)

num_of_rows = 100 の場合の出力:

--------- test insert --------------
Cost 0.568960905075 seconds
--------- test upsert --------------
Cost 0.569655895233 seconds
--------- test update --------------
Cost 20.0891299248 seconds

num_of_rows = 1000 の場合の出力:

--------- test insert --------------
Cost 0.807548999786 seconds
--------- test upsert --------------
Cost 0.584554195404 seconds
--------- test update --------------
Cost 206.199367046 seconds

データベース サーバーへのネットワーク遅延は約 500 ミリ秒です。

一括更新では、バッチではなく、各クエリを 1 つずつ送信して実行するように見えますか?

前もって感謝します。

4

1 に答える 1

4

データベースサーバー(あなたの場合のように)のレイテンシが非常に悪い場合でも、トリックを使用して一括更新操作を高速化できます。テーブルを直接更新する代わりに、stage-tableを使用して新しいデータを非常に高速に挿入してから、1 つの join-update をdestination-table に実行します。これには、データベースに送信する必要があるステートメントの数を大幅に削減できるという利点もあります。

これは UPDATE でどのように機能しますか?

テーブルがentriesあり、新しいデータが常に入ってくるが、既に保存されているものだけを更新したいとします。entries_stage関連するフィールドのみを含む宛先テーブルのコピーを作成します。

entries = Table('entries', metadata,
    Column('id', Integer, autoincrement=True, primary_key=True),
    Column('value', Unicode(64), nullable=False),
)

entries_stage = Table('entries_stage', metadata,
    Column('id', Integer, autoincrement=False, unique=True),
    Column('value', Unicode(64), nullable=False),
)

次に、一括挿入でデータを挿入します。これは、SQLAlchemy でネイティブにサポートされていない MySQL の複数値挿入構文を使用すると、さらに高速化できますが、それほど困難なく構築できます。

INSERT INTO enries_stage (`id`, `value`)
VALUES
(1, 'string1'), (2, 'string2'), (3, 'string3'), ...;

最後に、次のようにステージ テーブルの値で宛先テーブルの値を更新します。

 UPDATE entries e
 JOIN entries_stage es ON e.id = es.id
 SET e.value = es.value;

その後、完了です。

インサートはどうですか?

もちろん、これは挿入を高速化するためにも機能します。stage-tableには既にデータがあるので、 destination-tableにまだINSERT INTO ... SELECTないデータを使用してステートメントを発行するだけです。

INSERT INTO entries (id, value)
SELECT FROM entries_stage es
LEFT JOIN entries e ON e.id = es.id
HAVING e.id IS NULL;

これの良いところは、何もしない場合でも、主キーをインクリメントするINSERT IGNORE, REPLACEorON DUPLICATE KEY UPDATEを行う必要がないことです。

于 2015-10-27T09:00:43.633 に答える