不能用 readline() 或 split(';') 直接切分,因为 sql 备份中存在多行 insert、嵌套引号、行内注释 --、块注释 /.../ 及字段值含分号(如 json),盲目切分会导致语句截断或误判结束符。

直接读取整个大型 SQL 备份文件到内存会触发 MemoryError,必须用流式方式逐段解析。关键不是“能不能读”,而是“怎么安全切分 SQL 语句”——因为 split(';') 在注释、字符串、换行符存在时必然出错。
为什么不能用 readline() 或 split(';') 直接切分?
SQL 备份中常见 INSERT INTO ... VALUES (...) 含多行、嵌套单引号、行内注释 --、块注释 /* ... */,甚至字段值本身含分号(如 JSON 字符串)。盲目按分号切分会把一条完整语句截断,或把注释误判为语句结束。
实操建议:
- 必须跟踪当前是否处于字符串字面量(单引号/双引号)或注释块内,仅在“语句上下文”中识别分号
- 跳过所有
--行尾注释和/*...*/块注释中的分号 - 忽略字符串内的分号:例如
'UPDATE;user'中的分号不构成语句边界 - MySQL 的
DELIMITER指令会临时修改语句结束符,需动态识别(如DELIMITER $$后改用$$结束)
推荐用 sqlparse 库做流式语句提取
sqlparse 是少数能正确处理嵌套结构和注释的 Python SQL 解析器,且支持增量解析(无需加载全文)。它不执行 SQL,只做语法切分,开销可控。
实操建议:
- 安装:
pip install sqlparse - 用
sqlparse.parsestream()接收文件对象,边读边解析,避免全量加载 - 对每个解析出的
sqlparse.sql.Statement对象调用.value.strip()获取原始语句文本 - 过滤空语句和纯注释语句:
if stmt.token_first(skip_ws=True, skip_cm=True) is not None - 注意:默认不识别
DELIMITER,需手动预处理或配合正则提前提取 delimiter 变更
示例片段:
import sqlparse
<p>with open('backup.sql', 'r', encoding='utf-8') as f:
for stmt in sqlparse.parsestream(f, encoding='utf-8'):
sql = stmt.value.strip()
if sql and not sql.startswith('--') and not sql.startswith('/*'):</p><h1>处理单条 SQL,如写入队列、转发到目标库等</h1><pre class="brush:python;toolbar:false;"> process_sql(sql)
手动实现轻量级状态机(无第三方依赖时)
若环境受限无法装 sqlparse,可用 150 行内状态机处理常见备份格式(适用于 MySQL mysqldump 输出)。核心是维护三个状态:normal、in_single_quote、in_double_quote,并检测转义符 \' 和 \"。
实操建议:
- 逐字符读取(
f.read(1))或逐行读取后缓冲未闭合引号的行 - 遇到
--或/*进入注释态,跳过其间所有字符直到行尾或*/ - 单引号内遇
\'不切换状态;双引号同理 - 仅当处于
normal状态且前一非空格字符非反斜杠时,分号才表示语句结束 - 忽略空行、纯空格行、以
SET/USE/CREATE DATABASE开头的管理语句(按需)
真正难的不是读取,是判断“这条语句是否真的完整”。哪怕 sqlparse 也会在极少数嵌套括号+注释混合场景下误切,上线前务必用真实备份片段验证切分结果是否与 mysql 客户端执行行为一致。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











