PHP8で「PDOException: There is no active transaction」エラー

PHP8を使用している際に「PDOException: There is no active transaction」というエラーに遭遇した場合の対処法について解説します。

環境

  • PHP 8.0
  • MySQL 8.0

問題

「PDOException: There is no active transaction」というエラーが発生しました。

調査を進めていくと、トランザクションの中でテーブルをtruncateする際にこのエラーが発生していることがわかりました。

以下に、問題を再現するための最小限のコードを示します。

public function testTruncate(): void
{
    DB::transaction(function () {
        MyModel::query()->truncate();
    });
}

原因

このエラーの原因は、PHP 8.0でのPDO MySQLの扱いの変更にあります。
PHP 8.0では、トランザクションの状態がより厳格に管理されています。
これはPHPの公式マニュアルで確認できます。

また、MySQLのTRUNCATE TABLEステートメントは実行されると暗黙のコミットが発生します。
この点についてはMySQL 8.0 リファレンスマニュアルに詳細が記載されています。

truncateを実行すると、暗黙のコミットによりトランザクションが終了してしまいます。
その結果、トランザクションが存在しなくなり、「PDOException: There is no active transaction」というエラーが発生するのです。

LaravelでCSVファイルをアップロードする処理をテストする

概要

Laravelを使用してCSVファイルをアップロードする処理のテスト方法を説明します。

UploadedFile::fake()->createWithContent()を使うと、簡単にテスト用のCSVファイルを作成できます。

解説

createWithContent() メソッドを用いて、テスト用のCSVファイルを作成します。
このメソッドの第一引数にはファイル名を、第二引数にはファイルの内容を指定します。

以下の例では、id, name, created_at, updated_at の4つのカラムを持つCSVファイルを作成しています。

$content = <<<EOF
id,name,created_at,updated_at
1,北海道,2020-01-01 00:00:00,2020-01-01 00:00:00
2,青森県,2020-01-01 00:00:00,2020-01-01 00:00:00
EOF;
$file = UploadedFile::fake()->createWithContent('test.csv', $content);

次に、作成したCSVファイルをアップロードし、そのプロセスをテストします。

以下の例では、POSTリクエストを/uploadエンドポイントに送信し、レスポンスのステータスコードが200であることを確認しています。

$response = $this->json('POST', '/upload', [ 
    'csv_file' => $file,
]);
$response->assertStatus(200);

この方法を利用することで、LaravelアプリケーションにおけるCSVファイルのアップロード処理のテストが容易になります。

環境

  • PHP 8.0.6
  • Laravel バージョン6.20.27

mysqldumpを使用してAmazon RDS for MySQLにデータをインポートする

問題

mysqldumpを使用してデータをダンプし、Amazon RDS for MySQLにインポートしようとした場合に、以下のようなエラーメッセージが表示されることがあります。

ERROR 1227 (42000) at line 18: Access denied;
you need (at least one of) the SUPER, SYSTEM_VARIABLES_ADMIN or SESSION_VARIABLES_ADMIN privilege(s) for this operation

このエラーは、Amazon RDSのセキュリティ制限により、いくつかのシステム変数の変更が許可されていないために発生します。

解決策

以下の手順でデータをインポートできました。

  1. ダンプファイルを開き、以下の行を削除します。
    SET @@SESSION.SQL_LOG_BIN= 0;
    SET @@GLOBAL.GTID_PURGED=/*!80000 '+'*/ '';
    SET @@SESSION.SQL_LOG_BIN = @MYSQLDUMP_TEMP_LOG_BIN;
    
  2. 変更を保存し、再度インポートを試みます。

この手順でインポートに成功しました。

MySQLで長時間実行中のクエリを特定し、強制終了する方法

処理に時間がかかっているクエリを調べる

MySQLでは、SHOW PROCESSLISTステートメントを使って現在実行中のスレッドを確認できます。
このコマンドは、各スレッドのID、ユーザー名、実行中のクエリなど、重要な情報を提供します。
Infoフィールドでは、クエリの最初の100文字が表示されます。
すべてのクエリを表示するには、SHOW FULL PROCESSLISTを使用します。

mysql> show processlist;
+---------+-----------------+------------+-------+---------+---------+------------------------+----------
| Id      | User            | Host       | db    | Command | Time    | State                  | Info     
+---------+-----------------+------------+-------+---------+---------+------------------------+----------
| 5483780 | admin           | localhost  | my_db | Execute |     490 | executing              | select ...

この例では、Idが5483780のスレッドが490秒間実行中であることがわかります。
これは、パフォーマンスの問題を引き起こしている可能性があります。

処理に時間がかかっているクエリを強制終了する

長時間実行中のクエリを強制終了するには、KILL processlist_id ステートメントを使用します。
このコマンドは、指定したプロセスIDのスレッドを即座に終了させます。

Idが5483780のスレッドを終了する場合は、以下のようにします。

mysql> kill 5483780;

参考