MySQL 的 CREATE DATABASE 不支持事务,执行后立即生效且无法回滚;替代方案是先检查库是否存在,再建库并捕获异常,后续建表、插入等操作才可放入事务。
MySQL 的 CREATE DATABASE 是 DDL 语句,不支持事务控制。无论用 mysqli、PDO 还是原生 SQL,在事务中执行建库操作,ROLLBACK 都不会撤销它——建库会立即生效,且无法回滚。
常见错误现象:
– 在 beginTransaction() 后调用 exec("CREATE DATABASE test_db"),再 rollback(),发现库依然存在
– 误以为事务能包裹“建库 + 建表 + 插数据”全流程,结果建库成功但后续失败,留下空库
CRE
ATE DATABASE、DROP TABLE)在 MySQL 中会隐式提交当前事务PDO::ATTR_AUTOCOMMIT => false,也拦不住 DDL 的自动提交行为既然不能靠事务兜底,就得靠逻辑控制和防御性操作。核心思路是:先查库是否存在,不存在再建;建库失败就抛异常,避免继续执行下游逻辑。
$pdo = new PDO('mysql:host=localhost', $user, $pass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
]);
try {
// 检查库是否存在
$stmt = $pdo->query("SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = 'myapp_db'");
if ($stmt->fetch() === false) {
// 不存在才建库(注意:这里不进事务)
$pdo->exec("CREATE DATABASE myapp_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci");
}
// 切换到新库(必须显式切换)
$pdo->exec("USE myapp_db");
// ✅ 此处开始的建表、插数据等 DML/DDL 才可进事务
$pdo->beginTransaction();
$pdo->exec("CREATE TABLE users (id INT PRIMARY KEY, name VARCHAR(50))");
$pdo->exec("INSERT INTO users VALUES (1, 'Alice')");
$pdo->commit();
} catch (PDOException $e) {
// 建库失败或建表失败都会进这里,但建库本身无法回滚
error_log("DB setup failed: " . $e->getMessage());
// 可选:手动清理(如 DROP DATABASE myapp_db),但需确认权限和安全性
}
PHP 的 PDO 本身不支持真正的嵌套事务(savepoint 是模拟,非标准嵌套)。所谓“嵌套”,其实是用 SAVEPOINT 实现局部回滚,但前提是所有操作都在同一数据库连接、且底层支持(MySQL 支持 SAVEPOINT)。
beginTransaction() 只能调用一次,重复调用会报错 SQLSTATE[HY000]: General error: 2014 Cannot execute queries while other unbuffered queries are active
SAVEPOINT 手动管理,例如:$pdo->exec("SAVEPOINT sp1");,失败时 $pdo->exec("ROLLBACK TO sp1");,但这对 CREATE DATABASE 无效建库不行,但这些操作可以且应该放进事务:
CREATE TABLE / DROP TABLE(同库内 DDL,MySQL 5.6+ 支持事务内执行,但仍有风险,建议只用于初始化阶段)INSERT / UPDATE / DELETE 多表关联写入(如订单+订单项+库存扣减)PDO::prepare() + execute() 绑定参数的写操作,比裸 exec() 更安全复杂点在于:建库是部署期操作,事务是运行期保障。混在一起容易高估事务能力,低估 DDL 的破坏性。实际项目中,建库通常由 DBA 或部署脚本完成,PHP 应专注连接已有库后的业务事务。