ひとり勉強ログ

ITエンジニアの勉強したことメモ

MySQL講座#12 トランザクション

トランザクションとは何か

トランザクションという言葉は、RDBMSの世界においては、「テーブルのデータに対する更新の単位」を表す。もっと簡単に言うとトランザクションは「データベースに対する1つ以上の更新をまとめて呼ぶときの名称」である。

テーブルに対する更新は、INSERT、DELETE、UPDATEという3つの道具を使って行う。しかし、更新は一般的に1回の操作で終わることはなく、複数の操作をまとめて連続的に行うことが多い。トランザクションとは、このような複数の操作を意味的にわかりやすくひとまとまりにしたもの、と考える。

例えば、トランザクションの例として、こんな状況がある。

いま、chojinテーブルの管理を任されているプログラマやSEだとする。上司がやってきてこんなことを言う。

「正義超人の超人強度を10下げて、その代わりに悪魔超人の超人強度を50上げて」

「UPDATEで更新すればOKだな」となる。

さて、このときトランザクションは次の2つの更新によって構成される。

UPDATE chojin
SET power = power - 10
WHERE category = '正義超人';
UPDATE chojin
SET power = power + 50
WHERE category = '悪魔超人';

上記2つの更新は、必ずセットで行われる必要がある。このように、「ワンセットで行われるべき更新の集合」は、必ず「トランザクション」としてひとまとめに扱う必要がある。

なお、1つのトランザクションに「どの程度の数の更新処理を含むか」あるいは、「どんな処理を含むか」という点についての固定的な基準は、DBMS側にはない。それはあくまで、ユーザの要求に従って決められるものだからである。

トランザクションを作るには

トランザクションを開始するには、「トランザクション開始文」と「トランザクション終了文」で更新を行うDML文(INSERT/UPDATE/DELTE文)を囲む、という形をとっている。

トランザクション開始文」は、MySQLでは「START TRANSACTION」である。

例えば、上記の2つのUPDATEを使って、トランザクションを作ると、以下のようになる。

START TRANSACTION;
UPDATE chojin
SET power = power - 10
WHERE category = '正義超人';
UPDATE chojin
SET power = power + 50
WHERE category = '悪魔超人';
COMMIT;

COMMIT-処理の確定

COMMITとは、トランザクションに含まれていた処理による変更をすべて反映して、トランザクションを終了するコマンドである。一度コミットしたら、もうトランザクションの開始前の状態に戻すことはできないので、コミットする前には、本当に変更を確定して良いか自問自答する必要がある。

■ROLLBACK-処理の取り消し

ROLLBACKは、トランザクションに含まれていた処理による変更をすべて破棄して、トランザクションを終了するコマンドである。ロールバックしたら、データベースの状態はトランザクションを開始する前の状態に戻る。一般的にコミットと違ってロールバックが大きな損失につながることはない。

トランザクションをローバックする例。

START TRANSACTION;
UPDATE chojin
SET power = power - 10
WHERE category = '正義超人';
UPDATE chojin
SET power = power + 50
WHERE category = '悪魔超人';
ROLLBACK;

上記のサンプルコードは、実行してもテーブルのデータに一切変更が生じない。最終行の「ROLLBACK」によって、処理がすべてキャンセルされるからである。そのため、コミットと違って、ロールバックするときは、比較的気軽に実行しても構わない。

ACID特性

DBMSトランザクションには、守るべき4つの大事な約束事が標準規格によって取り決められている。「ACID特性」と呼ばれているが、これらの約束は、どんなDBMSも守らなければならない一般的なルールである。

原子性(Atomicity)

トランザクションが終わったとき、そこに含まれていた更新処理は、すべて実行されるか、またはすべて実行されない状態で終わることを保証する性質のこと。例えば、上記の例を使用すると、正義超人の超人強度は10下げる処理は行われたが、悪魔超人の超人強度を50上げる処理は行われていない、という状態でトランザクションが終わることは絶対にない。この場合トランザクションの終了状態は2つとも実行される(COMMIT)か、または2つとも実行されない(ROLLBACK)か、二者択一である。

なぜこのような原子性が重要であるかは、トランザクションが中途半端な終わり方をすることがあり得る場合を考えると分かる。ユーザが2つのUPDATE文を1つのトランザクションとして定義したのに、DBMSが気分によってその片方しかじっこうしてくれない、なんてことがあっては、業務に支障をきたすことは明らかである。

一貫性(Consistency)

トランザクションに含まれる処理は、データベースにあらかじめ設定された制約、例えば主キーやNOT NULL制約を満たす、という性質である。例えば、NOT NULL制約の付加された列をNULLに更新したり、主キーの制約違反のレコードを挿入するようなSQL文は、エラーになり、実行できない。これをトランザクション的な言い方で表現すると、そういう違法なSQLは「ロールバックされた」ということになる。要するにそういうSQLは、一文単位で実行が取り消され、実行されなかったのと同じことになるのである。

なお、一貫性は「整合性」とも呼ばれる。

独立性(Isolation)

トランザクション同士が互いに干渉を受けないことを保証する性質である。この性質によって、トランザクション同士が入れ子になることがない。また、あるトランザクションによる変更は、トランザクション終了時までは、別のトランザクションから隠蔽される。したがって、あるトランザクションがテーブルにレコードを追加していたとしても、コミットされるまでは、ほかのトランザクションからはその新規に追加されたレコードは「見えない」状態にある。

永続性(Durability)

これは耐久性といっても良いのであるが、トランザクションが(コミットにせよロールバックにせよ)終了したら、その時点でのデータの状態が保存されることを保証する性質である。たとえシステム障害が発生してデータが失われたとしても、データベースは何らかの手段でこれを復旧させる手段を持たなければならない。

永続性がないと、せっかく無事にトランザクションをコミットして終了させても、システムにい障害が発生してデータが全部消えて最初から処理を全部やり直す必要がある、といった脱力してしまうような状況が起きてしまう。

この永続性を保証する方法は、実装によって異なるが、一番ポピュラーなものは、トランザクションの実行記録をディスクなどに保存しておき(このような実行記録を「ログ」と呼ぶ)、障害が起きた場合は、このログを使って障害前の状態に復旧する、という方法である。