著者は、 Write for DOnations プログラムの一環として、 Open Sourcing MentalIllnessを選択して寄付を受け取りました。

序章

MySQL トランザクションは、データベース内で単一のユニットとして実行される、論理的に関連するSQLコマンドのグループです。 トランザクションは、アプリケーションで ACID(原子性、一貫性、分離、および耐久性)コンプライアンスを実施するために使用されます。 これは、データベースでの処理操作の信頼性を管理する一連の標準です。

Atomicityは、関連するトランザクションの成功、またはエラーが発生した場合の完全な失敗を保証します。 一貫性は、定義されたビジネスロジックに従ってデータベースに送信されたデータの有効性を保証します。 分離とは、データベースに接続しているさまざまなクライアントの影響が相互に影響しないようにする同時トランザクションの正しい実行です。 耐久性により、論理的に関連するトランザクションがデータベースに永続的に残ります。

トランザクションを介して発行されたSQLステートメントは、成功するか、完全に失敗する必要があります。 クエリのいずれかが失敗した場合、MySQLは変更をロールバックし、データベースにコミットされることはありません。

MySQLトランザクションがどのように機能するかを理解するための良い例は、eコマースWebサイトです。 顧客が注文すると、アプリケーションは次のようないくつかのテーブルにレコードを挿入します。 ordersorders_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サーバーにログインします。

  1. sudo mysql -u root -p

プロンプトが表示されたら、MySQLルートパスワードを入力して、 ENTER 続行します。 次に、データベースを作成します。このチュートリアルでは、データベースを呼び出します。 sample_store:

  1. CREATE DATABASE sample_store;

次の出力が表示されます。

Output
Query OK, 1 row affected (0.00 sec)

と呼ばれるユーザーを作成します sample_user あなたのデータベースのために。 交換することを忘れないでください PASSWORD 強い価値を持つ:

  1. CREATE USER 'sample_user'@'localhost' IDENTIFIED BY 'PASSWORD';

ユーザーに完全な権限を発行します sample_store データベース:

  1. GRANT ALL PRIVILEGES ON sample_store.* TO 'sample_user'@'localhost';

最後に、MySQL特権をリロードします。

  1. FLUSH PRIVILEGES;

ユーザーを作成すると、次の出力が表示されます。

Output
Query OK, 0 rows affected (0.01 sec) . . .

データベースとユーザーを配置すると、MySQLトランザクションがどのように機能するかを示すためのいくつかのテーブルを作成できるようになります。

MySQLサーバーからログアウトします。

  1. QUIT;

システムがログアウトすると、次の出力が表示されます。

Output
Bye.

次に、の資格情報を使用してログインします sample_user 作成したばかり:

  1. sudo mysql -u sample_user -p

のパスワードを入力します sample_user とヒット ENTER 続行します。

に切り替えます sample_store 現在選択されているデータベースにするには:

  1. USE sample_store;

選択すると、次の出力が表示されます。

Output
Database Changed.

次に、 products テーブル:

  1. CREATE TABLE products (product_id BIGINT PRIMARY KEY AUTO_INCREMENT, product_name VARCHAR(50), price DOUBLE) ENGINE = InnoDB;

このコマンドは、 products 名前の付いたフィールドを持つテーブル product_id. あなたは BIGINT 最大2^63-1の大きな値に対応できるデータ型。 これと同じフィールドを使用します PRIMARY KEY 製品を一意に識別するため。 The AUTO_INCREMENT キーワードは、新製品が挿入されたときに次の数値を生成するようにMySQLに指示します。

The product_name フィールドはタイプです VARCHAR それは最大まで保持することができます 50 文字または数字。 製品について price、使用します DOUBLE 10進数の価格で浮動小数点形式に対応するデータ型。

最後に、InnoDBENGINE のような他のストレージエンジンとは対照的に、MySQLトランザクションを快適にサポートするため MyISAM.

作成したら products テーブルでは、次の出力が得られます。

Output
Query OK, 0 rows affected (0.02 sec)

次に、いくつかのアイテムをに追加します products 次のコマンドを実行してテーブルを作成します。

  1. INSERT INTO products(product_name, price) VALUES ('WINTER COAT','25.50');
  2. INSERT INTO products(product_name, price) VALUES ('EMBROIDERED SHIRT','13.90');
  3. INSERT INTO products(product_name, price) VALUES ('FASHION SHOES','45.30');
  4. INSERT INTO products(product_name, price) VALUES ('PROXIMA TROUSER','39.95');

それぞれの後に次のような出力が表示されます INSERT 手術:

Output
Query OK, 1 row affected (0.02 sec) . . .

次に、データが製品テーブルに追加されたことを確認します。

  1. 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 顧客に関する基本情報を保持するためのテーブル:

  1. CREATE TABLE customers (customer_id BIGINT PRIMARY KEY AUTO_INCREMENT, customer_name VARCHAR(50) ) ENGINE = InnoDB;

のように products テーブル、あなたは使用します BIGINT のデータ型 customer_id これにより、テーブルが2^63-1レコードまでの多くの顧客をサポートできるようになります。 キーワード AUTO_INCREMENT 新しい顧客を挿入すると、列の値が増加します。

以来 customer_name 列は英数字値を受け入れます。VARCHARデータ型を使用します。制限は 50 文字。 繰り返しますが、 InnoDB 保管所 ENGINE トランザクションをサポートします。

前のコマンドを実行して作成した後 customers 表を見ると、次の出力が表示されます。

Output
Query OK, 0 rows affected (0.02 sec)

3つのサンプル顧客をテーブルに追加します。 次のコマンドを実行します。

  1. INSERT INTO customers(customer_name) VALUES ('JOHN DOE');
  2. INSERT INTO customers(customer_name) VALUES ('ROE MARY');
  3. INSERT INTO customers(customer_name) VALUES ('DOE JANE');

顧客が追加されると、次のような出力が表示されます。

Output
Query OK, 1 row affected (0.02 sec) . . .

次に、のデータを確認します customers テーブル:

  1. 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 テーブルで、次のコマンドを実行します。

  1. 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. The BIGINT データ型を使用すると、最大2 ^ 63-1の注文に対応でき、注文を挿入するたびに自動インクリメントされます。 The order_date フィールドには、注文が行われた実際の日時が保持されるため、 DATETIME データ・タイプ。 The customer_id に関連する customers 以前に作成したテーブル。

次の出力が表示されます。

Output
Query OK, 0 rows affected (0.02 sec)

1人の顧客の注文に複数のアイテムが含まれている可能性があるため、 orders_products この情報を保持するテーブル。

を作成するには orders_products テーブルで、次のコマンドを実行します。

  1. 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 これにより、レコードが挿入されるたびに自動インクリメントされます。 The order_idproduct_id に関連する orders そしてその products それぞれテーブル。 The price 列はデータ型です DOUBLE 浮動値に対応するため。

ストレージエンジン InnoDB 1人の顧客の注文は、トランザクションを使用する複数のテーブルに同時に影響するため、以前に作成した他のテーブルと一致する必要があります。

出力により、テーブルの作成が確認されます。

Output
Query OK, 0 rows affected (0.02 sec)

にデータを追加することはありません ordersorders_products 今のところテーブルですが、後でMySQLトランザクションを実装するPHPスクリプトを使用してこれを行います。

MySQLサーバーからログアウトします。

  1. QUIT;

これでデータベーススキーマが完成し、いくつかのレコードが入力されました。 次に、データベース接続とMySQLトランザクションを処理するためのPHPクラスを作成します。

ステップ2—MySQLトランザクションを処理するためのPHPクラスの設計

このステップでは、PDO(PHPデータオブジェクト)を使用してMySQLトランザクションを処理するPHPクラスを作成します。 クラスはMySQLデータベースに接続し、データベースにアトミックにデータを挿入します。

クラスファイルをApacheWebサーバーのルートディレクトリに保存します。 これを行うには、を作成します DBTransaction.php テキストエディタを使用したファイル:

  1. 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トランザクションを操作するには、次の3つの主要なメソッドを作成します。 DBTransaction クラス; startTransaction, insertTransaction、 と submitTransaction.

  • startTransaction:このメソッドは、トランザクションを開始するようにPDOに指示し、commitコマンドが発行されるまで自動コミットをオフにします。

  • insertTransaction :このメソッドは2つの引数を取ります。 The $sql 変数は、実行されるSQLステートメントを保持します。 $data 変数は、プリペアドステートメントを使用しているため、SQLステートメントにバインドされるデータの配列です。 データは配列として insertTransaction 方法。

  • submitTransaction :このメソッドは、を発行することにより、データベースへの変更を永続的にコミットします。 commit() 指図。 ただし、エラーが発生し、トランザクションに問題がある場合、メソッドは rollBack() PDO例外が発生した場合に、データベースを元の状態に戻すメソッド。

君の DBTransaction classはトランザクションを初期化し、実行するさまざまなSQLコマンドを準備し、問題がない場合は最後にデータベースへの変更をアトミックにコミットします。それ以外の場合、トランザクションはロールバックされます。 さらに、このクラスではレコードを取得できます order_id 公共施設にアクセスして作成したばかり last_insert_id.

The DBTransaction これで、次に作成するPHPコードでクラスを呼び出して使用する準備が整いました。

ステップ3—DBTransactionクラスを使用するためのPHPスクリプトの作成

を実装するPHPスクリプトを作成します DBTransaction クラスを作成し、SQLコマンドのグループをMySQLデータベースに送信します。 オンラインショッピングカートでの顧客の注文のワークフローを模倣します。

これらのSQLクエリは orders そしてその orders_products テーブル。 君の DBTransaction クラスは、すべてのクエリがエラーなしで実行された場合にのみ、データベースへの変更を許可する必要があります。 そうしないと、エラーが返され、変更を試みた場合はロールバックされます。

顧客に対して単一の注文を作成しています JOHN DOE customer_idで識別されます 1. 顧客の注文には、数量が異なる3つの異なるアイテムがあります。 products テーブル。 PHPスクリプトは、顧客の注文データを取得して、 DBTransaction クラス。

を作成します orders.php ファイル:

  1. 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);

インスタンスを初期化するPHPスクリプトを作成しました DBTransaction 手順2で作成したクラス。

このスクリプトには、 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 方法。 この後、パブリックプロパティの値を取得します last_insert_id から DBTransaction クラスとして使用します $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データベースに再度ログインします。

  1. sudo mysql -u sample_user -p

のパスワードを入力します sample_user とヒット ENTER 続ける。

に切り替えます sample_store データベース:

  1. USE sample_store;

次の出力を確認して、続行する前にデータベースが変更されていることを確認してください。

Output
Database Changed.

次に、次のコマンドを発行して、からレコードを取得します。 orders テーブル:

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

  1. 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については。