2番目のテーブルの2つのレコード(プレーヤーとターゲット)間の関係を表す大きなテーブル(5800万以上のレコード)があります。
残念ながら、私たちのスキーマを設計した人は誰も物事を適切に考慮せず、ユーザーレコードの数値IDではなく、ユーザー名を使用してこの関係を表すことにしました。状況が進むにつれて(通常のように)、ユーザー名は有効で一意なプレーヤーの表現ではなくなったため、これらの関係を数値IDを使用するように変換する必要があります。
ライブテーブルで変更できるpt-online-schema-changeを提供するPercona Toolkitのおかげで、ロックせずにフィールドを追加するのは簡単でした。ただし、テーブルにデータを入力するのは難しいかもしれません。
テーブルは次のようになり(関係のないフィールドの作成を削除しました)、2つの未入力フィールドはplayer_id
およびtarget_id
:
CREATE TABLE `player_targets` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`player` varchar(20) NOT NULL,
`player_id` int(10) unsigned DEFAULT NULL,
`target` varchar(20) NOT NULL,
`target_id` int(10) unsigned DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=58000000 DEFAULT CHARSET=latin1;
CREATE TABLE 'player_uuids' (
`id`int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(20) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=600000 DEFAUL CHARSET=latin1;
次のようなクエリを2つの新しいフィールドに入力することを計画していました。
UPDATE player_targets t
INNER JOIN player_uuids u1
ON u1.username = t.player
INNER JOIN player_uuids u2
ON u2.username = t.target
SET
t.player_id = u1.id,
t.target_id = u2.id
WHERE
t.player_id IS NULL
OR t.player_id IS NULL;
リレーションを格納するテーブルがMyISAMである場合、ドキュメントの解釈では、UPDATEクエリはすべての行が完了するまでテーブルをロックします。テーブルが大きいため、これはライブ環境ではあまりうまく機能しない可能性があります。
これに対する最善のアプローチは何でしょうか?リレーションのバッチを反復するスクリプトを作成していますか?テーブルエンジンをInnoDBに変更しますか(テーブルは読み取りが多いため、MyISAMである理由だと思います)?
ループステートメントでは、1行ずつ削除または更新できます。この場合、ブロッキングは発生しませんが、少し遅くなります。手順でこれを行うには、制限1を使用します。
PROCEDURE myProcedure()
BEGIN
DECLARE c int; -- to calculated affected rows
set c = 0;
ml:LOOP
UPDATE player_targets t
INNER JOIN player_uuids u1
ON u1.username = t.player
INNER JOIN player_uuids u2
ON u2.username = t.target
SET
t.player_id = u1.id,
t.target_id = u2.id
WHERE
t.player_id IS NULL
OR t.player_id IS NULL
LIMIT 1;
-- check if the loop has completed
IF ROW_COUNT() = 0 THEN
LEAVE ml;
END IF;
set c = c + 1;
IF c MOD 100 = 0 THEN
SELECT CONCAT(c, ' row(s) updated');
END IF;
END LOOP;
SELECT CONCAT(c, ' row(s) updated; The statement has completed');
END
そしてプロシージャを呼び出します:
Call myProcedure();
はい、バッチに対して繰り返します。 このブログでは、その方法に関するいくつかの提案を提供しています 。
大きなDELETE
sをチャンクする方法を説明しますが、原則はあなたのような大きなUPDATE
sでも機能します。
(通常)PRIMARY KEY
を使用してウォークスルーし、LIMIT
を使用して次のチャンクを決定する方法を示します。チャンク化の目的を無効にするため、テーブル全体をスキャンする必要はありません。
これに対する最善のアプローチは何でしょうか?リレーションのバッチを反復するスクリプトを記述していますか?テーブルエンジンをInnoDBに変更しますか(テーブルは読み取りが多く、これがMyISAMである理由だと思います)?
テーブルをInnoDBに変更します。テーブルロックの理由を排除するだけではありません。しかし、クラッシュからの回復のために、参照整合性と他の多くのものがあります。 要点については、この回答を参照してください。
なぜ回避策が必要なのですか? MyISAMは問題なく使用できますが、理由はあるはずです。つまり、あなたのDBは読み込みが重いと思います。 MySQLが提供する強化されたデフォルトを試してください。それがユースケースに失敗した場合は、MySQLが提供する別の方法を試してください。私はInnoDBを試す前に何かを考えたくありません。それが簡単な解決策です。別のストレージエンジンを試してください。