web-dev-qa-db-ja.com

MySQLでロックせずに(非常に)大きなテーブルを更新する方法

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である理由だと思います)?

5
Olle

ループステートメントでは、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();
1
yikekas

はい、バッチに対して繰り返します。 このブログでは、その方法に関するいくつかの提案を提供しています

大きなDELETEsをチャンクする方法を説明しますが、原則はあなたのような大きなUPDATEsでも機能します。

(通常)PRIMARY KEYを使用してウォークスルーし、LIMITを使用して次のチャンクを決定する方法を示します。チャンク化の目的を無効にするため、テーブル全体をスキャンする必要はありません。

0
Rick James

これに対する最善のアプローチは何でしょうか?リレーションのバッチを反復するスクリプトを記述していますか?テーブルエンジンをInnoDBに変更しますか(テーブルは読み取りが多く、これがMyISAMである理由だと思います)?

テーブルをInnoDBに変更します。テーブルロックの理由を排除するだけではありません。しかし、クラッシュからの回復のために、参照整合性と他の多くのものがあります。 要点については、この回答を参照してください。

なぜ回避策が必要なのですか? MyISAMは問題なく使用できますが、理由はあるはずです。つまり、あなたのDBは読み込みが重いと思います。 MySQLが提供する強化されたデフォルトを試してください。それがユースケースに失敗した場合は、MySQLが提供する別の方法を試してください。私はInnoDBを試す前に何かを考えたくありません。それが簡単な解決策です。別のストレージエンジンを試してください。

0
Evan Carroll