奇特なブログ

「殊勝に値する行いや心掛け」を意味する、奇特な人になる為のブログです

続・ダブルブッキングを防ぐ予約追加プログラムを考えた

前回:
kitoku-magic.hatenablog.com

さぁ、思ったよりも早く、再開の時期がやってきました。

今回は、以下の差分を載っけました。

github.com

で、まず今回の差分で、特記しておきたい所を。


$retry_count = 5;
このリトライ回数なんですけど、これが特に1回だと、ダブルブッキングしていない正常なレコードでも入らない事が多く、

5回だと、殆ど発生しない(0%ではないですが)という結果でしたね。

どっちかというと、PostgreSQLの方がレコードが入らない事が多い感じ。

次は、


array(
PDO::ATTR_EMULATE_PREPARES => false,
// エラーモードを指定しないと、エラー時に例外にならないので必ず指定
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
)
ここが、今回一番驚きましたかね。

PDOって、そういう動きだったのね。

どうも、これに限らないですが、いわゆる「デフォルト値には、センスが出る」と思いましたね。

「ATTR_EMULATE_PREPARES 」も、デフォルトはtrueなんですよね・・・違和感全開ですね。

で、上記エラーモードを追加した事で、前回MySQLでよく分からない動きをしていた所は明確になりました。

単に、例外を投げていたという(笑)

なので、後で出てきますが、例外処理の所が、かなり賑やかになっています(笑)

次は、


if ('postgresql' === $db_system)
{
// BEGINの後に書かないと、有効にならない
$pdo->exec('SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;');
}
PostgreSQLは、REPEATABLE READだとダブルブッキングしてしまう(ネクスキーロックの有無の差かな?)ので、

こうしたんですがね。

BEGINの後って事なので、逆に言えば元(デフォルトはREAD COMMITED)に戻す必要はないと思いますが。

あと、SERIALIZABLEの事をどうやら勘違いしてまして、

直列化っていうから、てっきり前のトランザクションが終わるまで、処理が開始しないと思ったら、

普通に、並列で処理が走るんですね(笑)

なんですが、並列で処理は走るけれども、レコードの状態が直列化している前提を守ろうとするみたいで、

それが守れなくなると、エラー(これは後述)になるっぽいです。

次は、


SELECT :conference_room_id_select_column, :reservation_date_select_column, :reservation_time_start_select_column, :reservation_time_end_select_column
ここは、前回は「from dual」があったんですが、

PostgreSQLがあったらダメで、MySQLも無くてもOKだったので、こうなりました。

あんまり、こういうSQLは、デバッグがしづらい(SELECTが悪いのか、INSERTが悪いのか分かりにくい)ので、

書きたくないんですが、別々にする事で、どうしてもSQL間で「隙間」が出来てしまい、並列でレコードを入れにいくので。

次は、


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;
}
}
ちょっと長いですが、catchの中のコードですね。

特に、MySQLデッドロック時と、PostgreSQLシリアライズエラーの時のリトライ。

MySQLの方は、前述のPDOのエラーモードを指定していなかった時のエラーが、

どうやらコレになっていただけの様で。

で、デッドロックなのにリトライって所ですが、デッドロックってロック待ちになるわけでもなく、

下手にレコードが入ってダブルブッキングになるわけでもなく、あと少なくとも今回の場合は避けれそうにないので、

別にしても良いのでは?と。というより、リトライしなかったら、単にレコードが入らないんですけどね。

なので、リトライをしています。

PostgreSQLの方は、おそらく直列化した状態が維持出来なくなった時に前述のエラーが起きているっぽい。

ただ、こっちはエラーの発生タイミングが、COMMITしようとする時の様で、

おそらくそれが原因だと思うんですが、AUTO_INCREMENTなカラム(SERIALの事)の値が、

歯抜けになってしまいます(苦笑)

歯抜け気にしないなら良いわけですが、MySQLはならないのでねぇ。

コードからの引用は以上。

MySQLPostgreSQLの比較でいうと、

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 などを、流しながらやってましたね(笑)