はじめに
たまによく使うテーブル設計の手法で、ロックテーブル?セマフォテーブル?(正式名称が分かりません...)というものがあります。今回はそれについての備忘録です。
設計
複数のプロセス(プログラムやスレッド、サーバーなど)で同じリソースを同時に使用すると、データの破損や二重実行などの問題が発生するため、排他制御が必要になります。本設計では行ロックを利用することで、排他制御を行います。
テーブルは非常にシンプルです。
-- ロックテーブルの作成。
CREATE TABLE m_lock (
lock_id VARCHAR(50) NOT NULL, -- ロック ID
lock_name NVARCHAR(100) NOT NULL, -- ロック名称
-- lock_id を主キーに設定する。
CONSTRAINT PK_m_lock PRIMARY KEY CLUSTERED (lock_id)
);
-- レコードの登録。
INSERT INTO m_lock (lock_id, lock_name)
VALUES ('0', 'XXXX 処理ロック');
実装
C# + SQL Server での実装例になります。
WITH (UPDLOCK, ROWLOCK, NOWAIT) は DB との接続が切れた瞬間に SQL Server 側で自動的にロックを強制解放してくれるという特性があるため、ゾンビロックが残らず、安心して使用できます。
private void Example()
{
try
{
// 1. トランザクションを開始する。
DB.BeginTransaction();
// 2. クエリを実行して、行ロックを試みる。
// ※ 先客がいる場合、待機せずに即座に例外が発生する。
DB.Execute("SELECT * FROM m_lock WITH (UPDLOCK, ROWLOCK, NOWAIT) WHERE lock_id = '0'");
// 3. ロックが取得できた場合、処理を行う。
ExecuteProcess();
// 4. 正常に処理が完了したら、トランザクションをコミットする。
DB.CommitTransaction();
}
catch (SqlException ex)
{
if (ex.Number == 1222)
{
// 1222 : ロック要求タイムアウトの場合、実行をスキップする。
Logger.Info("処理は既に別のプロセスで実行中のため、実行をスキップします。");
DB.RollbackTransaction();
}
else
{
Logger.Error(ex, "例外が発生しました。");
DB.RollbackTransaction();
}
}
catch (Exception ex)
{
Logger.Error(ex, "例外が発生しました。");
DB.RollbackTransaction();
}
}
参考
言及している記事が思ったよりもありませんでした。Web 屋さんのマイクロサービスなどで利用される分散ロックと考え方の方向性は同じかと思います。
余談
SQL Server の場合 sp_getapplock という組込のストアドプロシージャを使用すると、テーブルを用意しなくても実現できるようです。
おわりに
逆に Web 業界でも似たような考え方をするんだって気付きになりました。