InnoDBインポートのパフォーマンス


10

約1000万行(または7GB)からなる非常に大きなInnoDBテーブルを一括インポートするのに苦労しています(これは、これまでに作業した中で最大のテーブルです)。

Innoのインポート速度を改善する方法を調査しましたが、今のところ、私のセットアップは次のようになっています。

/etc/mysql/my.cnf/
[...]
innodb_buffer_pool_size = 7446915072 # ~90% of memory
innodb_read_io_threads = 64
innodb_write_io_threads = 64
innodb_io_capacity = 5000
innodb_thread_concurrency=0
innodb_doublewrite = 0
innodb_log_file_size = 1G
log-bin = ""
innodb_autoinc_lock_mode = 2
innodb_flush_method = O_DIRECT
innodb_flush_log_at_trx_commit=2
innodb_buffer_pool_instances=8


import is done via bash script, here is the mysql code:
SET GLOBAL sync_binlog = 1;
SET sql_log_bin = 0;
SET FOREIGN_KEY_CHECKS = 0;
SET UNIQUE_CHECKS = 0;
SET AUTOCOMMIT = 0;
SET SESSION tx_isolation='READ-UNCOMMITTED';
LOAD DATA LOCAL INFILE '$filepath' INTO TABLE monster
COMMIT;

データはCSVファイルで提供されます。
現在、設定を200万、300万、…の小さな「テストダンプ」でテストtime import_script.shし、パフォーマンスの比較に使用しています。

欠点は、全体の実行時間しか取得できないため、結果を取得するために完全なインポートが完了するのを待つ必要があることです。

これまでの私の結果:

  • 10 000行:<1秒
  • 100 000行:10秒
  • 30万行:40秒
  • 200万行:18分
  • 300万行:26分
  • 400万行:(2時間後にキャンセル)

「クックブック」の解決策はないようで、自分で設定の最適な組み合わせを見つける必要があります。
セットアップで何を変更するかについての提案に加えて、インポートプロセスのベンチマークを改善したり、何が起こっているのか、ボトルネックがどこにあるのかについてより多くの洞察を得たりする方法についての情報もありがたいです。
私は変更している設定のドキュメントを読み込もうとしましたが、再び副作用を認識していません。不適切な値を選択するとパフォーマンスが低下する可能性さえあります。

今のところ、MyISAMインポート中に使用するチャットからの提案を試し、後でテーブルエンジンを変更します。
私はこれを試したいのですが、今のところ私のDROP TABLEクエリも完了するまでに数時間かかります。(私の設定が最適ではない別の指標のようです)。

追加情報:
私が現在使用しているマシンには、8 GBのRAMと5400RPMのソリッドステートハイブリッドハードドライブが搭載されています。
問題のテーブルから古いデータを削除することも目的としていますが、 a)開発中の
テストautomatic data cleanup featureと、
b)サーバーがクラッシュした場合に2番目のサーバーを交換用として使用するために、いくらか高速にインポートする必要があります(必要です) -最新のデータ、最後のインポートに24時間以上かかった)

mysql> SHOW CREATE TABLE monster\G
*************************** 1. row ***************************
       Table: monster
Create Table: CREATE TABLE `monster` (
  `monster_id` int(11) NOT NULL AUTO_INCREMENT,
  `ext_monster_id` int(11) NOT NULL DEFAULT '0',
  `some_id` int(11) NOT NULL DEFAULT '0',
  `email` varchar(250) NOT NULL,
  `name` varchar(100) NOT NULL,
  `address` varchar(100) NOT NULL,
  `postcode` varchar(20) NOT NULL,
  `city` varchar(100) NOT NULL,
  `country` int(11) NOT NULL DEFAULT '0',
  `address_hash` varchar(250) NOT NULL,
  `lon` float(10,6) NOT NULL,
  `lat` float(10,6) NOT NULL,
  `ip_address` varchar(40) NOT NULL,
  `cookie` int(11) NOT NULL DEFAULT '0',
  `party_id` int(11) NOT NULL,
  `status` int(11) NOT NULL DEFAULT '2',
  `creation_date` datetime NOT NULL,
  `someflag` tinyint(1) NOT NULL DEFAULT '0',
  `someflag2` tinyint(4) NOT NULL,
  `upload_id` int(11) NOT NULL DEFAULT '0',
  `news1` tinyint(4) NOT NULL DEFAULT '0',
  `news2` tinyint(4) NOT NULL,
  `someother_id` int(11) NOT NULL DEFAULT '0',
  `note` varchar(2500) NOT NULL,
  `referer` text NOT NULL,
  `subscription` int(11) DEFAULT '0',
  `hash` varchar(32) DEFAULT NULL,
  `thumbs1` int(11) NOT NULL DEFAULT '0',
  `thumbs2` int(11) NOT NULL DEFAULT '0',
  `thumbs3` int(11) NOT NULL DEFAULT '0',
  `neighbours` tinyint(4) NOT NULL DEFAULT '0',
  `relevance` int(11) NOT NULL,
  PRIMARY KEY (`monster_id`),
  KEY `party_id` (`party_id`),
  KEY `creation_date` (`creation_date`),
  KEY `email` (`email`(4)),
  KEY `hash` (`hash`(8)),
  KEY `address_hash` (`address_hash`(8)),
  KEY `thumbs3` (`thumbs3`),
  KEY `ext_monster_id` (`ext_monster_id`),
  KEY `status` (`status`),
  KEY `note` (`note`(4)),
  KEY `postcode` (`postcode`),
  KEY `some_id` (`some_id`),
  KEY `cookie` (`cookie`),
  KEY `party_id_2` (`party_id`,`status`)
) ENGINE=InnoDB AUTO_INCREMENT=13763891 DEFAULT CHARSET=utf8

2
1万行や10万行のように、インポートを減らしてみましたか
ypercubeᵀᴹ

1
実行SHOW CREATE TABLE yourtable\Gして、この1000万行のテーブルのテーブル構造を表示してください。
RolandoMySQLDBA 2014

@RolandoMySQLDBAのように(フィールド名が覆い隠されて)
nuala

二重書き込みバッファー(innodb_doublewrite = 0)を無効にすると、MySQLのインストールはクラッシュしても安全ではありません。停電(MySQLのクラッシュではない)が発生した場合、データが静かに破損する可能性があります。
jfg956 2014

回答:


13

まず、何百万もの行をInnoDBテーブルに送り込むときにInnoDBに対して何をしているのかを知る必要があります。InnoDBアーキテクチャを見てみましょう。

InnoDBアーキテクチャ

左上隅に、InnoDBバッファープールの図があります。挿入バッファー専用のセクションがあることに注意してください。それは何をしますか?セカンダリインデックスへの変更をバッファプールからシステムテーブルスペース(別名ibdata1)内の挿入バッファに移行することが目的です。デフォルトでは、innodb_change_buffer_max_sizeは25に設定されています。これは、バッファープールの最大25%をセカンダリインデックスの処理に使用できることを意味します。

あなたの場合、InnoDBバッファープール用に6.935 GBがあります。セカンダリインデックスの処理には最大1.734 GBが使用されます。

今、あなたのテーブルを見てください。13個のセカンダリインデックスがあります。処理する各行は、セカンダリインデックスエントリを生成し、それを行の主キーと結合し、それらをペアとして、バッファープールの挿入バッファーからibdata1の挿入バッファーに送信する必要があります。これは、各行で13回発生します。これに1000万を掛けると、ボトルネックが発生しそうな気がします。

1回のトランザクションで1000万行をインポートすると、すべてが1つのロールバックセグメントに蓄積され、ibdata1のUNDOスペースがいっぱいになることを忘れないでください。

提案

提案#1

このかなり大きなテーブルをインポートするための私の最初の提案は

  • 一意でないインデックスをすべて削除します
  • データをインポートする
  • 一意でないすべてのインデックスを作成する

提案#2

重複するインデックスを削除します。あなたの場合、あなたは持っています

KEY `party_id` (`party_id`),
KEY `party_id_2` (`party_id`,`status`)

両方のインデックスはで始まり、party_idセカンダリインデックスの処理を少なくとも7.6%増やすことで、13のうち1つのインデックスを取り除くことができます。最終的に実行する必要があります

ALTER TABLE monster DROP INDEX party_id;

提案#3

使用しないインデックスを削除します。アプリケーションコードを調べて、クエリがすべてのインデックスを使用しているかどうかを確認します。pt-index-usageを調べて、使用されていないインデックスを提案させることもできます。

提案#4

デフォルトは8M なのでinnodb_log_buffer_sizeを64Mに増やす必要があります。大きなログバッファーはInnoDB書き込みI / Oパフォーマンスを向上させる可能性があります。

エピローグ

最初の2つの提案を配置して、以下を実行します。

  • 13の一意でないインデックスを削除する
  • データをインポートする
  • インデックスを除くすべての一意でないインデックスをparty_id作成する

おそらく以下が役立つかもしれません

CREATE TABLE monster_new LIKE monster;
ALTER TABLE monster_new
  DROP INDEX `party_id`,
  DROP INDEX `creation_date`,
  DROP INDEX `email`,
  DROP INDEX `hash`,
  DROP INDEX `address_hash`,
  DROP INDEX `thumbs3`,
  DROP INDEX `ext_monster_id`,
  DROP INDEX `status`,
  DROP INDEX `note`,
  DROP INDEX `postcode`,
  DROP INDEX `some_id`,
  DROP INDEX `cookie`,
  DROP INDEX `party_id_2`;
ALTER TABLE monster RENAME monster_old;
ALTER TABLE monster_new RENAME monster;

データをにインポートしますmonster。次に、これを実行します

ALTER TABLE monster
  ADD INDEX `creation_date`,
  ADD INDEX `email` (`email`(4)),
  ADD INDEX `hash` (`hash`(8)),
  ADD INDEX `address_hash` (`address_hash`(8)),
  ADD INDEX `thumbs3` (`thumbs3`),
  ADD INDEX `ext_monster_id` (`ext_monster_id`),
  ADD INDEX `status` (`status`),
  ADD INDEX `note` (`note`(4)),
  ADD INDEX `postcode` (`postcode`),
  ADD INDEX `some_id` (`some_id`),
  ADD INDEX `cookie` (`cookie`),
  ADD INDEX `party_id_2` (`party_id`,`status`);

試してみる !!!

代替

monster_csvインデックスのないMyISAMテーブルと呼ばれるテーブルを作成し、これを行うことができます:

CREATE TABLE monster_csv ENGINE=MyISAM AS SELECT * FROM monster WHERE 1=2;
ALTER TABLE monster RENAME monster_old;
CREATE TABLE monster LIKE monster_old;
ALTER TABLE monster DROP INDEX `party_id`;

データをにインポートしますmonster_csv。次に、mysqldumpを使用して別のインポートを作成します

mysqldump -t -uroot -p mydb monster_csv | sed 's/monster_csv/monster/g' > data.sql

mysqldumpファイルdata.sqlは、一度に10,000-20,000行をインポートするINSERTコマンドを拡張します。

次に、mysqldumpをロードします。

mysql -uroot -p mydb < data.sql

最後に、MyISAMテーブルを削除します

DROP TABLE monster_csv;

私はそれらすべてのキーを認識していませんでしたが(それは私のデザインではありません)、あなたの説明は非常に説得力があるようです。今日は別の試みを始めるのは遅いですが、私は明日何を試すべきかについていくつかの素晴らしいアドバイスを参照してください。常にお知らせします!<3
ヌアラ2014

1
monsterInnoDBテーブルにキーがない場合、20分未満で(テーブルだけでなく)データベース全体をインポートできました。キーの追加には約かかりました。さらに20分。これはこの場合私の問題をかなり解決すると思います。どうもありがとうございました!
ヌアラ2014

8

私はコメントを書きたかったのですが(これは決定的な答えではないため)、長くなりすぎました。

私はあなたにいくつかの幅広いアドバイスを与えるつもりです、そしてあなたが望むなら、それぞれについて詳細に行くことができます:

  • 耐久性を低下させます(すでに一部を実行しています)。最新バージョンでは、さらに多くのことができます。インポートでは破損は問題にならないため、二重書き込みバッファーを無効にすることもできます。
  • バッファリングを増やす:トランザクションログサイズを増やし、使用可能なバッファプールサイズを増やします。トランザクションログファイルの使用状況とチェックポイントを監視します。インポートの巨大なログを恐れないでください。
  • 巨大なトランザクションを避けてください。ロールバックは不要なデータでいっぱいになります。これはおそらくあなたの最大の問題です。
  • SQLはボトルネックになり、SQLオーバーヘッド(handlersocket、memcached)を回避するか、同時に複数のスレッドで同時にロードします。同時実行性は、多すぎず、少なすぎず、スイートスポットに到達する必要があります。
  • 主キーの順序でデータをロードすると、断片化が問題になる可能性があります
  • IOがボトルネックであり、CPUとメモリによって速度が低下しない場合は、InnoDB圧縮をテストします。
  • 後で(場合によってはより速く)セカンダリキーを作成してみてください。インデックス付きデータをロードしないでください。DISABLEKEYSはInnoDBに影響しません。そうでない場合は、挿入バッファーを監視します(おそらく、バッファープールの半分を追い越します)。
  • チェックサムアルゴリズムを変更または無効にします。おそらく問題ではありませんが、ハイエンドフラッシュカードではボトルネックになります。
  • 最後の手段:サーバーを監視して現在のボトルネックを見つけ、軽減を試みます(InnoDBはそれについて非常に柔軟です)。

これらの一部は、非インポート(通常の操作)では安全でないか、推奨されないことに注意してください。


どうもありがとうございました!最初にインデックスに関するRolandoのアイデアを試してみたいのですが、この「トランザクションロールバック」の問題はまだ問題だと思います。これについて詳しく説明してもらえますか?私は、インポート時に可能な限り、この機能の多くとして無効にすると、生産に入るときだけ再度有効だと思う〜私は思う...
nuala

1
Rolandoの提案は私のポイント#7です。ロールバックのオーバーヘッドを回避することはSET SESSION tx_isolation='READ-UNCOMMITTED';、複数のスレッドを並行してインポートする場合にのみ役立つ)と、バッチでの挿入に関する@ypercubeコメントの組み合わせと同じくらい簡単です。あなたはここに完全な例があります。mysqlperformanceblog.com/2008/07/03/...のを確認してください最新のInnoDBのバージョンのすべての機能の利点を得ている:mysqlperformanceblog.com/2011/01/07/...
jynus

1
小さなチャックでのインポートは避け、むしろ「すべて込み」の操作を行うという一般的な印象がありましたが、マルチスレッド化によっていくつかの可能性が開かれると思います。それは非常に特定のケースだと思います。ただし、この調整(あなたの#7)だけで1時間未満で完全にインポートできたので、私はRolandoの回答を受け入れましたが、あなたのリストは間違いなく価値がなく、DBが少し成長しているので、すぐに参照に使用すると思います私を怖がらせます:)
ヌアラ2014

@yoshiに同意します。回答は、トラブルシューティングとパフォーマンスの改善の点でより包括的です。+1
RolandoMySQLDBA 2014

3

これまでのところ、優れたヒントのほとんどは提供されていますが、最良のヒントについては多くの説明がありません。詳細をお知らせします。

まず、インデックスの作成を遅らせることは良いことであり、他の応答に十分な詳細があります。私はそれに戻りません。

大きいInnoDBログファイルは非常に役立ちます(MySQL 5.6を使用している場合、MySQL 5.5では増やすことができないため)。7 GBのデータを挿入する場合、合計ログサイズを少なくとも8 GBにすることをお勧めinnodb_log_files_in_groupします(デフォルト(2)を維持し、innodb_log_file_size4 GBでバンプします)。この8 GBは正確ではありません。少なくともREDOログのインポートサイズであり、おそらくそのサイズの2倍または4倍である必要があります。InnoDBログサイズの背後にある理由により、ログがほぼ満杯になると、InnoDBは積極的にそのバッファープールをディスクにフラッシュし始めて、ログがいっぱいになるのを回避します(ログが満杯の場合、InnoDBはデータベースの書き込みが完了するまで、バッファプールのページがディスクに書き込まれます)。

大きいInnoDBログファイルが役立ちますが、主キーの順序で挿入する必要もあります(挿入する前にファイルを並べ替えます)。主キーの順序で挿入すると、InnoDBは1つのページを埋め、次に別のページを埋めていきます。主キーの順序で挿入しないと、次の挿入はページがいっぱいになり、「ページ分割」が発生する可能性があります。このページ分割はInnoDBにとって費用がかかり、インポートが遅くなります。

RAMが許す限りの大きさのバッファプールがすでにあり、テーブルがそれに収まらない場合、RAMを追加購入する以外にできることは多くありません。ただし、テーブルがバッファープールに収まるが、バッファープールの75%より大きい場合はinnodb_max_dirty_pages_pct、インポート中に85または95に増やしてみてください(デフォルト値は75)。この構成パラメーターは、ダーティページのパーセンテージがこの制限に達すると、バッファープールの積極的なフラッシュを開始するようにInnoDBに指示します。このパラメーターを増やすことで(そして、データサイズが運が良ければ)、インポート中の積極的なIOを回避し、それらのIOを後で遅らせることができます。

多分(これは推測ですが)多くの小さなトランザクションでデータをインポートすると役立つでしょう。REDOログがどのように構築されるかは正確にはわかりませんが、トランザクションの進行中にRAM(およびRAMが多すぎる場合はディスク)にバッファーされると、不要なIOが発生する可能性があります。あなたはこれを試すことができます:ファイルがソートされたら、それを多くのチャンクに分割し(16 MBと他のサイズで試してください)、それらを1つずつインポートします。これにより、インポートの進行状況を制御することもできます。インポートの実行中にデータが他のリーダーに部分的に表示されないようにするには、別のテーブル名を使用してインポートし、後でインデックスを作成してから、テーブルの名前を変更できます。

ハイブリッドSSD / 5400RPMディスクについては、それらについて、およびこれを最適化する方法についてはわかりません。5400RPMはデータベースにとって低速に見えますが、SSDがそれを回避している可能性があります。ディスクのSSD部分をREDOログへの順次書き込みで埋めている可能性があり、SSDがパフォーマンスを低下させています。私は知らない。

試してはいけない(または注意が必要な)悪いヒントは次のとおりです。マルチスレッドを使用しないでください。InnoDBでページ分割を回避するために最適化するのは非常に困難です。マルチスレッドを使用する場合は、異なるテーブル(または同じテーブルの異なるパーティション)に挿入します。

マルチスレッドを検討している場合は、マルチソケット(NUMA)コンピュータを使用している可能性があります。この場合は、MySQLスワップの狂気の問題を回避してください。

MySQL 5.5を使用している場合は、MySQL 5.6にアップグレードします。これには、REDOログサイズを増やすオプションがあり、バッファプールのフラッシュアルゴリズムが向上しています。

インポートで頑張ってください。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.