
本文介绍一种健壮的正则方案,利用 (*SKIP)(*F) 跳过字符串字面量和注释中的分号,精准按语义分号分割多条 SQL 语句,尤其适用于含 COMMENT '...;...' 等嵌套分号的复杂场景。
本文介绍一种健壮的正则方案,利用 `(*skip)(*f)` 跳过字符串字面量和注释中的分号,精准按语义分号分割多条 sql 语句,尤其适用于含 `comment '...;...'` 等嵌套分号的复杂场景。
在处理数据库迁移脚本或 SQL 批量执行时,常需将包含多条语句的 SQL 字符串(如 .sql 文件内容)按语句拆分为数组。但直接用 explode(';', $sql) 会失败——因为 SQL 注释(如 COMMENT '0=none; 1=test;')或字符串字面量中本身含有分号,导致错误切分。
PHP 的 preg_split() 结合 PCRE 的控制动词 (*SKIP)(*F) 是解决该问题的推荐方式:它能「跳过」括号、单引号、双引号等界定符内的所有内容,仅在外部真正的语句结束处分割。
以下为经过验证的完整解决方案:
$multiSql = "
ALTER TABLE `my_table` CHANGE `typ` `typ` INT(10) UNSIGNED NOT NULL DEFAULT '0' COMMENT '0=none; 1=test1; 2=test2; 3=test3';
ALTER TABLE `my_table2`
ADD `date` varchar(25) COLLATE utf8_czech_ci DEFAULT NULL AFTER `test`;
ALTER TABLE `my_table3` ADD `date` varchar(25) COLLATE utf8_czech_ci DEFAULT NULL AFTER `test`; ALTER TABLE `my_table3` ADD `test2` varchar(25) COLLATE utf8_czech_ci DEFAULT NULL AFTER `date`;
ALTER TABLE `my_table3` CHANGE `test2` `test2` INT(10) UNSIGNED NOT NULL DEFAULT '0' COMMENT '0=test; 1=test2;';
";
// 核心正则:跳过括号内内容,匹配语句末尾的分号(后接空白行或下一个语句关键字)
$pattern = '~\( [^)]* \)(*SKIP)(*F) | \' [^\']* \'(*SKIP)(*F) | " [^"]* "(*SKIP)(*F) | (?<=;)(?=\h*$|\s*\b(ALTER|CREATE|INSERT|UPDATE|DELETE|DROP|REPLACE|SELECT)\b)~x';
$sqlArray = preg_split($pattern, trim($multiSql), -1, PREG_SPLIT_NO_EMPTY);
// 清理每条语句:合并换行、去除首尾空白
$sqlArray = array_map(function($stmt) {
return trim(preg_replace('/\s+/', ' ', $stmt));
}, $sqlArray);
print_r($sqlArray);✅ 输出效果(共 5 条有效语句):
立即学习“PHP免费学习笔记(深入)”;
Array
(
[0] => ALTER TABLE `my_table` CHANGE `typ` `typ` INT(10) UNSIGNED NOT NULL DEFAULT '0' COMMENT '0=none; 1=test1; 2=test2; 3=test3';
[1] => ALTER TABLE `my_table2` ADD `date` varchar(25) COLLATE utf8_czech_ci DEFAULT NULL AFTER `test`;
[2] => ALTER TABLE `my_table3` ADD `date` varchar(25) COLLATE utf8_czech_ci DEFAULT NULL AFTER `test`;
[3] => ALTER TABLE `my_table3` ADD `test2` varchar(25) COLLATE utf8_czech_ci DEFAULT NULL AFTER `date`;
[4] => ALTER TABLE `my_table3` CHANGE `test2` `test2` INT(10) UNSIGNED NOT NULL DEFAULT '0' COMMENT '0=test; 1=test2;';
)? 关键说明:
- 正则中
(*SKIP)(*F)组合确保匹配到括号(...)、单引号'...'或双引号"..."内容时立即回溯并跳过,避免误切内部的分号; (? 是肯定性后行断言,要求分割点前必须是分号;-
(?=\h*$|\s*\b(ALTER|...)\b)是前瞻性检查:分号后必须是「纯空白至行尾」或「空白 + 下一条 SQL 关键字」,从而确保只在语句边界分割; -
x修饰符启用正则注释与空白忽略,提升可读性(实际使用时可移除换行与空格以适配单行模式); -
array_map()后处理统一规范化语句格式(压缩多余空格、清理换行),提升可执行性与可读性。
⚠️ 注意事项:
- 本方案默认支持常见 DDL/DML 关键字(
ALTER,CREATE,INSERT等),如需支持WITH,BEGIN,DO等,需扩展关键词列表; - 不处理三重引号(如 MySQL 的反引号嵌套)或转义字符(如
'O\'Reilly'),生产环境建议优先使用专业 SQL 解析器(如php-sql-parser); - 若 SQL 中存在无关键字开头的语句(如纯
SELECT ...;后紧跟;),请确保前瞻条件覆盖完整。
掌握此正则逻辑,即可安全、准确地解析绝大多数手工编写的多语句 SQL 脚本,为自动化数据库部署打下坚实基础。



















