web-dev-qa-db-ja.com

MySQLでの高速バルクインクリメント

多対多の関係を記述し、数百万のfoobar、数百万のfoo、およびすべてのbarが数百を含む1つの大きなテーブルbarがあります。 fooの-​​>数十億行。

CREATE TABLE `foobar` (
`foo_id` INT(10) UNSIGNED NOT NULL,
`bar_id` INT(10) UNSIGNED NOT NULL,
PRIMARY KEY (`foo_id`, `bar_id`),
INDEX `bar_id_idx` (`bar_id`))

foobar内のfoo_idをカウントする別のテーブルがあります。

CREATE TABLE `foo_amount` (
`foo_id` INT(10) UNSIGNED NOT NULL,
`amount` INT(10) UNSIGNED NOT NULL,
PRIMARY KEY (`foo_id`),
INDEX `amount_idx` (`amount`))

カウントは次のように行うことができます。

INSERT INTO foo_amount (SELECT foo_id, COUNT(*) AS amount FROM foobar GROUP BY foo_id);

しかし、foobarに挿入/削除されたすべての行でテーブルを再計算する必要があります。

挿入は通常、新しいbar-オブジェクトをいくつかのfooで追加します。たとえば、barbar_id 42を挿入し、foofoo_idの3、8、26、44、...を挿入すると、次のようになります。

INSERT INTO foobar VALUES (3,42), (8,42), (26,42), (44,42), ...;

2番目の試みは、挿入されたbarオブジェクトごとにfoo_countテーブルを更新することでした。

INSERT INTO foo_amount (SELECT foo_id, 1 FROM foobar WHERE bar_id = 42)
ON DUPLICATE KEY UPDATE amount = amount + 1;

しかし、これは非常に遅いです。これを最適化する方法について何かアイデアはありますか?オプションとして、新しいbarを一時的なfoo_count_tmpに蓄積し、それをfoo_countとマージすることがあります。 foo_countテーブルは常に最新であるとは限りませんが、問題はありません。しかし、どのように更新をトリガーしますか?

4
Ben

GROUP BYを最初からfoobarに頼るのはどうですか?

まず、新しいデータをfoobarに挿入します

次に、foobarで新しいGROUP BYカウントを一時テーブルに実行します。

CREATE TABLE foo_amount_new LIKE foo_amount;
INSERT INTO foo_amount_new
SELECT foo_id,COUNT(1)
FROM foobar WHERE bar_id = ... 
GROUP BY foo_id;

最後に、一時テーブルを交換して、古いfoo_amountを削除します

ALTER TABLE foo_amount RENAME foo_amount_zap;
ALTER TABLE foo_amount_new RENAME foo_amount;
DROP TABLE foo_amount_zap;

ただし、数十億のテーブルがある場合、再構築するインデックスがあるため、これは困難な戦いです。以下はすべてのINSERT ... ON DUPLICATE KEYで発生するため:

  • 金額を増やす必要があります
  • 金額はamountインデックスでシフトする必要があります

INSERTとUPDATEを高速化するために、amountインデックスを削除してみてください。

代替案

別の方法を使用して一時テーブルソリューションを試してください

ステップ01)CREATE TABLE foobar_new LIKE foobar;

ステップ02)foobar_newに一括INSERTを実行します

ステップ03)CREATE TABLE foo_amount_new LIKE foo_amount;

ステップ04)最新の一括INSERTバッチでGROUP BYカウントを実行します

INSERT INTO foo_amount_new
SELECT foo_id,COUNT(1) FROM foobar_new WHERE bar_id = ... 
GROUP BY foo_id;

ステップ05)foobar_newからfoobarへの一括INSERTを実行します

INSERT INTO foobar SELECT * FROM foobar_new;

ステップ06)foo_amountからfoo_amount_newの一括更新を実行します

UPDATE foo_amount A INNER JOIN foo_amount_new B
USING (foo_id) SET A.amount = A.amount + B.amount;

ステップ07)一時テーブルを削除します

DROP TABLE foobar_new;
DROP TABLE foo_amount_new;
4
RolandoMySQLDBA

次のクエリはあなたの典型的なものですか?

_INSERT INTO foobar VALUES (3,42), (8,42), (26,42), (44,42), ...;
_

もしそうなら、そしてこれが(手作業ではなく)コードによって生成されていると私は仮定しているので、次のクエリを作成することをお勧めします:

_UPDATE foo_amount SET amount=amount+1 WHERE foo_in IN (3, 8, 26, 44, ...);
_

しかし、いくつかのことが私にははっきりしていません。

  • INSERTは機能することが保証されていますか?つまり、INSERT INTO foobar VALUES (3,42), (8,42), (26,42), (44,42)に重複が含まれているため、操作が失敗する可能性がありますか?

  • また、IGNOREのソートを使用している場合は、_foo_amount_のamountをインクリメントする必要があるかどうかの理解が複雑になります(ソリューションにも適用されます)

  • 最後に、あなたがしていることは本質的に要約テーブルを管理することです。私はあなたがすべきではないと言うつもりはありません-しかしあなたは絶対にそれらを必要としていると確信していますか?必要なときにデータをフェッチすることはできますか?それでも、すべての書き込みを管理するよりも全体的に効率的であることが証明される場合があります。もちろん、「効率的」とは、最適化の優先度が高いのは誰であるか(読み取りまたは書き込み)を決定する必要があるため、ここでは多少曖昧です。

0
Shlomi Noach