このシリーズはPHPでデータベースを扱いたいと考えている方を対象とした記事となっています。
今回は少し応用的な内容を紹介します。データベースを扱うプログラムには様々な種類のものがありますが、複数の利用者が同時にアクセスしているWebサービスのようなシステムではデータベースを利用したものが多くあります。複数の利用者が操作する中で、データの更新や削除といった処理が同時に起こってしまうと、データの整合性が合わなくなってしまうなどの不都合が生じることがあります。
今回紹介するデータベース機能のトランザクションは、そういった問題を回避することに役立ちますので、是非参考にしてみてください。
トランザクションとは
トランザクションというのは英単語としては「取引」のような意味ですが、情報処理分野においては「整合性を担保したまま(矛盾なく)複数の処理をまとめて実行する」という意味で使われます。特にデータベース分野においては、複数のSQLを実行した後で、適用(Commit)または巻き戻し(Rollback)を判断することが可能な技術を指し、逐次実行による不整合の発生を回避する重要な役目を担います。
トランザクションを開始して、Commit/Rollbackするまでの間は、トランザクション中やトランザクションが掛かっているという表現が用いられます。その間に発行したSQLは、いずれも即時反映されず仮の状態となります。これはPHP/PDOといったプログラム分野に限らず、コンソールや他のデータベースツールなどからも利用することができるデータベース上での一般的な機能です。
余談ですが、トランザクションシステムというとITソフトウェアの分類を表し、金融や航空券の予約といった「取引/データ更新の頻度が極端に高いシステム」のことを指します。また同じデータベース分野であっても、トランザクションという言葉は違う意味で使われることがあります。データベースの基本構成要素であるテーブルを役割で区分けする場合、基本となる情報をマスター、履歴などの一般データの事をトランザクションと呼称します。この場合はトランザクションのことをトランと略して呼ばれることもあります。販売管理などでの顧客台帳はマスターで、取引履歴はトランといった感じです。
今回扱うのはデータベースの機能のトランザクションになります。
トランザクションのメリット
複数のSQL処理を順次実行するのは単純で、一人でアクセスしているだけであれば、特に問題は発生しないかもしれません。
しかし、複数人でデータベースを利用するシステムの場合、SQLがどういった順番で実際のデータベースに対して実行されるのか分からず、連続して発行したつもりのSQLが、間に他の人のSQLが実行されてしまうということが想定されます。
他の利用者とSQLの発行合戦になると、更新しようとしたデータが誰かに削除されてしまっていたり、最新の情報を取得しようとすると他の人がデータ登録して最新が変ってしまうなど、プログラムが予測できない動作をする可能性がでてきます。トランザクションを使って複数のSQLを一つにまとめて実行すると、こういった問題を回避することができるのが最大のメリットと言えるでしょう。
また、データベースシステムにもよりますが、単一のSQL実行を複数回実行するよりも、一つにまとまったトランザクションで実行する方が、実行にかかる合計時間が短くなる・高速化できるといった場合もあります。
プログラムとは関係ないところでも、データのメンテナンス等の作業で危険度の高いSQLを直接実行する必要がある場合などでは、トランザクションを利用して十分な確認をしながら進めるのが良いでしょう。
以下は、テーブルbooksのcategory_idの値が4のデータを対象に、category_idの値を3に更新するという簡単な例です。BEGINによってトランザクションを開始し、対象データの確認~更新~更新後チェックという3つのSQLを実行しています。以下はMySQLでのトランザクション処理の一例です。
// データの更新を直接データベースに行う場合の、トランザクション活用例
// booksテーブルのcategory_id=4のデータをcategory_id=3に更新
// トランザクションの開始
BEGIN;
// 更新前(対象)データの確認
SELECT * FROM books WHERE category_id = 4;
// 更新
UPDATE books SET category_id = 3 WHERE category_id = 4
// 更新後データの確認
SELECT * FROM books WHERE category_id = 4;
// トランザクションの破棄 (巻き戻し)
// 確認が取れた後で、ここだけCOMMITに置き換える
ROLLBACK;
すべてのSQLを実行し終わった後に、ROLLBACKを行っているので、全ての処理が無かったことになっています。実際にはデータベースへ反映されませんが、SELECT文などの途中経過は得られ、画面にも更新前~後の状況が表示され、確認できます。十分な確認が取れたらROLLBACKとなっている部分だけをCOMMITに書き換えて実行することで、途中のSQLが間違ってしまう危険性を排除します。
PDOでのトランザクションの実際
では実際にPDOからトランザクションを掛ける方法を見ていきましょう。下記例のSQLはbooksテーブルのcategory_idを4から3に更新かけるものになっています。
// データベースへ接続
$conn = new PDO($dsn, $user, $password);
// トランザクション開始
$conn->beginTransaction();
// SQL文を準備
$sql = "UPDATE books SET category_id = 3 WHERE category_id = 4";
// Query実行
$conn->query($sql);
// データベースへの処理を反映。戻す場合はrollback();
// $conn->rollback();
$conn->commit();
PDOオブジェクトのbeginTransactionによって、トランザクションを開始し、UPDATE文によるデータベースの処理が終わった後でcommitを実行して確定しています。今回の例では実行するSQLが一つだけですが、間に複数のSQLを実行することも可能です。
実際には、実行した結果が正しい状態かどうかを確認して異常がなければcommitし、異常が検知された場合はrollbackによってデータベースへの処理を取り消し(巻き戻し)を行い、利用者に対してエラーが発生した旨を通知する、といった処理を行うことになるでしょう。
比較的よくある「登録データのIDが欲しい」などでも使える
データベースの処理をプログラムから行っていると、INSERT文でデータを登録した直後に、登録したデータのID(Primary Key)を取得したいという状況は比較的よくあります。他のテーブルにもそのIDを含んだデータを追加する必要があるなどです。こういった場合のデータ整合性を担保するために、トランザクションは便利です。
// データベースへ接続
$conn = new PDO($dsn, $user, $password);
// トランザクション開始
$conn->beginTransaction();
// 登録 SQL
$sql = "INSERT INTO books(name) VALUES('new Book')";
$conn->query($sql);
// 最終ID取得 SQL
$sql = "SELECT last_insert_id() AS last_id";
$st = $conn->query($sql);
$result = $st->fetchAll();
// データベースへの処理を反映。戻す場合はrollback();
// $conn->rollback();
$conn->commit();
最終IDを取得するlast_insert_id()はMySQLの機能です。後で処理しやすいようにAS句で結果セットの列に名前を付けています。最終IDの取得は、PDOに抽象化されたPDO::lastInsertIdという関数も用意されているので、こちらを利用しても実現が可能です。
複数のSQLをトランザクションでまとめているため、booksテーブルに対して他の利用者が次々にデータを登録をしているような状況でも、正しく自分が登録した最後のIDを取得することができます。トランザクションがない状況だと、他の利用者が登録したIDを取得してしまう危険性があるため、こういった状況ではトランザクション処理は欠かせません。
まとめ
トランザクション処理は、これまでに紹介してきたPDOの基本的な機能とは異なり、(自分一人で利用するシステムを作っている場合など)制作するプログラムによっては必要ない事もあるかもしれません。ただ、Webシステムとして公開するものであったり、IT企業に入ってから作るようなシステムの場合、データの整合性を保ち高い品質のソフトウェアとするために、高い確率で必要となる重要な技術の一つです。
トランザクションは、PDOで抽象化されていることからも分かるように、MySQLやPostgreSQLといった利用するデータベース製品に依らず、ほぼすべてのデータベースシステムで実装されている機能です。プログラムとして単に利用できるようになるだけでなく、どのような効果があり、どういった時に使用しなければならないのかを理解しておくことが大事なので、何度も実践してしっかり確認しておきましょう。