前提・実現したいこと
PHPでPDOを使ってMySQLにアクセスしています。複数のPDOオブジェクトを作り、それぞれbeginTransaction()
しているのですが、PDO::ATTR_PERSISTENT
をtrue
にしていると、エラーになってしまいます。PDO::ATTR_PERSISTENT
は別のリクエストで使った接続をコネクションプールに返して再利用するのかと思っていたのですが、他のPDOオブジェクトで使用中の接続にも影響が出ているということなのか、といった疑問が生まれよく分かりません。
エラーになる理由を教えて下さい。
発生している問題・エラーメッセージ
There is already an active transaction #0 /var/www/html/hoge.php(14): PDO->beginTransaction() #1 /var/www/html/hoge.php(29): PdoTest->beginTransaction() #2 /var/www/html/hoge.php(45): test() #3 {main}
該当のソースコード
PHP
1<?php 2class PdoTest { 3 public function __construct($persistent = true) { 4 $this->pdo = new PDO( 5 "mysql:host=db;dbname=exampledb", 6 "exampleuser", 7 "examplepass", 8 array( 9 PDO::ATTR_PERSISTENT => $persistent, 10 )); 11 $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 12 } 13 public function beginTransaction() { 14 $this->pdo->beginTransaction(); 15 } 16 public function commit() { 17 $this->pdo->commit(); 18 } 19 function __destruct() { 20 $this->pdo = null; 21 } 22} 23function test($persistent = true) { 24 echo "Persistent: ".($persistent ? "true" : "false")."\n"; 25 try { 26 $test1 = new PdoTest($persistent); 27 $test2 = new PdoTest($persistent); 28 $test1->beginTransaction(); 29 $test2->beginTransaction(); 30 $test1->commit(); 31 $test2->commit(); 32 echo "->Success\n"; 33 } 34 catch(Exception $e) { 35 echo "->Failed\n"; 36 echo $e->getMessage()."\n".$e->getTraceAsString()."\n"; 37 } 38 finally { 39 $test1 = null; 40 $test1 = null; 41 } 42} 43echo "<pre>"; 44test(false); 45test(); 46?>
試したこと
MySQLのprocesslistを見てみました。
bash
1$ docker-compose exec db bash 2root@ecff0829b2de:/# mysql -D exampledb -u exampleuser -p 3Enter password: 4Welcome to the MySQL monitor. Commands end with ; or \g. 5Your MySQL connection id is 21 6Server version: 8.0.25 MySQL Community Server - GPL 7 8Copyright (c) 2000, 2021, Oracle and/or its affiliates. 9 10Oracle is a registered trademark of Oracle Corporation and/or its 11affiliates. Other names may be trademarks of their respective 12owners. 13 14Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. 15 16mysql> show full processlist; 17+----+-------------+------------------+-----------+---------+------+-------+-----------------------+ 18| Id | User | Host | db | Command | Time | State | Info | 19+----+-------------+------------------+-----------+---------+------+-------+-----------------------+ 20| 8 | exampleuser | 172.25.0.3:58734 | exampledb | Sleep | 1553 | | NULL | 21| 9 | exampleuser | 172.25.0.3:58756 | exampledb | Sleep | 3192 | | NULL | 22| 10 | exampleuser | 172.25.0.3:58760 | exampledb | Sleep | 3071 | | NULL | 23| 13 | exampleuser | 172.25.0.3:58842 | exampledb | Sleep | 1864 | | NULL | 24| 16 | exampleuser | 172.25.0.3:58848 | exampledb | Sleep | 1783 | | NULL | 25| 21 | exampleuser | localhost | exampledb | Query | 0 | init | show full processlist | 26+----+-------------+------------------+-----------+---------+------+-------+-----------------------+ 276 rows in set (0.00 sec) 28 29mysql> show full processlist; ←この直前でphpを実行している 30+----+-------------+------------------+-----------+---------+------+-------+-----------------------+ 31| Id | User | Host | db | Command | Time | State | Info | 32+----+-------------+------------------+-----------+---------+------+-------+-----------------------+ 33| 8 | exampleuser | 172.25.0.3:58734 | exampledb | Sleep | 1625 | | NULL | 34| 9 | exampleuser | 172.25.0.3:58756 | exampledb | Sleep | 3264 | | NULL | 35| 10 | exampleuser | 172.25.0.3:58760 | exampledb | Sleep | 3143 | | NULL | 36| 13 | exampleuser | 172.25.0.3:58842 | exampledb | Sleep | 1936 | | NULL | 37| 16 | exampleuser | 172.25.0.3:58848 | exampledb | Sleep | 1855 | | NULL | 38| 21 | exampleuser | localhost | exampledb | Query | 0 | init | show full processlist | 39| 24 | exampleuser | 172.25.0.3:58902 | exampledb | Sleep | 6 | | NULL | 40+----+-------------+------------------+-----------+---------+------+-------+-----------------------+ 417 rows in set (0.00 sec) 42 43mysql>
1つしか接続が出来てないように見えます。ただしPHPにsleepなどを入れて、ATTR_PERSISTENT=falseの接続を見ると新規のポートが開いて、終了と同時に消えているようでした(ログは割愛。ATTR_PERSISTENT=falseでは$test1, $test2には接続が1つずつ出来ており、nullセットと同時に消えていました。)
追記
調べているうちに、以下の記事に当たりました。
MySQL Connection Pooling と Persistent Connections はチョット違うという話 - mita2 database life
私がConnection PoolingとPersistent Connectionsを混同していたのは間違いなく(Connection Poolingのみが正しいと思っていた)、PDOではPersistent Connectionsを実装したから、同じワーカーには同じConnectionが割り当てられたのだろうという仮説に辿り着きました。ちなみにこの記事だけでなく、Connection PoolingとPersistent Connectionsを比較する記事は(主に英語で)沢山見つかり、1対1対応については解釈にブレがあるものの、おおよそ似たような内容の記事でした。
仮説が正しければ理由としては十分なので、当面「同じワーカーには同じConnectionが割り当てられる」仮説を処理系コード上で確認する作業を続けます。
追記2
公式にもそれっぽい内容がPDOではありませんが見つかりました。
永続的な接続は、スクリプトの実行が終了しても閉じないリンクです。持続的接続が要求されると、PHP は同一の持続的接続 (以前から開いていたもの) がすでに存在するかどうかをチェックします。存在する場合は、それを使用します。存在しない場合は、リンクを作成します。「同一の」接続とは、同じユーザー名とパスワード (該当する場合) を使用して、同じホストに対して開かれた接続です。
動作中の接続に対する、具体的な動きが明確ではありませんが、内容的には現象と一致する説明です。
補足情報(FW/ツールのバージョンなど)
以下の環境(docker-compose)で確認しています。
yaml
1version: "3" 2services: 3 web: 4 build: . 5 ports: 6 - 80:80 7 volumes: 8 - ./html:/var/www/html 9 - ./php.ini:/usr/local/etc/php/php.ini 10 11 db: 12 image: mysql 13 environment: 14 MYSQL_DATABASE: exampledb 15 MYSQL_USER: exampleuser 16 MYSQL_PASSWORD: examplepass 17 MYSQL_RANDOM_ROOT_PASSWORD: '1' 18 volumes: 19 - ./db:/var/lib/mysql
Dockerfile
1FROM php:apache 2RUN docker-php-ext-install pdo_mysql 3RUN pecl install xdebug \ 4 && docker-php-ext-enable xdebug
php.ini
ini
1[xdebug] 2xdebug.mode=debug 3xdebug.start_with_request=yes 4; IDEが動いてるところのIP 5xdebug.client_host= 6xdebug.client_port=9003 7xdebug.log=/tmp/xdebug.log 8; ビルド時に出たもの 9zend_extension=/usr/local/lib/php/extensions/no-debug-non-zts-20200930/xdebug.so
vscodeのデバッグ設定
json
1{ 2 "version": "0.2.0", 3 "configurations": [ 4 { 5 "name": "Listen for Xdebug", 6 "type": "php", 7 "request": "launch", 8 "port": 9003, 9 "pathMappings": { 10 "/var/www/html":"${workspaceFolder}/html" 11 }, 12 } 13 ] 14}
回答3件
あなたの回答
tips
プレビュー
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2021/05/31 11:44 編集
退会済みユーザー
2021/05/31 11:46