標準のMyISAMテーブルのカウンターをインクリメントするWebサイトを作成しています。
簡略化した例:
UPDATE votes SET num = num + 1;
複数の接続が同じクエリを実行している場合、これにより問題が発生しますか、またはMySQLがそれを処理し、テーブルまたは何かをロックして競合がないことを確認しますか?
MyISAMテーブルは、テーブルレベルのロックを使用します。つまり、更新クエリの実行中はテーブル全体がロックされます。簡単な使用例の答えは次のとおりです。はい、これはスレッドセーフです。ただし、別のストレージエンジンを使用している場合や、更新に複数のテーブルが含まれている場合は、この限りではありません。
より明確にするために、MySQLマニュアルからの引用を以下に示します。
テーブルのロックにより、多くのセッションが同時にテーブルから読み取ることができますが、セッションがテーブルに書き込む場合は、最初に排他アクセスを取得する必要があります。更新中、この特定のテーブルにアクセスする他のすべてのセッションは、更新が完了するまで待機する必要があります。
設計に合う場合は、自動インクリメント列、トランザクション、または外部同期の使用を検討することもできます。
乾杯!
書き込みはアトミックですが、増分には読み取りも必要です。したがって、問題は次のとおりです。読み取りは安全ですか、つまり、インクリメントを実行している別のスレッドがインクリメントされる同じ値で終了しないことを確信していますか?疑問があります。これを行う100%正しい方法は次のとおりです。
-- begin transaction here
select counter from myCounters where counter_id = 1 FOR UPDATE;
-- now the row is locked and nobody can read or modify its values
update myCounters set counter = ? where id = 1;
-- set ? to counter + 1 programmatically
commit; -- and unlock...
はい、更新クエリを実行すると、テーブル(またはInnoDB形式のデータベースの行)が自動的にロックされます。
この形式のUPDATE
はアトミックです。 UPDATE
の他の形式は、SELECT ... FOR UPDATE
でトランザクションを使用することによりアトミックにすることができます。
同じ問題がありましたが、クエリはより複雑でした。
UPDATE CLB_SYNC_T SET PENDING_MESSAGES = PENDING_MESSAGES + ? WHERE USER_ID = ?
MyISAMをデフォルトのエンジンとして使用しても役に立たなかったため、SELECT FOR UPDATEを使用します。
SELECT FOR UPDATEを使用すると、MySQLが行全体を更新するためにテーブル全体をロックしなかったため、パフォーマンスが約10倍向上しました。
InnoDBを使用する場合の別のアプローチは、次のように複数の列で一意のインデックスを使用することです。
Table 'Sessions' {unique_key(browser_session_id、profile_id)//確実に、セッションごとに1つのエントリの挿入が1回発生することを保証します}
セッションからカウント(browser_session_id)を選択します
ユーザーごとに複数のセッションは許可されないため、一意のセッションの結果を保証します。
結論
利点
各挿入には事前選択が必要です。
不利益
すべての場合に適しているわけではありません。
書き込みパフォーマンスが低下する可能性があり、追加の管理が必要