MySQL 8.0から、トランザクションのスケジューリングが新しいロジックに置き換わった、
とのことなので、これについて少し調べてみました。
基本となる情報はMySQL Server Teamブログの以下の記事です。
Contention-Aware Transaction Scheduling Arriving in InnoDB to Boost Performance
少しググったところでは日本語での記事が見当たらなかったので、
とりあえず上記記事をかいつまんで日本語にした、
というところで少しは意味があるかと思って書いてます。
さて、本題に入ります。
大体のデータベース製品において、ロック獲得のメカニズムは、
FIFO(First In First Out)であり、
アクセスしようとしたリソースがロックされていた場合、
後ろに行列を作って到着した順にリソースを獲得していきます。
言い換えれば、トランザクションに重みづけや優先順位などはなく、
リソースの獲得は早い者勝ち、ということです。
これに対してMySQL 8.0.3で採用された
Contention-Aware Transaction Scheduling(CATS)は、
大量のトランザクションを待たせているようなトランザクションが
ロック獲得の列に並んだ場合、
そのトランザクションに優先的にロック獲得の権利を与えよう、
という挙動をします。
なお、待たせているトランザクション、という中には、
直接待たせているトランザクションだけではなく、
間接的に待たせているトランザクション、
つまり、自分が待たせているトランザクションが待たせているトランザクションも含まれます。
ここで、MySQL Server Teamブログでは、
タクシー(Cab)のドライバーとバスのドライバーがコーヒーショップの列に並ぶ、
という例を出しています。
バスのように大量のお客さんを持っているようなドライバーと、
タクシーのように少数のお客さんしかいないドライバーを比較した場合、
バスのドライバーにコーヒーショップの列への割り込みを許すことで、
より多くの乗客が目的地に早く到達できるようになる、ということです。
ここで重要なのは、
バスのドライバーがタクシーのドライバーよりも後に列に並んだとしても、
先にコーヒーを受け取ることができる
という点です。
また、ブログでPeter Zaitsevが質問している通り、
「小さい(他のトランザクションを待たせていない)トランザクションは
いつまで経ってもロックが獲得できないのではないか?」
という問題があります。
(starvation problemというそうです)
これに対しては、
長く待たされているトランザクションの優先順位を上げたり、
一時的にロックへのエンキューをさせなくする(やや訳に自信なし)などの
対策をしているそうです。
理屈としてはこんなところです。
これを踏まえて、実際に試してみました。
※CATS発動は、待たされているトランザクションが32に到達した時だそうなので、
それを踏まえての実験です。
1. TRX1~TRX3: begin; 2. TRX1(ロック待ちの根本原因): select * from t1 where id = 10 for update; # id = 10の行のロックを取る。 3. TRX2(Cabドライバー): update t1 set comment = 'updated by cab driver' where id = 10; # TRX1によって待たされる。 4. TRX3(バスドライバー): select * from t1 where id = 5 for update; # id = 5の行のロックを取る。 5. TRX4~35(バスの乗客): update t1 set comment = 'updated' where id = 5; # TRX3によって待たされる。 6. TRX3(バスドライバー): update t1 set comment = 'updated by bus driver' where id = 10; # TRX1によって待たされる。 # この時点で、TRX2とTRX3は、TRX1のid = 10のロック開放待ちをしており、 # TRX4~35はTRX3のid = 5のロック開放待ちをしている。 # TRX2よりTRX3の方が大量のトランザクションを待たせているが、 # TRX2の方が到着したのは先。 7. TRX1(ロック待ちの根本原因): commit; # TRX3がid=10のロックを獲得。ここが重要。 # TRX2はまだ待たされている。 8. TRX3(バスドライバー): commit; # TRX2がid=10のロックを獲得 # 同時にTRX4~35(バスの乗客)が順次ロックを獲得し、commitしていく(autocommit=ONだから) 9. TRX2(Cabドライバー): commit; # すべてのロックが解放され、トランザクションが完結する。
結果、
id=5は’updated’、id=10は’updated by cab driver’となりました。
# Cabドライバーの方が後にトランザクションを実行したから。
ここで、TRX4~35(バスの乗客)は32個あり、
ギリギリCATSが発動する数になっていますが、
これが1つ少なく31個しかトランザクションがない状態で試したところ、
7でTRX1がコミットした直後にロックを獲得するのは、
FIFOロジックに従い、先に到着したTRX2となりました。
その結果、TRX2の方がTRX3より先に実行されるため、
最終的な結果としてはid=10は’updated by bus driver’となりました。
5.7でも念のため試してみたところ、
やはり7のところでTRX2(先に到着した方)にロックの権利が先に渡されました。
これを知っていてどのくらい役に立つか、
と言われると正直難しいですが、
ロックの問題に取り組む時には知っておかないと
思わぬところでハマる可能性があるかな、
と思いました。
では。