MySQL単一テーブルの1,000万行以上をできるだけ速く更新する方法は?


32

ほとんどのテーブルでMySQL 5.6とInnoDBストレージエンジンを使用します。InnoDBバッファープールのサイズは15 GBで、Innodb DB +インデックスは約10 GBです。サーバーには32GBのRAMがあり、Cent OS 7 x64を実行しています。

約1,000万件以上のレコードを含む1つの大きなテーブルがあります。

24時間ごとにリモートサーバーから更新されたダンプファイルを取得します。ファイルはcsv形式です。私はそのフォーマットを制御できません。ファイルは最大750 MBです。MyISAMテーブルに行ごとにデータを挿入しようとしましたが、35分かかりました。

ファイルから10-12のうち1行につき3つの値のみを取得し、データベースで更新する必要があります。

このようなことを達成する最良の方法は何ですか?

これを毎日する必要があります。

現在、Flowは次のようになっています。

  1. mysqli_begin_transaction
  2. ファイルを1行ずつ読み込む
  3. 各レコードを行ごとに更新します。
  4. mysqli_commit

上記の操作を完了するには約30〜40分かかりますが、これを実行している間、他の更新が行われているため、

ロック待機タイムアウトを超過しました。トランザクションを再開してみてください

アップデート1

を使用して新しいテーブルにデータを読み込みますLOAD DATA LOCAL INFILE。MyISAM 38.93 secでは、InnoDBでは7分5.21秒かかりました。それから私はやった:

UPDATE table1 t1, table2 t2
SET 
t1.field1 = t2.field1,
t1.field2 = t2.field2,
t1.field3 = t2.field3
WHERE t1.field10 = t2.field10

Query OK, 434914 rows affected (22 hours 14 min 47.55 sec)

更新2

結合クエリを使用した同じ更新

UPDATE table1 a JOIN table2 b 
ON a.field1 = b.field1 
SET 
a.field2 = b.field2,
a.field3 = b.field3,
a.field4 = b.field4

(14 hours 56 min 46.85 sec)

コメントの質問からの明確化:

  • テーブル内の行の約6%がファイルによって更新されますが、25%になることもあります。
  • 更新されるフィールドにはインデックスがあります。テーブルには12個のインデックスがあり、8個のインデックスには更新フィールドが含まれています。
  • 1つのトランザクションで更新を行う必要はありません。時間がかかることがありますが、24時間以内です。後でこのテーブルに依存しているsphinxインデックスを更新する必要があるため、テーブル全体をロックせずに1時間で完了できるようにしています。データベースが他のタスクに使用できる限り、ステップに時間がかかるかどうかは関係ありません。
  • 前処理ステップでcsv形式を変更できます。重要なのは、迅速な更新とロックなしです。
  • 表2はMyISAMです。これは、インファイルのロードデータを使用してcsvファイルから新しく作成されたテーブルです。MYIファイルのサイズは452 MBです。表2は、field1列で索引付けされています。
  • MyISAMテーブルのMYDは663MBです。

アップデート3:

両方のテーブルの詳細を次に示します。

CREATE TABLE `content` (
  `hash` char(40) CHARACTER SET ascii NOT NULL DEFAULT '',
  `title` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '',
  `og_name` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '',
  `keywords` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '',
  `files_count` smallint(5) unsigned NOT NULL DEFAULT '0',
  `more_files` smallint(5) unsigned NOT NULL DEFAULT '0',
  `files` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '0',
  `category` smallint(3) unsigned NOT NULL DEFAULT '600',
  `size` bigint(19) unsigned NOT NULL DEFAULT '0',
  `downloaders` int(11) NOT NULL DEFAULT '0',
  `completed` int(11) NOT NULL DEFAULT '0',
  `uploaders` int(11) NOT NULL DEFAULT '0',
  `creation_date` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  `upload_date` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  `last_updated` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  `vote_up` int(11) unsigned NOT NULL DEFAULT '0',
  `vote_down` int(11) unsigned NOT NULL DEFAULT '0',
  `comments_count` int(11) NOT NULL DEFAULT '0',
  `imdb` int(8) unsigned NOT NULL DEFAULT '0',
  `video_sample` tinyint(1) NOT NULL DEFAULT '0',
  `video_quality` tinyint(2) NOT NULL DEFAULT '0',
  `audio_lang` varchar(127) CHARACTER SET ascii NOT NULL DEFAULT '',
  `subtitle_lang` varchar(127) CHARACTER SET ascii NOT NULL DEFAULT '',
  `verified` tinyint(1) unsigned NOT NULL DEFAULT '0',
  `uploader` int(11) unsigned NOT NULL DEFAULT '0',
  `anonymous` tinyint(1) NOT NULL DEFAULT '0',
  `enabled` tinyint(1) unsigned NOT NULL DEFAULT '0',
  `tfile_size` int(11) unsigned NOT NULL DEFAULT '0',
  `scrape_source` tinyint(1) unsigned NOT NULL DEFAULT '0',
  `record_num` int(11) unsigned NOT NULL AUTO_INCREMENT,
  PRIMARY KEY (`record_num`),
  UNIQUE KEY `hash` (`hash`),
  KEY `uploaders` (`uploaders`),
  KEY `tfile_size` (`tfile_size`),
  KEY `enabled_category_upload_date_verified_` (`enabled`,`category`,`upload_date`,`verified`),
  KEY `enabled_upload_date_verified_` (`enabled`,`upload_date`,`verified`),
  KEY `enabled_category_verified_` (`enabled`,`category`,`verified`),
  KEY `enabled_verified_` (`enabled`,`verified`),
  KEY `enabled_uploader_` (`enabled`,`uploader`),
  KEY `anonymous_uploader_` (`anonymous`,`uploader`),
  KEY `enabled_uploaders_upload_date_` (`enabled`,`uploaders`,`upload_date`),
  KEY `enabled_verified_category` (`enabled`,`verified`,`category`),
  KEY `verified_enabled_category` (`verified`,`enabled`,`category`)
) ENGINE=InnoDB AUTO_INCREMENT=7551163 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci ROW_FORMAT=FIXED


CREATE TABLE `content_csv_dump_temp` (
  `hash` char(40) CHARACTER SET ascii NOT NULL DEFAULT '',
  `title` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  `category_id` int(11) unsigned NOT NULL DEFAULT '0',
  `uploaders` int(11) unsigned NOT NULL DEFAULT '0',
  `downloaders` int(11) unsigned NOT NULL DEFAULT '0',
  `verified` tinyint(1) unsigned NOT NULL DEFAULT '0',
  PRIMARY KEY (`hash`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

そして、ここcontentからのデータを使用してテーブルを更新する更新クエリですcontent_csv_dump_temp

UPDATE content a JOIN content_csv_dump_temp b 
ON a.hash = b.hash 
SET 
a.uploaders = b.uploaders,
a.downloaders = b.downloaders,
a.verified = b.verified

アップデート4:

上記のテストはすべてテストマシンで行われましたが、現在は実稼働マシンで同じテストを行っており、クエリは非常に高速です。

mysql> UPDATE content_test a JOIN content_csv_dump_temp b
    -> ON a.hash = b.hash
    -> SET
    -> a.uploaders = b.uploaders,
    -> a.downloaders = b.downloaders,
    -> a.verified = b.verified;
Query OK, 2673528 rows affected (7 min 50.42 sec)
Rows matched: 7044818  Changed: 2673528  Warnings: 0

私の間違いをおforびします。各レコードの更新の代わりに結合を使用する方が適切です。今私はrick_jamesによって提案されたインデックスを使用してmpreを改善しようとしていますが、ベンチマークが完了したら更新されます。


コンポジットを INDEX(field2, field3, field4)(任意の順序で)持っていますか?見せてくださいSHOW CREATE TABLE
リック・ジェームズ

1
12と8のインデックスは問題の深刻な部分です。MyISAMは別の重大な部分です。InnoDBまたはTokuDBは、複数のインデックスを使用するとパフォーマンスが大幅に向上します。
リック・ジェームズ

あなたは2つの異なるがあり UPDATEsます。csvデータからテーブルを更新するための簡単なステートメントがどのように見えるかを正確に教えてください。 その後、お客様の要件を満たす手法を考案するお手伝いをいたします。
リックジェームズ

@RickJamesが一つだけありupdate、かつ更新質問を確認してください、ありがとう。
AMB

回答:


17

私の経験に基づいて、LOAD DATA INFILEを使用してCSVファイルをインポートします。

LOAD DATA INFILEステートメントは、テキストファイルから行を非常に高速でテーブルに読み込みます。

例インターネットロードデータの例で見つけました。私のボックスでこの例をテストし、うまくいきました

サンプル表

CREATE TABLE example (
  `Id` int(11) NOT NULL AUTO_INCREMENT,
  `Column2` varchar(14) NOT NULL,
  `Column3` varchar(14) NOT NULL,
  `Column4` varchar(14) NOT NULL,
  `Column5` DATE NOT NULL,
  PRIMARY KEY (`Id`)
) ENGINE=InnoDB

CSVファイルの例

# more /tmp/example.csv
Column1,Column2,Column3,Column4,Column5
1,A,Foo,sdsdsd,4/13/2013
2,B,Bar,sdsa,4/12/2013
3,C,Foo,wewqe,3/12/2013
4,D,Bar,asdsad,2/1/2013
5,E,FOObar,wewqe,5/1/2013

MySQLコンソールから実行されるインポートステートメント

LOAD DATA LOCAL INFILE '/tmp/example.csv'
    -> INTO TABLE example
    -> FIELDS TERMINATED BY ','
    -> LINES TERMINATED BY '\n'
    -> IGNORE 1 LINES
    -> (id, Column3,Column4, @Column5)
    -> set
    -> Column5 = str_to_date(@Column5, '%m/%d/%Y');

結果

MySQL [testcsv]> select * from example;
+----+---------+---------+---------+------------+
| Id | Column2 | Column3 | Column4 | Column5    |
+----+---------+---------+---------+------------+
|  1 |         | Column2 | Column3 | 0000-00-00 |
|  2 |         | B       | Bar     | 0000-00-00 |
|  3 |         | C       | Foo     | 0000-00-00 |
|  4 |         | D       | Bar     | 0000-00-00 |
|  5 |         | E       | FOObar  | 0000-00-00 |
+----+---------+---------+---------+------------+

IGNOREは、列ヘッダーである最初の行を単に無視します。

IGNOREの後、インポートする列を指定します(column2をスキップします)。これは質問の基準の1つに一致します。

Oracleの別の例を次に示します。LOAD DATA INFILEの例

これで開始できます。


私は、一時テーブルにデータをロードするためのロードデータを使用して、メインテーブルにそれを更新するために他のクエリを使用して、おかげでした。
AMB

14

言及されたすべてのことに照らして、ボトルネックが結合そのものであるように見えます。

側面#1:結合バッファーサイズ

おそらく、join_buffer_sizeが低すぎる可能性があります。

MySQLが結合バッファーキャッシュを使用する方法に関するMySQLドキュメントによる

行全体ではなく、使用された列のみを結合バッファーに保存します。

この場合、結合バッファーのキーをRAMに保持します。

各キーに4バイトを掛けた1,000万行があります。それは約4,000万です。

セッションで42M(40Mより少し大きい)に上げてみてください

SET join_buffer_size = 1024 * 1024 * 42;
UPDATE table1 a JOIN table2 b 
ON a.field1 = b.field1 
SET 
a.field2 = b.field2,
a.field3 = b.field3,
a.field4 = b.field4;

これでうまくいかない場合は、次へ追加してください my.cnf

[mysqld]
join_buffer_size = 42M

新しい接続にmysqldを再起動する必要はありません。ただ走れ

mysql> SET GLOBAL join_buffer_size = 1024 * 1024 * 42;

側面#2:結合操作

オプティマイザーをtweekingすることにより、結合操作のスタイルを操作できます。

ネストされたループとバッチキーアクセス結合のブロックに関するMySQLドキュメントによると

BKAを使用する場合、join_buffer_sizeの値は、ストレージエンジンへの各リクエストのキーバッチの大きさを定義します。バッファが大きいほど、結合操作の右側のテーブルへの順次アクセスが多くなり、パフォーマンスが大幅に向上します。

BKAを使用するには、optimizer_switchシステム変数のbatched_key_accessフラグをオンに設定する必要があります。BKAはMRRを使用するため、mrrフラグもオンにする必要があります。現在、MRRのコスト見積もりは悲観的すぎます。したがって、BKAを使用するには、mrr_cost_basedをオフにする必要もあります。

これと同じページでこれを行うことをお勧めします。

mysql> SET optimizer_switch='mrr=on,mrr_cost_based=off,batched_key_access=on';

側面3:更新をディスクに書き込む(オプション)

ほとんどの場合、innodb_write_io_threadsを増やして、ダーティページをより速くバッファプールから書き出すことを忘れています。

[mysqld]
innodb_write_io_threads = 16

この変更のためにMySQLを再起動する必要があります

試してみる !!!


いいね!調整可能な結合バッファーチップの場合は+1。参加する必要がある場合は、メモリに参加します。良いヒント!
ピーターディクソンモーゼ

3
  1. CREATE TABLE CSVと一致する
  2. LOAD DATA そのテーブルに
  3. UPDATE real_table JOIN csv_table ON ... SET ..., ..., ...;
  4. DROP TABLE csv_table;

ステップ3は行ごとよりもはるかに高速ですが、それでもテーブル内のすべての行を重要な時間ロックします。プロセス全体にかかる時間よりもこのロック時間が重要な場合、...

テーブルに他に何も書き込んでいない場合、...

  1. CREATE TABLECSVと一致します。で必要とされるものを除いていないインデックスJOINの中でUPDATE。一意であれば、それを作りますPRIMARY KEY
  2. LOAD DATA そのテーブルに
  3. コピーreal_tablenew_tableCREATE ... SELECT
  4. UPDATE new_table JOIN csv_table ON ... SET ..., ..., ...;
  5. RENAME TABLE real_table TO old, new_table TO real_table;
  6. DROP TABLE csv_table, old;

特に不要なインデックスが残っている場合、手順3は更新よりも高速です。
ステップ5は「即時」です。


たとえば、ステップ3の後、ステップ4を実行している間に、新しいデータがreal_tableに挿入されるので、new_table内のそのデータを見逃しますか?これの回避策は何ですか?感謝
AMB

何を参照してくださいpt-online-schema-digest。を介してこのような問題を処理しTRIGGERます。
リックジェームズ

おそらく、テーブルのインデックス必要ありませLOAD DATA。不要なインデックスの追加は(時間内に)コストがかかります。
リックジェームズ

最新情報に基づいて、CSVファイルをMyISAMテーブルにロードしAUTO_INCREMENT、PKに基づいて一度に1K行をチャンクします。ただし、詳細を説明する前に、すべての要件テーブルスキーマを確認する必要があります。
リックジェームズ

ハッシュをPRIMARY indexに設定しましたが、順序クエリを使用して50kでチャンクするのに時間がかかりますが、自動インクリメントを作成した方が良いでしょうか?として設定しPRIMARY indexますか?
AMB

3

あなたは言った:

  • 更新はテーブルの6-25%に影響します
  • できるだけ早くこれを行いたい(1時間未満)
  • ロックなし
  • 単一のトランザクションである必要はありません
  • まだ(Rick Jamesの答えに対するコメントで)、あなたは競合状態について懸念を表明します

これらの声明の多くは矛盾している可能性があります。たとえば、テーブルをロックしない大きな更新。または、1つの巨大なトランザクションを使用して競合状態を回避します。

また、テーブルのインデックスが頻繁に作成されるため、挿入と更新の両方が遅くなる可能性があります。


競合状態の回避

更新されたタイムスタンプをテーブルに追加できる場合は、単一のトランザクションで50万の更新を記録することを回避しながら、競合状態を解決できます。

これにより、現在のように行ごとの更新を実行できますが、自動コミットまたはより合理的なトランザクションバッチを使用できます。

後の更新がまだ発生していないことを確認することにより(行ごとに更新中に)競合状態を回避します(UPDATE ... WHERE pk = [pk] AND updated < [batchfile date]

そして重要なことは、これにより並列更新を実行できることです。


可能な限り高速で実行-並列化

このタイムスタンプチェックを実施すると、次のようになります。

  1. バッチファイルを適切なサイズのチャンクに分割します(たとえば、50,000行/ファイル)
  2. 並行して、各ファイルでスクリプトを読み取り、50,000個のUPDATEステートメントを含むファイルを出力します。
  3. 並行して、一度(2)が終了したら、mysql各sqlファイルを実行します。

(たとえば、コマンドを並列に多くの方法で簡単に実行する方法と方法をbash探します。並列度は、更新専用のスレッド数に依存します)splitxargs -P


「ライン・バイ・ライン」は、少なくとも100のバッチで物事を行うよりも遅い10倍である可能性が高いことを心に留めておいてください
リック・ジェームス

この場合、確実にベンチマークする必要があります。テーブルの6〜25%を更新すると(更新された列に8つのインデックスが含まれる)、インデックスのメンテナンスがボトルネックになる可能性があります。
ピーターディクソンモーゼ

つまり、場合によっては、インデックスを削除し、一括更新してから再作成する方が速い場合がありますが、OPはダウンタイムを必要としません。
ピーターディクソンモーゼ

1

大規模な更新はI / Oバウンドです。私はお勧めします:

  1. 頻繁に更新される3つのフィールドを格納する個別のテーブルを作成します。静的データを保持する場所であるasset_staticテーブルと、アップローダー、ダウンローダー、および検証済みを格納する他のasset_dynamicテーブルを呼び出しましょう。
  2. 可能であれば、assets_dynamicテーブルにMEMORYエンジンを使用します。(各更新後のディスクへのバックアップ)。
  3. 更新4に従って軽量かつ軽快なasset_dynamicを更新します(すなわち、LOAD INFILE ... INTO temp; UPDATE asset_dynamic a a.JOIN temp b on a.id = b.id SET [更新する必要があるもの]。 (システムでは、assets_dynamicには95M行があり、更新による影響は最大で 6M行で、40 秒強です。)
  4. Sphinxのインデクサーを実行するとき、assets_staticasset_dynamicを結合します(これらのフィールドのいずれかを属性として使用すると仮定します)。

0

以下のためにUPDATE高速で実行するには、次のものが必要

INDEX(uploaders, downloaders, verified)

どちらのテーブルでもかまいません。3つのフィールドの順序は任意です。

これUPDATEにより、2つのテーブル間の行をすばやく一致させることができます。

そして、2つのテーブルでデータ型を同じにします(両方INT SIGNEDまたは両方INT UNSIGNED)。


これにより、実際に更新が遅くなりました。
AMB

うーん...提供してくださいEXPLAIN UPDATE ...;
リックジェームズ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.