前回:
kitoku-magic.hatenablog.com
さぁ、思ったよりも早く、再開の時期がやってきました。
今回は、以下の差分を載っけました。
で、まず今回の差分で、特記しておきたい所を。
このリトライ回数なんですけど、これが特に1回だと、ダブルブッキングしていない正常なレコードでも入らない事が多く、
$retry_count = 5;
5回だと、殆ど発生しない(0%ではないですが)という結果でしたね。
どっちかというと、PostgreSQLの方がレコードが入らない事が多い感じ。
次は、
ここが、今回一番驚きましたかね。
array(
PDO::ATTR_EMULATE_PREPARES => false,
// エラーモードを指定しないと、エラー時に例外にならないので必ず指定
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
)
PDOって、そういう動きだったのね。
どうも、これに限らないですが、いわゆる「デフォルト値には、センスが出る」と思いましたね。
「ATTR_EMULATE_PREPARES 」も、デフォルトはtrueなんですよね・・・違和感全開ですね。
で、上記エラーモードを追加した事で、前回MySQLでよく分からない動きをしていた所は明確になりました。
単に、例外を投げていたという(笑)
なので、後で出てきますが、例外処理の所が、かなり賑やかになっています(笑)
次は、
PostgreSQLは、REPEATABLE READだとダブルブッキングしてしまう(ネクストキーロックの有無の差かな?)ので、
if ('postgresql' === $db_system)
{
// BEGINの後に書かないと、有効にならない
$pdo->exec('SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;');
}
こうしたんですがね。
BEGINの後って事なので、逆に言えば元(デフォルトはREAD COMMITED)に戻す必要はないと思いますが。
あと、SERIALIZABLEの事をどうやら勘違いしてまして、
直列化っていうから、てっきり前のトランザクションが終わるまで、処理が開始しないと思ったら、
普通に、並列で処理が走るんですね(笑)
なんですが、並列で処理は走るけれども、レコードの状態が直列化している前提を守ろうとするみたいで、
それが守れなくなると、エラー(これは後述)になるっぽいです。
次は、
ここは、前回は「from dual」があったんですが、
SELECT :conference_room_id_select_column, :reservation_date_select_column, :reservation_time_start_select_column, :reservation_time_end_select_column
PostgreSQLがあったらダメで、MySQLも無くてもOKだったので、こうなりました。
あんまり、こういうSQLは、デバッグがしづらい(SELECTが悪いのか、INSERTが悪いのか分かりにくい)ので、
書きたくないんですが、別々にする事で、どうしてもSQL間で「隙間」が出来てしまい、並列でレコードを入れにいくので。
次は、
ちょっと長いですが、catchの中のコードですね。
if ('mysql' === $db_system)
{
if ('40001' === $e->errorInfo[0] && 1213 === $e->errorInfo[1])
{
// デッドロックの時は、リトライする
}
else if ('23000' === $e->errorInfo[0] && 1062 === $e->errorInfo[1])
{
// ユニーク制約エラーの時は、すぐに終了する
break;
}
else if ('HY000' === $e->errorInfo[0] && 1205 === $e->errorInfo[1])
{
// ロック取得のタイムアウトの時は、すぐに終了する(秒数の設定によっては、リトライでも良いが)
break;
}
else
{
// 上記以外のエラーの時は、すぐに終了する
break;
}
}
else if ('postgresql' === $db_system)
{
if ('40001' === $e->errorInfo[0] && 7 === $e->errorInfo[1])
{
// シリアライズエラーの時は、リトライする
}
else if ('23505' === $e->errorInfo[0] && 7 === $e->errorInfo[1])
{
// ユニーク制約エラーの時は、すぐに終了する
break;
}
else if ('55P03' === $e->errorInfo[0] && 7 === $e->errorInfo[1])
{
// ロック取得のタイムアウトの時は、すぐに終了する(秒数の設定によっては、リトライでも良いが)
break;
}
else
{
// 上記以外のエラーの時は、すぐに終了する
break;
}
}
特に、MySQLのデッドロック時と、PostgreSQLのシリアライズエラーの時のリトライ。
MySQLの方は、前述のPDOのエラーモードを指定していなかった時のエラーが、
どうやらコレになっていただけの様で。
で、デッドロックなのにリトライって所ですが、デッドロックってロック待ちになるわけでもなく、
下手にレコードが入ってダブルブッキングになるわけでもなく、あと少なくとも今回の場合は避けれそうにないので、
別にしても良いのでは?と。というより、リトライしなかったら、単にレコードが入らないんですけどね。
なので、リトライをしています。
PostgreSQLの方は、おそらく直列化した状態が維持出来なくなった時に前述のエラーが起きているっぽい。
ただ、こっちはエラーの発生タイミングが、COMMITしようとする時の様で、
おそらくそれが原因だと思うんですが、AUTO_INCREMENTなカラム(SERIALの事)の値が、
歯抜けになってしまいます(苦笑)
歯抜け気にしないなら良いわけですが、MySQLはならないのでねぇ。
コードからの引用は以上。
MySQLとPostgreSQLの比較でいうと、
1.速度は、PostgreSQL(登録だけなら0.2秒ぐらい)、MySQL(0.5秒ぐらい)と、意外にもPostgreSQLに軍配。
2.ただ、PostgreSQLは、歯抜けレコードが出来る(これはもう、テーブルロックしかないか?)
3.正常なレコードが入る確率は、どっちかといえばMySQLの方が高そう(でも、たった今、入らなかったけど)
他の、アプローチとしては、「事前に、レコードを入れておく」でしょうかね。
これなら、そもそもSELECTの空振りがしないので。
ただ、時間帯が固定される(極論、1分単位とかでレコードを作れば、ほぼ防げますが)のと、
レコード数が多くなるので、それはそれで問題が。
今回は、こんな所でしょうか。
それにしても、WEBでの予約システムってのは、最近意外と多くて、
1.↑の社内の会議室予約
2.音楽ライブチケットの予約
3.寿司の予約(くら寿司)
4.飛行機の座席予約
5.美容室とかの予約
6.公共施設の予約(ちょっとビックリ)
7.レストランの予約
と、まだまだあるんでしょうけど、排他制御が不要なWEBの予約システムは、きっと無いだろうなと。
なので、また再開する事でしょう、きっと(笑)
そういえば、全然関係ないですが、この実験をしていた時、
B'zのFeverっていう曲 http://j-lyric.net/artist/a00067d/l004249.html などを、流しながらやってましたね(笑)