ほとんどのテーブルでMySQL 5.6とInnoDBストレージエンジンを使用します。 InnoDBバッファープールのサイズは15 GBで、Innodb DB +インデックスは約10 GBです。サーバーには32GB RAMがあり、Cent OS 7 x64を実行しています。
約1000万件以上のレコードを含む大きなテーブルが1つあります。
24時間ごとにリモートサーバーから更新されたダンプファイルを取得します。ファイルはcsv形式です。私はそのフォーマットを制御できません。ファイルは約750 MBです。行ごとにMyISAMテーブルにデータを挿入しようとしましたが、35分かかりました。
ファイルから10〜12の値を1行あたり3つだけ取得し、データベースで更新する必要があります。
このようなことを達成するための最良の方法は何ですか?
これを毎日行う必要があります。
現在のフローは次のとおりです:
上記の操作には約-40分かかり、これを実行している間、他の更新が行われています。
ロック待機タイムアウトを超えました。トランザクションを再開してみてください
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)
結合クエリを使用した同じ更新
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)
コメントの質問からの説明:
更新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_csv_dump_temp
のデータを使用してcontent
テーブルを更新する更新クエリです
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
私は私の間違いをお詫び申し上げます。各レコードの更新の代わりに結合を使用することをお勧めします。今私はrick_jamesによって提案されたインデックスを使用してmpreを改善しようとしています、ベンチマークが行われると更新されます。
私の経験に基づいて、私は 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の後、インポートする列を指定します(列2をスキップ)。これは、質問の基準の1つに一致します。
次に、Oracleからの別の例を示します。 LOAD DATA INFILEの例
これはあなたが始めるのに十分なはずです。
言及したすべてのことを考慮すると、ボトルネックが結合自体であるように見えます。
おそらく、あなたの join_buffer_size はおそらく低すぎます。
MySQLが結合バッファキャッシュを使用する方法 のMySQLドキュメントによると==
行全体ではなく、使用した列のみを結合バッファーに格納します。
この場合は、結合バッファのキーをRAMに残してください。
1,000万行×各キーの4バイトがあります。それは約4,000万です。
セッションでそれを4200万(4千万より少し大きい)に増やす
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;
オプティマイザをテストすることにより、結合操作のスタイルを操作できます
MySQLのドキュメントによると Block Nested-Loop and Batched Key Access Joins
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';
innodb_write_io_threads を増やして、ダーティページをバッファプールからより速く書き込むことを忘れることがほとんどです。
[mysqld]
innodb_write_io_threads = 16
この変更のためにMySQLを再起動する必要があります
あなたは言った:
これらのステートメントの多くは、矛盾する可能性があります。たとえば、テーブルをロックしない大規模な更新。または、1つの巨大なトランザクションを使用せずに競合状態を回避します。
また、テーブルのインデックスは頻繁に作成されるため、挿入と更新の両方が遅くなる可能性があります。
競合状態の回避
updatedタイムスタンプをテーブルに追加できる場合は、競合状態を解決しながら、50万の更新を単一トランザクション。
これにより、(現在のように)行ごとの更新を実行できますが、自動コミットまたはより適切なトランザクションバッチを使用できます。
後の更新がまだ行われていないことを確認することにより(行ごとに更新しながら)競合状態を回避します(UPDATE ... WHERE pk = [pk] AND updated < [batchfile date]
)
そして重要なことに、これによりparallel更新を実行できます。
できるだけ早く実行する—並列化
このタイムスタンプチェックを実行すると、次のようになります。
mysql
に各SQLファイルを実行させます。(たとえば、bash
では、split
とxargs -P
を見て、コマンドを並列に多くの方法で簡単に実行する方法を確認してください。並列処理の程度は、専用のスレッドをどれだけ更新)
CREATE TABLE
CSVに一致LOAD DATA
そのテーブルにUPDATE real_table JOIN csv_table ON ... SET ..., ..., ...;
DROP TABLE csv_table;
ステップ3は行単位よりもはるかに高速ですが、テーブル内のすべての行を重要な時間ロックします。このロック時間の方が、プロセス全体にかかる時間よりも重要である場合、...
他に何もテーブルに書き込んでいない場合は、...
CREATE TABLE
CSVと一致します。 JOIN
のUPDATE
で必要なものを除いて、インデックスはありません。一意の場合は、PRIMARY KEY
。LOAD DATA
そのテーブルにreal_table
からnew_table
(CREATE ... SELECT
)UPDATE new_table JOIN csv_table ON ... SET ..., ..., ...;
RENAME TABLE real_table TO old, new_table TO real_table;
DROP TABLE csv_table, old;
特に不要なインデックスが省略されている場合、ステップ3は更新よりも高速です。
ステップ5は「瞬時」です。
大規模な更新はI/Oバウンドです。私はお勧めします:
UPDATE
を高速に実行するには、
INDEX(uploaders, downloaders, verified)
どちらのテーブルにも配置できます。 3つのフィールドは任意の順序にすることができます。
これにより、UPDATE
が2つのテーブル間の行をすばやく照合できるようになります。
And 2つのテーブルでデータ型を同じにします(両方のINT SIGNED
または両方 INT UNSIGNED
)。