最終更新日:
公開日:
レシピ
データベース PDO
PDOのトランザクション
データベースの内容を更新するときに重要なトランザクションについて、PDOでの実用的なコード例を交えて紹介します。
この記事のポイント
- PDOでトランザクションを実装する
- MySQL、MariaDB、PostgreSQL、SQLiteの4種類のデータベースを使ってトランザクションを実装する
目次
PDOでトランザクションを実装する
今回はPDOでトランザクションを実装する方法について解説します。
まずはトランザクションという機能自体について解説してから、MySQL、MariaDB、PostgreSQL、SQLiteの4種類のデータベースを使ってトランザクションを実装するコード例を紹介していきます。
すでにトランザクションのことは知っていて実装方法のみ知りたい方は、次の「データベースのトランザクションについて」はスキップしてください。
PDO自体の基本的な内容については「PDOについて」を、データベースへの接続方法については「PDOを使ってデータベースへ接続」をご覧ください。
データベースのトランザクションについて
データベースの「トランザクション」という機能について紹介します。
ここのセクションでトランザクションがどういうことを行うか、使うメリットがあるかをざっくり理解することができます。
データベースにおけるトランザクションは、データの更新処理(参照を除いたデータの新規登録、更新、削除)を「コミット(処理実行)」の命令があるまで仮実行して、コミットの命令が出た時に一括して本実行の処理を行います。
仮実行したときは「下書き」のような状態なのでデータベースには反映されません。
全てのSQLを仮実行して正常に処理ができる状態のときのみ、「コミット」によって本実行を行いデータベースに反映します。
トランザクションを使う1つ目のメリットは、実行する処理を一旦全て仮実行するため、全てのクエリが正常に実行できるかデータベースに影響を与えることなく確認することができます。
あわせて、コミットのときに一度のタイミングで全てのクエリを集中して本実行するので複数の更新処理を行うときは処理速度が向上します。
2つ目のメリットは、トランザクションを行なっている間は、「コミット(反映)」か「ロールバック(取り消し)」のいずれかが行われるまでは他のトランザクションを受け付けません。
そのため、データベースの同時アクセスによるデータの行き違い(データの不整合)を未然に防ぐことができます。
不特定多数のユーザーが同じタイミングでアクセスされることが想定されるシステムでは非常に重要な機能です。
以上の2つの大きなメリットがることから、適切なタイミングでトランザクションを行うとシステムの高速化&堅牢性を同時に向上させる効果があります。
PDOのトランザクションの流れを確認する
PDOではトランザクションを簡単に実装するためのメソッドが用意されています。
ここでは例として、MySQLのデータベースに新しくデータを登録するときのコードを使ってトランザクションの基本的な流れを紹介していきます。
赤字になっているコードはトランザクションのメソッドを実行する箇所です。
PDOのトランザクションコード例
<?php
// (1) 登録する値を設定
$first_name = '佐藤2';
$last_name = '太郎2';
$email = 'okay@gray-code.com';
$tel = '090-xxxx-xxxx';
try {
// (2) データベースに接続
$pdo = new PDO('mysql:charset=UTF8;dbname=test;host=localhost', 'username', 'password');
// (3) トランザクション開始
$pdo->beginTransaction();
// (4) データを登録するSQL
$stmt = $pdo->prepare('INSERT INTO users (
first_name, last_name, email, tel
) VALUES (
:first_name, :last_name, :email, :tel
)');
// (5) 値をセット
$stmt->bindParam( ':first_name', $first_name, PDO::PARAM_STR);
$stmt->bindParam( ':last_name', $last_name, PDO::PARAM_STR);
$stmt->bindParam( ':email', $email, PDO::PARAM_STR);
$stmt->bindParam( ':tel', $tel, PDO::PARAM_STR);
// (6) SQL実行
$res = $stmt->execute();
// (7) コミット
if( $res ) {
$pdo->commit();
}
} catch(PDOException $e) {
// (8) エラーメッセージを出力
echo $e->getMessage();
// (9) ロールバック
$pdo->rollBack();
} finally {
// (10) データベースの接続解除
$pdo = null;
}
beginTransactionメソッドがトランザクションの開始の合図となります。
このメソッドが実行されてからcommitメソッドかrollBackメソッドが実行されるまでは処理を仮実行の状態になります。
commitメソッドが実行されるとデータベースに変更を反映し、rollBackメソッドが実行されたときは仮実行のクエリは全てキャンセルします。
また、commitメソッドとrollBackメソッドのいずれも実行されないときもデータベースには変更が反映されません。
トランザクションの途中でデータベースが何かしらのエラーを起こした場合はPDOExceptionオブジェクトの例外が投げられるので、上記のようにtry文〜catch文で囲む形が基本的な使い方となります。
全て正常であればtry文の最後にcommitメソッド処理を実行し、一方で途中にエラーが起こった場合はcatch文に移動してrollBackメソッドを実行します。
以上のように、トランザクションを取り入れることで処理全体が正常に終了した場合のみデータベースに更新を反映し、一部でもうまく行かなかったときは処理前の状態に戻すことでデータの整合性を保つことができます。
以降ではMySQL、MariaDB、Postgresql、SQLite3のそれぞれの場合でPDOのトランザクションを実行する例を紹介していきます。
前提として、全てのデータベースには「test」というデータベースがあり、以下の構造を持つuser_listテーブルがあることとします。
カラム名 | 型 | その他 |
---|---|---|
id | 整数 | 主キー |
name | 文字列 | |
age | 整数 | |
created_at | 日付(またはタイムスタンプ、文字列) |
MySQL / MariaDBでトランザクションを実装する
MySQLとMariaDBに対してPDOのトランザクションを実装します。
この2つのデータベースは互換性があるため共通のコードを使用することができます。
今回は例として、テーブルuserにあるidカラムが5のデータの値を更新します。
user_list 更新前
id | name | age | created_at
----+------------+-----+------------
1 | 山田太郎 | 34 | 2021-06-24 11:20:00
2 | 山田太郎 | 33 | 2021-06-24 11:20:00
3 | 高岡幸子 | 30 | 2021-06-24 11:20:00
4 | 山田太郎 | 30 | 2021-06-24 09:12:47
5 | テスト健太 | 30 | 2021-06-01 10:49:05
以下のコードではカラムname、ageの値を(1)でセットした値に変更していきます。
コード例
<?php
// (1) 登録する値を設定
$id = 5;
$name = '岡島健太';
$age = '31';
try {
// (2) データベースに接続
$pdo = new PDO('mysql:charset=UTF8;dbname=test;host=localhost', 'username', 'password');
// (3) トランザクション開始
$pdo->beginTransaction();
// (4) データを更新するSQL
$stmt = $pdo->prepare('UPDATE user_list SET name = :name, age = :age WHERE id = :id');
// (5) 値をセット
$stmt->bindParam( ':id', $id, PDO::PARAM_INT);
$stmt->bindParam( ':name', $name, PDO::PARAM_STR);
$stmt->bindParam( ':age', $age, PDO::PARAM_INT);
// (6) SQL実行
$res = $stmt->execute();
// (7) コミット
if( $res ) {
$pdo->commit();
}
} catch(PDOException $e) {
// (8) エラーメッセージを出力
echo $e->getMessage();
// (9) ロールバック
$pdo->rollBack();
} finally {
// (10) データベースの接続解除
$pdo = null;
}
(3)のbeginTransactionメソッドを実行したタイミングでトランザクションを開始します。
(7)でcommitメソッドを実行するか、またはcatch文の中の(9)にあるrollBackメソッドを実行するまではトランザクションが続きます。
(5)でbindParamメソッドでセットした値は(6)のexecuteメソッドで仮実行されます。
先述の通り、この段階ではまだデータベースに反映されません。
executeメソッドはクエリが実行されるとtrueを返し、失敗したときはfalseを返します。
そこで(7)ではif文を使って返り値を確認し、正常にクエリが実行されたときのみcommitメソッドを実行するようにしています。
commitメソッドが実行されたタイミングでデータベースにも更新内容がしっかり反映されます。
user_list 更新後
id | name | age | created_at
----+------------+-----+------------
1 | 山田太郎 | 34 | 2021-06-24 11:20:00
2 | 山田太郎 | 33 | 2021-06-24 11:20:00
3 | 高岡幸子 | 30 | 2021-06-24 11:20:00
4 | 山田太郎 | 30 | 2021-06-24 09:12:47
5 | 岡島健太 | 31 | 2021-06-01 10:49:05
PostgreSQLでトランザクションを実装する
続いて、PostgreSQLでPDOのトランザクションを実装します。
基本的なコードは先ほどのMySQLと同様です。
今回はテーブルuser_listに次のようなデータが入っていることとします。
user_list 更新前
id | name | age | created_at
----+------------+-----+------------
1 | 山田太郎 | 30 | 2021-06-25
2 | 山田太郎 | 30 | 2021-06-25
3 | 山田太郎 | 26 | 2021-06-25
4 | 山田太郎3 | 30 | 2021-06-25
5 | 高岡幸子 | 26 | 2021-06-01
idカラムに3が入っているデータのnameカラムとageカラムのデータを更新します。
コード例
<?php
// (1) 登録するデータを用意
$id = 3;
$name = '山下健太';
$age = 27;
try {
// (2) データベースに接続
$pdo = new PDO('pgsql:dbname=test;options=\'--client_encoding=UTF8\';host=localhost', 'username', 'password');
// (3) トランザクション開始
$pdo->beginTransaction();
// (4) SQL作成
$stmt = $pdo->prepare("UPDATE user_list SET name = :name, age = :age WHERE id = :id");
// (5) 登録するデータをセット
$stmt->bindParam( ':id', $id, PDO::PARAM_INT);
$stmt->bindParam( ':name', $name, PDO::PARAM_STR);
$stmt->bindParam( ':age', $age, PDO::PARAM_INT);
// (6) SQL実行
$res = $stmt->execute();
// (7) コミット
if( $res ) {
$pdo->commit();
}
} catch(PDOException $e) {
// (8) エラーメッセージを出力
echo $e->getMessage();
// (9) ロールバック
$pdo->rollBack();
} finally {
// (10) データベースの接続解除
$pdo = null;
}
commitメソッドを実行するとidカラムに3が入っているデータは以下のように更新されます。
user_list 更新後
id | name | age | created_at
----+------------+-----+------------
1 | 山田太郎 | 30 | 2021-06-25
2 | 山田太郎 | 30 | 2021-06-25
3 | 山下健太 | 27 | 2021-06-25
4 | 山田太郎3 | 30 | 2021-06-25
5 | 高岡幸子 | 26 | 2021-06-01
SQLiteでトランザクションを実装する
最後にSQLiteでトランザクションを実装します。
全体の流れは先ほどの2つのデータベースと同じ内容です。
今回はテーブルuser_listに以下のようなデータが入っていることとします。
user_list 更新前
1|山田太郎|30|2021-06-25 13:22:50
2|佐藤遥|26|2021-06-27 11:02:54
3|岡部|19|2021-06-27 11:03:07
4|宮田えり|24|2021-07-02 10:05:02
5|杉田咲|23|2021-06-27 11:03:33
ここでは、テーブルuser_listにあるidカラムが3のデータの値を更新します。
コード例
<?php
// (1) 登録するデータを用意
$id = 3;
$name = '岡部倫太郎';
$age = 24;
try {
// (2) データベースに接続
$pdo = new PDO('sqlite:./sqlite/test.sqlite3');
// (3) トランザクション開始
$pdo->beginTransaction();
// (4) SQL作成
$stmt = $pdo->prepare("UPDATE user_list SET name = :name, age = :age WHERE id = :id");
// (5) 登録するデータをセット
$stmt->bindParam( ':id', $id, PDO::PARAM_INT);
$stmt->bindParam( ':name', $name, PDO::PARAM_STR);
$stmt->bindParam( ':age', $age, PDO::PARAM_INT);
// (6) SQL実行
$res = $stmt->execute();
// (7) コミット
if( $res ) {
$pdo->commit();
}
} catch(PDOException $e) {
// (8) エラーメッセージを出力
echo $e->getMessage();
// (9) ロールバック
$pdo->rollBack();
} finally {
// (10) データベースの接続解除
$pdo = null;
}
処理が正常に実行されると、idカラム「3」のnameカラムとageカラムが(1)で設定した値に更新されます。
user_list 更新後
1|山田太郎|30|2021-06-25 13:22:50
2|佐藤遥|26|2021-06-27 11:02:54
3|岡部倫太郎|24|2021-06-27 11:03:07
4|宮田えり|24|2021-07-02 10:05:02
5|杉田咲|23|2021-06-27 11:03:33
以上がPDOによるトランザクションの実装例になります。
トランザクションに関するbeginTransactionメソッド、commitメソッドrollBackメソッドはデータベースの種類に関係なく共通のため、あまり違いを気にすることなく使用することができます。