PDOPHP拡張機能を使用してUbuntu18.04のPHPでMySQLトランザクションを実行する方法
著者は、 Write for DOnations プログラムの一環として、 Open Sourcing MentalIllnessを選択して寄付を受け取りました。
序章
MySQL トランザクションは、データベース内で単一のユニットとして実行される、論理的に関連するSQLコマンドのグループです。 トランザクションは、アプリケーションで ACID(原子性、一貫性、分離、および耐久性)コンプライアンスを実施するために使用されます。 これは、データベースでの処理操作の信頼性を管理する一連の標準です。
Atomicityは、関連するトランザクションの成功、またはエラーが発生した場合の完全な失敗を保証します。 一貫性は、定義されたビジネスロジックに従ってデータベースに送信されたデータの有効性を保証します。 分離とは、データベースに接続しているさまざまなクライアントの影響が相互に影響しないようにする同時トランザクションの正しい実行です。 耐久性により、論理的に関連するトランザクションがデータベースに永続的に残ります。
トランザクションを介して発行されたSQLステートメントは、成功するか、完全に失敗する必要があります。 クエリのいずれかが失敗した場合、MySQLは変更をロールバックし、データベースにコミットされることはありません。
MySQLトランザクションがどのように機能するかを理解するための良い例は、eコマースWebサイトです。 顧客が注文すると、アプリケーションは、ビジネスロジックに応じて、orders
やorders_products
などのいくつかのテーブルにレコードを挿入します。 単一の注文に関連するマルチテーブルレコードは、単一の論理ユニットとしてデータベースにアトミックに送信する必要があります。
もう1つのユースケースは、銀行のアプリケーションです。 クライアントが送金しているとき、いくつかのトランザクションがデータベースに送信されます。 送信者のアカウントから借方に記入され、受信者のパーティアカウントに貸方記入されます。 2つのトランザクションは同時にコミットする必要があります。 それらの1つに障害が発生した場合、データベースは元の状態に戻り、変更をディスクに保存する必要はありません。
このチュートリアルでは、 PDO PHP拡張機能を使用します。これは、PHPでデータベースを操作するためのインターフェイスを提供し、Ubuntu18.04サーバーでMySQLトランザクションを実行します。
前提条件
始める前に、次のものが必要になります。
- Ubuntu 18.04を使用した初期サーバーセットアップに従ってセットアップされた1つのUbuntu18.04サーバー(sudo非rootユーザーを含む)。
- システムにインストールされているApache、MySQL、およびPHP。 Linux、Apache、MySQL、PHP(LAMP)スタックをUbuntu18.04にインストールする方法に関するガイドに従うことができます。 手順4(仮想ホストの設定)をスキップして、デフォルトのApache設定を直接操作できます。
ステップ1—サンプルデータベースとテーブルの作成
MySQLトランザクションの操作を開始する前に、まずサンプルデータベースを作成し、いくつかのテーブルを追加します。 まず、rootとしてMySQLサーバーにログインします。
sudo mysql -u root -p
プロンプトが表示されたら、MySQLルートパスワードを入力し、ENTER
を押して続行します。 次に、データベースを作成します。このチュートリアルでは、データベースをsample_store
と呼びます。
CREATE DATABASE sample_store;
次の出力が表示されます。
OutputQuery OK, 1 row affected (0.00 sec)
データベース用にsample_user
というユーザーを作成します。 PASSWORD
を強力な値に置き換えることを忘れないでください。
CREATE USER 'sample_user'@'localhost' IDENTIFIED BY 'PASSWORD';
sample_store
データベースに対してユーザーの完全な特権を発行します。
GRANT ALL PRIVILEGES ON sample_store.* TO 'sample_user'@'localhost';
最後に、MySQL特権をリロードします。
FLUSH PRIVILEGES;
ユーザーを作成すると、次の出力が表示されます。
OutputQuery OK, 0 rows affected (0.01 sec) . . .
データベースとユーザーを配置すると、MySQLトランザクションがどのように機能するかを示すためのいくつかのテーブルを作成できるようになります。
MySQLサーバーからログアウトします。
QUIT;
システムがログアウトすると、次の出力が表示されます。
OutputBye.
次に、作成したsample_user
の資格情報を使用してログインします。
sudo mysql -u sample_user -p
sample_user
のパスワードを入力し、ENTER
を押して続行します。
sample_store
に切り替えて、現在選択されているデータベースにします。
USE sample_store;
選択すると、次の出力が表示されます。
OutputDatabase Changed.
次に、products
テーブルを作成します。
CREATE TABLE products (product_id BIGINT PRIMARY KEY AUTO_INCREMENT, product_name VARCHAR(50), price DOUBLE) ENGINE = InnoDB;
このコマンドは、product_id
という名前のフィールドを持つproducts
テーブルを作成します。 最大2^63-1の大きな値に対応できるBIGINT
データ型を使用します。 PRIMARY KEY
と同じフィールドを使用して、製品を一意に識別します。 AUTO_INCREMENT
キーワードは、新製品が挿入されるときに次の数値を生成するようにMySQLに指示します。
product_name
フィールドのタイプはVARCHAR
で、最大50
の文字または数字を保持できます。 製品price
の場合、DOUBLE
データ型を使用して、10進数の価格の浮動小数点形式に対応します。
最後に、 InnoDBをENGINE
として使用します。これは、MyISAM
などの他のストレージエンジンとは対照的に、MySQLトランザクションを快適にサポートするためです。
products
テーブルを作成すると、次の出力が得られます。
OutputQuery OK, 0 rows affected (0.02 sec)
次に、次のコマンドを実行して、products
テーブルにいくつかのアイテムを追加します。
INSERT INTO products(product_name, price) VALUES ('WINTER COAT','25.50'); INSERT INTO products(product_name, price) VALUES ('EMBROIDERED SHIRT','13.90'); INSERT INTO products(product_name, price) VALUES ('FASHION SHOES','45.30'); INSERT INTO products(product_name, price) VALUES ('PROXIMA TROUSER','39.95');
INSERT
の各操作の後に、次のような出力が表示されます。
OutputQuery OK, 1 row affected (0.02 sec) . . .
次に、データが製品テーブルに追加されたことを確認します。
SELECT * FROM products;
挿入した4つの製品のリストが表示されます。
Output+------------+-------------------+-------+ | product_id | product_name | price | +------------+-------------------+-------+ | 1 | WINTER COAT | 25.5 | | 2 | EMBROIDERED SHIRT | 13.9 | | 3 | FASHION SHOES | 45.3 | | 4 | PROXIMA TROUSER | 39.95 | +------------+-------------------+-------+ 4 rows in set (0.01 sec)
次に、顧客に関する基本情報を保持するためのcustomers
テーブルを作成します。
CREATE TABLE customers (customer_id BIGINT PRIMARY KEY AUTO_INCREMENT, customer_name VARCHAR(50) ) ENGINE = InnoDB;
products
テーブルと同様に、customer_id
にはBIGINT
データ型を使用します。これにより、テーブルが2^63-1レコードまでの多くの顧客をサポートできるようになります。 。 キーワードAUTO_INCREMENT
は、新しい顧客を挿入すると、列の値をインクリメントします。
customer_name
列は英数字値を受け入れるため、[X104X]文字の制限でVARCHARデータ型を使用します。 ここでも、InnoDB
ストレージENGINE
を使用してトランザクションをサポートします。
前のコマンドを実行してcustomers
テーブルを作成すると、次の出力が表示されます。
OutputQuery OK, 0 rows affected (0.02 sec)
3つのサンプル顧客をテーブルに追加します。 次のコマンドを実行します。
INSERT INTO customers(customer_name) VALUES ('JOHN DOE'); INSERT INTO customers(customer_name) VALUES ('ROE MARY'); INSERT INTO customers(customer_name) VALUES ('DOE JANE');
顧客が追加されると、次のような出力が表示されます。
OutputQuery OK, 1 row affected (0.02 sec) . . .
次に、customers
テーブルのデータを確認します。
SELECT * FROM customers;
3人の顧客のリストが表示されます。
Output+-------------+---------------+ | customer_id | customer_name | +-------------+---------------+ | 1 | JOHN DOE | | 2 | ROE MARY | | 3 | DOE JANE | +-------------+---------------+ 3 rows in set (0.00 sec)
次に、さまざまな顧客からの注文を記録するためのorders
テーブルを作成します。 orders
テーブルを作成するには、次のコマンドを実行します。
CREATE TABLE orders (order_id BIGINT AUTO_INCREMENT PRIMARY KEY, order_date DATETIME, customer_id BIGINT, order_total DOUBLE) ENGINE = InnoDB;
order_id
列をPRIMARY KEY
として使用します。 BIGINT
データ型を使用すると、最大2 ^ 63-1の注文に対応でき、注文を挿入するたびに自動インクリメントされます。 order_date
フィールドには、注文が行われた実際の日時が保持されるため、DATETIME
データ型を使用します。 customer_id
は、前に作成したcustomers
テーブルに関連しています。
次の出力が表示されます。
OutputQuery OK, 0 rows affected (0.02 sec)
1人の顧客の注文に複数のアイテムが含まれている可能性があるため、この情報を保持するためにorders_products
テーブルを作成する必要があります。
orders_products
テーブルを作成するには、次のコマンドを実行します。
CREATE TABLE orders_products (ref_id BIGINT PRIMARY KEY AUTO_INCREMENT, order_id BIGINT, product_id BIGINT, price DOUBLE, quantity BIGINT) ENGINE = InnoDB;
ref_id
をPRIMARY KEY
として使用すると、レコードが挿入されるたびに自動インクリメントされます。 order_id
とproduct_id
は、それぞれorders
テーブルとproducts
テーブルに関連しています。 price
列は、浮動値に対応するためにデータ型DOUBLE
です。
ストレージエンジンInnoDB
は、以前に作成された他のテーブルと一致する必要があります。これは、単一の顧客の注文が、トランザクションを使用する複数のテーブルに同時に影響するためです。
出力により、テーブルの作成が確認されます。
OutputQuery OK, 0 rows affected (0.02 sec)
今のところ、orders
テーブルとorders_products
テーブルにデータを追加することはありませんが、後でMySQLトランザクションを実装するPHPスクリプトを使用して追加します。
MySQLサーバーからログアウトします。
QUIT;
これでデータベーススキーマが完成し、いくつかのレコードが入力されました。 次に、データベース接続とMySQLトランザクションを処理するためのPHPクラスを作成します。
ステップ2—MySQLトランザクションを処理するためのPHPクラスの設計
このステップでは、PDO(PHPデータオブジェクト)を使用してMySQLトランザクションを処理するPHPクラスを作成します。 クラスはMySQLデータベースに接続し、データベースにアトミックにデータを挿入します。
クラスファイルをApacheWebサーバーのルートディレクトリに保存します。 これを行うには、テキストエディタを使用してDBTransaction.php
ファイルを作成します。
sudo nano /var/www/html/DBTransaction.php
次に、次のコードをファイルに追加します。 PASSWORD
を、ステップ1で作成した値に置き換えます。
/var/www/html/DBTransaction.php
<?php class DBTransaction { protected $pdo; public $last_insert_id; public function __construct() { define('DB_NAME', 'sample_store'); define('DB_USER', 'sample_user'); define('DB_PASSWORD', 'PASSWORD'); define('DB_HOST', 'localhost'); $this->pdo = new PDO("mysql:host=" . DB_HOST . ";dbname=" . DB_NAME, DB_USER, DB_PASSWORD); $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $this->pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); }
DBTransaction
クラスの開始に向けて、PDOは定数(DB_HOST
、DB_NAME
、DB_USER
、およびDB_PASSWORD
)を使用して手順1で作成したデータベースを初期化して接続します。
注:ここではMySQLトランザクションを小規模に示しているため、DBTransaction
クラスでデータベース変数を宣言しました。 大規模な本番プロジェクトでは、通常、別の構成ファイルを作成し、PHPrequire_onceステートメントを使用してそのファイルからデータベース定数をロードします。
次に、PDOクラスに2つの属性を設定します。
ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION
:この属性は、エラーが発生した場合に例外をスローするようにPDOに指示します。 このようなエラーは、デバッグのためにログに記録できます。ATTR_EMULATE_PREPARES, false
:このオプションは、準備されたステートメントのエミュレーションを無効にし、MySQLデータベースエンジンがステートメント自体を準備できるようにします。
次に、次のコードをファイルに追加して、クラスのメソッドを作成します。
/var/www/html/DBTransaction.php
. . . public function startTransaction() { $this->pdo->beginTransaction(); } public function insertTransaction($sql, $data) { $stmt = $this->pdo->prepare($sql); $stmt->execute($data); $this->last_insert_id = $this->pdo->lastInsertId(); } public function submitTransaction() { try { $this->pdo->commit(); } catch(PDOException $e) { $this->pdo->rollBack(); return false; } return true; } }
CTRL
+ X
、Y
、ENTER
の順に押して、ファイルを保存して閉じます。
MySQLトランザクションを操作するには、DBTransaction
クラスに3つの主要なメソッドを作成します。 startTransaction
、insertTransaction
、およびsubmitTransaction
。
startTransaction
:このメソッドは、トランザクションを開始するようにPDOに指示し、commitコマンドが発行されるまで自動コミットをオフにします。insertTransaction
:このメソッドは2つの引数を取ります。$sql
変数は、実行されるSQLステートメントを保持しますが、$data
変数は、プリペアドステートメントを使用しているため、SQLステートメントにバインドされるデータの配列です。 データは配列としてinsertTransaction
メソッドに渡されます。submitTransaction
:このメソッドは、commit()
コマンドを発行することにより、データベースへの変更を永続的にコミットします。 ただし、エラーが発生してトランザクションに問題がある場合、メソッドはrollBack()
メソッドを呼び出して、PDO例外が発生した場合にデータベースを元の状態に戻します。
DBTransaction
クラスは、トランザクションを初期化し、実行するさまざまなSQLコマンドを準備し、問題がない場合は最後にデータベースへの変更をアトミックにコミットします。それ以外の場合、トランザクションはロールバックされます。 さらに、このクラスでは、パブリックプロパティlast_insert_id
にアクセスして作成したばかりのレコードorder_id
を取得できます。
これで、DBTransaction
クラスを呼び出して、次に作成するPHPコードで使用できるようになりました。
ステップ3—DBTransactionクラスを使用するためのPHPスクリプトの作成
DBTransaction
クラスを実装するPHPスクリプトを作成し、SQLコマンドのグループをMySQLデータベースに送信します。 オンラインショッピングカートでの顧客の注文のワークフローを模倣します。
これらのSQLクエリは、orders
テーブルとorders_products
テーブルに影響します。 DBTransaction
クラスは、すべてのクエリがエラーなしで実行された場合にのみ、データベースへの変更を許可する必要があります。 そうしないと、エラーが返され、変更を試みた場合はロールバックされます。
customer_id1
で識別される顧客JOHN DOE
の単一の注文を作成しています。 顧客の注文には、products
テーブルとは数量が異なる3つの異なるアイテムがあります。 PHPスクリプトは、顧客の注文データを取得してDBTransaction
クラスに送信します。
orders.php
ファイルを作成します。
sudo nano /var/www/html/orders.php
次に、次のコードをファイルに追加します。
/var/www/html/orders.php
<?php require("DBTransaction.php"); $db_host = "database_host"; $db_name = "database_name"; $db_user = "database_user"; $db_password = "PASSWORD"; $customer_id = 2; $products[] = [ 'product_id' => 1, 'price' => 25.50, 'quantity' => 1 ]; $products[] = [ 'product_id' => 2, 'price' => 13.90, 'quantity' => 3 ]; $products[] = [ 'product_id' => 3, 'price' => 45.30, 'quantity' => 2 ]; $transaction = new DBTransaction($db_host, $db_user, $db_password, $db_name);
手順2で作成したDBTransaction
クラスのインスタンスを初期化するPHPスクリプトを作成しました。
このスクリプトでは、DBTransaction.php
ファイルをインクルードし、DBTransaction
クラスを初期化します。 次に、顧客が店舗に注文しているすべての製品の多次元配列を準備します。 また、startTransaction()
メソッドを呼び出してトランザクションを開始します。
次に、次のコードを追加して、orders.php
スクリプトを完成させます。
/var/www/html/orders.php
. . . $order_query = "insert into orders (order_id, customer_id, order_date, order_total) values(:order_id, :customer_id, :order_date, :order_total)"; $product_query = "insert into orders_products (order_id, product_id, price, quantity) values(:order_id, :product_id, :price, :quantity)"; $transaction->insertQuery($order_query, [ 'customer_id' => $customer_id, 'order_date' => "2020-01-11", 'order_total' => 157.8 ]); $order_id = $transaction->last_insert_id; foreach ($products as $product) { $transaction->insertQuery($product_query, [ 'order_id' => $order_id, 'product_id' => $product['product_id'], 'price' => $product['price'], 'quantity' => $product['quantity'] ]); } $result = $transaction->submit(); if ($result) { echo "Records successfully submitted"; } else { echo "There was an error."; }
CTRL
+ X
、Y
、ENTER
の順に押して、ファイルを保存して閉じます。
insertTransaction
メソッドを使用して、注文テーブルに挿入するコマンドを準備します。 この後、DBTransaction
クラスからパブリックプロパティlast_insert_id
の値を取得し、それを$order_id
として使用します。
$order_id
を入手したら、一意のIDを使用して、顧客の注文アイテムをorders_products
テーブルに挿入します。
最後に、メソッドsubmitTransaction
を呼び出して、問題がなければ、顧客の注文の詳細全体をデータベースにコミットします。 それ以外の場合、メソッドsubmitTransaction
は試行された変更をロールバックします。
次に、ブラウザでorders.php
スクリプトを実行します。 次のコマンドを実行し、your-server-IP
をサーバーのパブリックIPアドレスに置き換えます。
http://your-server-IP/orders.php
レコードが正常に送信されたことの確認が表示されます。
PHPスクリプトは期待どおりに機能しており、注文は関連する注文商品とともにデータベースにアトミックに送信されました。
ブラウザウィンドウでorders.php
ファイルを実行しました。 スクリプトはDBTransaction
クラスを呼び出し、orders
の詳細をデータベースに送信しました。 次のステップでは、レコードが関連するデータベーステーブルに保存されているかどうかを確認します。
ステップ4—データベースのエントリを確認する
このステップでは、顧客の注文に対してブラウザウィンドウから開始されたトランザクションが、期待どおりにデータベーステーブルに転記されたかどうかを確認します。
これを行うには、MySQLデータベースに再度ログインします。
sudo mysql -u sample_user -p
sample_user
のパスワードを入力し、ENTER
を押して続行します。
sample_store
データベースに切り替えます。
USE sample_store;
次の出力を確認して、続行する前にデータベースが変更されていることを確認してください。
OutputDatabase Changed.
次に、次のコマンドを発行して、orders
テーブルからレコードを取得します。
SELECT * FROM orders;
これにより、顧客の注文の詳細を示す次の出力が表示されます。
Output+----------+---------------------+-------------+-------------+ | order_id | order_date | customer_id | order_total | +----------+---------------------+-------------+-------------+ | 1 | 2020-01-11 00:00:00 | 2 | 157.8 | +----------+---------------------+-------------+-------------+ 1 row in set (0.00 sec)
次に、orders_products
テーブルからレコードを取得します。
SELECT * FROM orders_products;
顧客の注文からの製品のリストとともに、次のような出力が表示されます。
Output+--------+----------+------------+-------+----------+ | ref_id | order_id | product_id | price | quantity | +--------+----------+------------+-------+----------+ | 1 | 1 | 1 | 25.5 | 1 | | 2 | 1 | 2 | 13.9 | 3 | | 3 | 1 | 3 | 45.3 | 2 | +--------+----------+------------+-------+----------+ 3 rows in set (0.00 sec)
出力は、トランザクションがデータベースに保存され、ヘルパーDBTransaction
クラスが期待どおりに機能していることを確認します。
結論
このガイドでは、PHPPDOを使用してMySQLトランザクションを処理しました。 これはeコマースソフトウェアの設計に関する決定的な記事ではありませんが、アプリケーションでMySQLトランザクションを使用するための例を提供しています。
MySQL ACIDモデルの詳細については、MySQLの公式WebサイトからInnoDBおよびACIDモデルガイドにアクセスすることを検討してください。 私たちをご覧ください MySQLコンテンツページ関連するチュートリアル、記事、およびQ&Aについては。