PHPプログラミング

最終更新日:
公開日:

レシピ

データベース 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テーブルがあることとします。

テーブル名: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

以下のコードではカラムnameageの値を(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メソッドはデータベースの種類に関係なく共通のため、あまり違いを気にすることなく使用することができます。