PDOStatement::closeCursor()を使用しないとエラーになる例

このページは、PHPの PDOStatement::closeCursor() を使用しないとエラー(例外)になる例を検証してまとめたページです。

注意

  • 下記のバージョンで検証したものです。別のバージョンや組み合わせでは違う動作になるかもしれません。
    • PHP 8.1, MariaDB 10.4
    • PHP 7.1, MariaDB 10.4
  • 各コードは下記のコードが前にあるものとしています。
$dsn = 'mysql:host=localhost;dbname=test'; // DSN
$user = 'user'; // ユーザー
$pass = 'pass'; // パスワード
$pdo = new PDO($dsn, $user, $pass, [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
]);

// テーブルとストアドプロシージャの定義
$pdo->query('DROP TABLE IF EXISTS emp');
$pdo->query('CREATE TABLE emp (id SERIAL, name VARCHAR(100))');
$pdo->query("INSERT INTO emp VALUES(1, 'emp1'), (2, 'emp2'), (3, 'emp3')");
$pdo->query('DROP PROCEDURE IF EXISTS P1');
$pdo->query('CREATE PROCEDURE P1() SELECT * FROM emp');
  • 例示のコードを単純にするため、prepare(), bindValue(), execute(), fetch() などの処理は除いています。

PDOStatement::closeCursor()を使用しないとエラーになる例

  • 下記のようなSQLを実行し、結果セットが最後まで処理されていないときに別のSQLを実行する
    • 非バッファモードでのSQLの実行 (非バッファモードの場合は最後の結果セットの最後の行まで処理されていないとエラー)
    • 複文の実行
    • ストアドプロシージャの実行

非バッファモードでのSQLの実行

非バッファモードの例
// 非バッファモードに変更 (コンストラクタで指定してもいい)
$pdo->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);
// SQL実行 (prepare()とexecute()でも同じ)
$stmt1 = $pdo->query('SELECT * FROM emp WHERE id=1');

//// ここでcloseCurosrするとエラーにならない
// $stmt1->closeCursor();

// closeCursor()を使用せずに別のSQLを実行すると下記の例外になる
// Uncaught PDOException: SQLSTATE[HY000]: General error: 2014 Cannot execute queries while other unbuffered queries are active.  Consider using PDOStatement::fetchAll().  Alternatively, if your code is only ever going to run against mysql, you may enable query buffering by setting the PDO::MYSQL_ATTR_USE_BUFFERED_QUERY attribute
$stmt2 = $pdo->query('SELECT * FROM emp WHERE id=2');

複文の実行

複文の例
// 複文を使用 (prepare()とexecute()でも同じ)
$stmt1 = $pdo->query('SELECT * FROM emp WHERE id=1; SELECT * FROM emp WHERE id=2');

//// ここでcloseCurosrするとエラーにならない
// $stmt1->closeCursor();

// closeCursor()を使用せずに別のSQLを実行すると下記の例外になる
// Uncaught PDOException: SQLSTATE[HY000]: General error: 2014 Cannot execute queries while there are pending result sets. Consider unsetting the previous PDOStatement or calling PDOStatement::closeCursor()
$stmt2 = $pdo->query('SELECT * FROM emp WHERE id=3');

ストアドプロシージャの実行

ストアドプロシージャの例
// ストアドプロシージャを使用 (prepare()とexecute()でも同じ)
$stmt1 = $pdo->query('CALL P1()');

//// ここでcloseCurosrするとエラーにならない
// $stmt1->closeCursor();

// closeCursor()を使用せずに別のSQLを実行すると下記の例外になる
// Uncaught PDOException: SQLSTATE[HY000]: General error: 2014 Cannot execute queries while there are pending result sets. Consider unsetting the previous PDOStatement or calling PDOStatement::closeCursor()
$stmt2 = $pdo->query('SELECT * FROM emp WHERE id=1');

closeCursor()を使用しなくてもエラーにならない場合の例

PDOStatementをunset()している

非バッファモードの例
// SQL実行
$stmt1 = $pdo->query('SELECT * FROM emp WHERE id=1');

// unset
unset($stmt1);

// 別のSQLを実行
$stmt2 = $pdo->query('SELECT * FROM emp WHERE id=2');
複文の例
// 複文を使用
$stmt1 = $pdo->query('SELECT * FROM emp WHERE id=1; SELECT * FROM emp WHERE id=2');

// unset
unset($stmt1);

// 別のSQLを実行
$stmt2 = $pdo->query('SELECT * FROM emp WHERE id=3');
ストアドプロシージャの例
// ストアドプロシージャを使用
$stmt1 = $pdo->query('CALL P1()');

// unset
unset($stmt1);

// 別のSQLを実行
$stmt2 = $pdo->query('SELECT * FROM emp WHERE id=1');
pdo_mysql_stmt_cursor_closer (PHP 8.1.14)
static int pdo_mysql_stmt_cursor_closer(pdo_stmt_t *stmt) /* {{{ */
{
	pdo_mysql_stmt *S = (pdo_mysql_stmt*)stmt->driver_data;

	PDO_DBG_ENTER("pdo_mysql_stmt_cursor_closer");
	PDO_DBG_INF_FMT("stmt=%p", S->stmt);

	S->done = 1;
	pdo_mysql_free_result(S);
	if (S->stmt) {
		mysql_stmt_free_result(S->stmt);
	}

	while (mysql_more_results(S->H->server)) {
		MYSQL_RES *res;
		if (mysql_next_result(S->H->server) != 0) {
			pdo_mysql_error_stmt(stmt);
			PDO_DBG_RETURN(0);
		}
		res = mysql_store_result(S->H->server);
		if (res) {
			mysql_free_result(res);
		}
	}
	PDO_DBG_RETURN(1);
}
pdo_mysql_stmt_dtor (PHP 8.1.14)
static int pdo_mysql_stmt_dtor(pdo_stmt_t *stmt) /* {{{ */
{
	pdo_mysql_stmt *S = (pdo_mysql_stmt*)stmt->driver_data;

	PDO_DBG_ENTER("pdo_mysql_stmt_dtor");
	PDO_DBG_INF_FMT("stmt=%p", S->stmt);

	pdo_mysql_free_result(S);
	if (S->einfo.errmsg) {
		pefree(S->einfo.errmsg, stmt->dbh->is_persistent);
		S->einfo.errmsg = NULL;
	}
	if (S->stmt) {
		mysql_stmt_close(S->stmt);
		S->stmt = NULL;
	}

#ifndef PDO_USE_MYSQLND
	if (S->params) {
		efree(S->params);
	}
	if (S->in_null) {
		efree(S->in_null);
	}
	if (S->in_length) {
		efree(S->in_length);
	}
#endif

	if (!S->done && !Z_ISUNDEF(stmt->database_object_handle)
		&& IS_OBJ_VALID(EG(objects_store).object_buckets[Z_OBJ_HANDLE(stmt->database_object_handle)])
		&& (!(OBJ_FLAGS(Z_OBJ(stmt->database_object_handle)) & IS_OBJ_FREE_CALLED))) {
		while (mysql_more_results(S->H->server)) {
			MYSQL_RES *res;
			if (mysql_next_result(S->H->server) != 0) {
				break;
			}

			res = mysql_store_result(S->H->server);
			if (res) {
				mysql_free_result(res);
			}
		}
	}

	efree(S);
	PDO_DBG_RETURN(1);
}

PDOStatementが最後の結果セットまで処理されている

非バッファモードの例
// SQL実行
$stmt1 = $pdo->query('SELECT * FROM emp WHERE id=1');

// 結果セットを進める (fetchAll()などでも可。非バッファモードでは最後の結果セットの最後の行まで処理する必要がある)
$stmt1->nextRowset();

// 別のSQLを実行
$stmt2 = $pdo->query('SELECT * FROM emp WHERE id=2');
複文の例
// 複文を使用
$stmt1 = $pdo->query('SELECT * FROM emp WHERE id=1; SELECT * FROM emp WHERE id=2');

// 結果セットを進める (上記のSQLが2つ分のため、これで最後になる。非バッファモードかつ複文の場合はもう1回必要)
$stmt1->nextRowset();

// 別のSQLを実行
$stmt2 = $pdo->query('SELECT * FROM emp WHERE id=3');
ストアドプロシージャの例
// ストアドプロシージャを使用
$stmt1 = $pdo->query('CALL P1()');

// 結果セットを進める (プロシージャが返す結果セットが1つでも結果セットを進める必要がある? / fetchAll()は×)
$stmt1->nextRowset();

// 別のSQLを実行
$stmt2 = $pdo->query('SELECT * FROM emp WHERE id=1');
単文の例
// 単文を使用 (単文は結果セットが1つのため何もしなくていい)
$stmt1 = $pdo->query('SELECT * FROM emp WHERE id=1');

// 別のSQLを実行
$stmt2 = $pdo->query('SELECT * FROM emp WHERE id=3');