web-dev-qa-db-ja.com

INSERT INTO ... SELECTロック動作を改善する方法

運用データベースでは、次の擬似コードSQLバッチクエリを1時間ごとに実行しました。

INSERT INTO TemporaryTable
    (SELECT FROM HighlyContentiousTableInInnoDb
     WHERE allKindsOfComplexConditions are true)

このクエリ自体は高速である必要はありませんが、HighlyContentiousTableInInnoDbからロックしていることに気付きました。他の非常に単純なクエリを作成するのに約25秒かかりました(他のクエリにかかる時間です)。

次に、そのような場合のInnoDBテーブルが実際にSELECTによってロックされていることを発見しました! http://www.mysqlperformanceblog.com/2006/07/12/insert-into-select-performance-with-innodb-tables/

しかし、私はOUTFILEを選択するという記事の解決策が本当に好きではありません。それはハックのようです(ファイルシステム上の一時ファイルは嫌なようです)。他のアイデアはありますか? InnoDBテーブルの完全なコピーを、コピー中にこの方法でロックすることなく作成する方法はありますか。次に、HighlyContentiousTableを別のテーブルにコピーして、そこでクエリを実行できます。

38
Artem

この質問への答えは、はるかに簡単になりました。-行ベースのレプリケーションと読み取りコミット分離レベルを使用します。

あなたが経験していたロックは消えます。

より長い説明: http://harrison-fisk.blogspot.com/2009/02/my-favorite-new-feature-of-mysql-51.html

23
Morgan Tocker

次のようなbinlog形式を設定できます。

SET GLOBAL binlog_format = 'ROW';

永続的にする場合は、my.cnfを編集します。

[mysqld]
binlog_format=ROW

クエリを実行する前に、現在のセッションの分離レベルを設定します。

SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
INSERT INTO t1 SELECT ....;

これで問題が解決しない場合は、現在のセッションだけでなく、サーバー全体で分離レベルを設定してみてください。

SET GLOBAL TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

永続的にする場合は、my.cnfを編集します。

[mysqld]
transaction-isolation = READ-UNCOMMITTED

READ-UNCOMMITTEDを、より良い分離レベルであるREAD-COMMITTEDに変更できます。

5
diamonddog

Innodbテーブルを使用するすべての人は、おそらくInnodbテーブルが非ロック読み取りを実行するという事実に慣れました。つまり、LOCK IN SHARE MODEやFOR UPDATEなどの修飾子を使用しない限り、SELECTステートメントは実行中に行をロックしません。

これは一般に正しいですが、注目すべき例外があります-INSERT INTO table1 SELECT * FROM table2。このステートメントは、table2テーブルのロック読み取り(共有ロック)を実行します。また、where句と結合を使用した同様のテーブルにも適用されます。 MyISAMテーブルで書き込みが行われた場合でも、読み取られるテーブルがInnodbであることが重要です。

では、なぜこれが行われ、MySQLのパフォーマンスと並行性にかなり悪かったのでしょうか?

その理由は-複製です。 MySQL 5.1以前では、レプリケーションはステートメントベースであるため、マスターで応答されたステートメントはスレーブと同じ効果を引き起こすはずです。 Innodbがソーステーブルの行をロックしない場合、他のトランザクションが行を変更し、INSERT .. SELECTステートメントを実行しているトランザクションの前にコミットできます。これにより、このトランザクションはINSERT ... SELECTステートメントの前にスレーブに適用され、マスターとは異なるデータになる可能性があります。読み取り中にソーステーブルの行をロックすると、他のトランザクションがINSERT…SELECTが行にアクセスする前に行を変更するため、この効果から保護されます。スレーブでも同じ順序で変更されます。トランザクションがアクセスされてINSERT…SELECTによってロックされた後に行を変更しようとすると、トランザクションはステートメントが完了するまで待機して、スレーブで適切な順序で実行されることを確認する必要があります。かなり複雑になりますか? 5.1の前にMySQLで正しく機能するには、レプリケーションを前に行う必要があることを知っている必要があります。

MySQL 5.1では、これと他のいくつかの問題は行ベースのレプリケーションで解決する必要があります。ただし、パフォーマンスを確認するための実際のストレステストはまだ行っていません。

考慮すべきもう1つの点は、INSERT…SELECTは実際にロックモードで読み取りを実行するため、バージョン管理を部分的にバイパスし、最新のコミットされた行を取得します。そのため、REPEATABLE-READモードで操作している場合でも、この操作はREAD-COMMITTEDモードで実行され、純粋なSELECTで得られる結果とは異なる結果をもたらす可能性があります。ちなみに、これはSELECT .. LOCK IN SHARE MODEおよびSELECT…FOR UPDATEにも適用されます。

レプリケーションを使用せず、バイナリログを無効にしている場合はどうすればよいですか?レプリケーションを使用しない場合は、innodb_locks_unsafe_for_binlogオプションを有効にできます。これにより、Innodbがステートメントの実行時に設定するロックが緩和され、一般に同時実行性が向上します。ただし、名前からわかるように、レプリケーションとポイントインタイムリカバリではロックが安全ではないため、innodb_locks_unsafe_for_binlogオプションは注意して使用してください。

バイナリログを無効にしても、リラックスロックをトリガーするには不十分です。 innodb_locks_unsafe_for_binlog = 1も設定する必要があります。これは、バイナリログを有効にしても、ロック動作の予期しない変更やパフォーマンスの問題が発生しないようにするためです。また、自分が何をしているのか本当にわかっている場合は、レプリケーションでこのオプションを使用することもできます。将来のバージョンでどの他のロックが緩和されるか、そしてそれがレプリケーションにどのように影響するか分からないかもしれないので、本当に必要でない限り、それをお勧めしません。

5
Vipin Jain

おそらくビューの作成コマンドを使用できます( ビューの作成構文 を参照)。例えば、

Create View temp as SELECT FROM HighlyContentiousTableInInnoDb WHERE allKindsOfComplexConditions are true

その後、このビューでinsertステートメントを使用できます。このようなもの

INSERT INTO TemporaryTable (SELECT * FROM temp)

これは私の提案です。

1
smg

免責事項:私はデータベースについてあまり経験がなく、このアイデアが実行可能かどうかもわかりません。そうでない場合は私を修正してください。

セカンダリ同等テーブルHighlyContentiousTableInInnoDb2を設定し、同じデータで新しいテーブルを更新し続ける最初のテーブルでAFTER INSERTなどのトリガーを作成する方法はどうでしょうか。これで、HighlyContentiousTableInInnoDb2をロックでき、すべてのクエリではなく、プライマリテーブルのトリガーのみをスローできるようになります。

潜在的な問題:

  • 2 xデータ保存
  • すべての挿入、更新、削除の追加作業
  • トランザクション的に適切でない場合があります
1
Internet Friend

いくつかの異常を許可できる場合は、ISOLATION LEVELを最も厳密でないもの(READ UNCOMMITTED)に変更できます。ただし、この間、誰かがur宛先テーブルから読み取ることができます。または、宛先テーブルを手動でロックできます(mysqlがこの機能を提供していると思いますか?)。

または、READ COMMITTEDを使用して、ソーステーブルもロックしないようにすることができます。ただし、コミットされるまで、宛先テーブルに挿入された行もロックします。

私は二番目を選びます。

1
Azho KG

CREATE TEMPORARY TABLE ... SELECT ...SQLSTATE[HY000]: General error: 1205 Lock wait timeout exceeded; try restarting transactionを使用して同じ問題に直面していました。

最初のクエリに基づいて、クエリを開始する前にHighlyContentiousTableInInnoDbをロックすることで私の問題を解決しました。

LOCK TABLES HighlyContentiousTableInInnoDb READ;
INSERT INTO TemporaryTable
    (SELECT FROM HighlyContentiousTableInInnoDb
    WHERE allKindsOfComplexConditions are true)
UNLOCK TABLES;

私はMySQLに慣れていませんが、トランザクション分離レベルSnapshotおよびRead committed snapshot SQL Serverで。これらのいずれかを使用すると、問題が解決するはずです。

0
MEMark

ロック(readlock)の理由は、並列トランザクションが現在書き込んでいる「ダーティ」データを読み取らないように、読み取りトランザクションを保護するためです。ほとんどのDBMSには、ユーザーが読み取りおよび書き込みロックを手動で設定および取り消すことができる設定が用意されています。ダーティデータの読み取りが問題にならない場合、これは興味深いかもしれません。

複数のトランザクションを使用するDBSでロックなしでテーブルから読み取る安全な方法はないと思います。

ただし、以下はブレーンストーミングです。スペースが問題にならない場合は、同じテーブルの2つのインスタンスを実行することを考えることができます。 HighlyContentiousTableInInnoDb2常に読み取り/書き込みトランザクションとHighlyContentiousTableInInnoDb2_shadowバッチアクセス用。 DBMS内のトリガー/ルーチンを介して自動化されたシャドウテーブルを埋めることができます。

別のアイデアは、すべてのトランザクションがテーブル全体にアクセスする必要があるという質問です。それ以外の場合は、ビューを使用して必要な列のみをロックできます。連続アクセスとバッチアクセスが列に関してばらばらの場合、それらが互いにロックしない可能性があります!

0
Philipp Andre