mysqli_query()默认不支持多条SQL语句,必须使用mysqli_multi_query();后者按语法层级正确解析分号边界,配合store_result()、next_result()和more_results()循环处理结果集,而手动explode()分割极不安全。

PHP里不能直接用mysqli_query()执行多条SQL语句
默认情况下,mysqli_query() 一次只接受一条语句。如果你传入类似 "INSERT INTO t1 VALUES(1); INSERT INTO t2 VALUES(2);" 这样的字符串,它会报错:mysqli_query(): You have an error in your SQL syntax —— 因为分号不是合法的语句结尾标识,而是被当作非法字符处理了。
根本原因在于:MySQLi 的普通查询接口不启用多语句支持,这是安全机制,默认关闭。
- 必须显式启用多语句模式,靠
mysqli_real_connect()的MYSQLI_CLIENT_MULTI_STATEMENTS标志,或使用mysqli::real_connect()构造时传入 - 更常用也更安全的做法是改用
mysqli_multi_query(),它专为此设计,且不要求连接时特殊配置 -
PDO默认也不支持,需开启PDO::ATTR_EMULATE_PREPARES = false并用PDO::exec()或循环执行(但注意:PDO::exec() 仍只执行单条)
用mysqli_multi_query()安全执行多条SQL
这是最直接、无需改连接配置的方式。它把整个字符串按分号切分(注意:仅按顶层分号,不解析引号/注释内的分号),逐条发送给 MySQL 执行。
关键点:
立即学习“PHP免费学习笔记(深入)”;
- 每条语句必须以分号结尾,否则后续语句可能被吞掉或报错
- 执行后要用
mysqli_store_result()或mysqli_use_result()清空当前结果集,再调用mysqli_next_result()切换到下一条语句的结果 - 忽略中间结果会导致后续
mysqli_next_result()失败,常见错误是Commands out of sync - 没有返回布尔值,得靠
mysqli_more_results()和循环判断是否还有语句待处理
mysqli_multi_query($conn, "INSERT INTO users(name) VALUES('a'); UPDATE logs SET cnt=cnt+1 WHERE id=1;");
do {
if ($result = mysqli_store_result($conn)) {
mysqli_free_result($result);
}
} while (mysqli_next_result($conn));为什么不用explode(';', $sql)手动拆分再循环执行
看起来简单,但极容易出错——MySQL 的 SQL 语句里允许出现分号,比如在字符串字面量、注释、存储过程定义中:
-
INSERT INTO t(v) VALUES('hello; world');—— 这里的分号不该拆 -
/* comment; here */ SELECT 1;—— 注释里的分号也不该算 -
DELIMITER $$ CREATE PROCEDURE p() BEGIN SELECT 1; END$$—— 存储过程更复杂
mysqli_multi_query() 内部由 MySQL 客户端库解析语法层级,能正确识别语句边界;自己用 explode() 是纯文本切割,完全不可靠。生产环境严禁这么做。
事务包裹多条语句比多语句更可控
多数时候你真正需要的不是“一次发多条”,而是“全部成功或全部失败”。这时候应放弃多语句,改用事务 + 单条执行:
- 用
mysqli_begin_transaction()或START TRANSACTION - 逐条调用
mysqli_query(),检查每条是否成功 - 任一失败就
mysqli_rollback(),全成功才mysqli_commit() - 好处是可精细控制错误位置、记录具体哪条出错、兼容所有驱动(包括 PDO)、支持预处理防注入
多语句执行本质是“批量提交”,但缺乏错误定位能力——只要其中一条失败,整批就中断,你还得自己解析错误信息才能知道是第几条挂了。事务方式虽然代码略多,但健壮得多。
真正需要多语句的场景其实很少,比如导入 SQL 转储文件、运行 DBA 提供的初始化脚本等。日常业务逻辑中,优先走事务 + 单语句路径。



















