SQLite 文本里的 NUL:长度只报二,为什么后面的字符仍然存在
导入一段编号后,SQLite 的 length 只返回二,应用重新读取却拿到五个字符。若立刻把问题归为驱动截断,就会查错方向。文本中间的 NUL,也就是 U+0000,会让 length 在那里停止计数,但后面的内容仍可能完整保存在值中。
NUL 是一个实际字符,与 SQL 的 NULL 空值不同。下面绑定固定字符串 AB、NUL、CD,不把控制字符直接拼进 SQL。保存为 demo.py,用 python3 demo.py 执行;示例只建立内存数据库,关闭连接后释放。
AI模型生成概念示意:短括号表示提前停止的观察范围,长括号表示仍存在的完整数据;非实测截图。
import sqlite3
db = sqlite3.connect(":memory:")
try:
text = "AB\x00CD"
row = db.execute(
"SELECT ?, length(?), length(CAST(? AS BLOB)), "
"hex(?), ? = 'AB', instr(?, char(0))",
(text,) * 6,
).fetchone()
assert row == (text, 2, 5, "4142004344", 0, 3)
print("round trip:", repr(row[0]))
print("text length:", row[1])
print("blob bytes:", row[2])
print("hex:", row[3])
print("equal to AB:", row[4])
print("NUL position:", row[5])
chinese = db.execute(
"SELECT length(?), length(CAST(? AS BLOB))", ("甲乙", "甲乙")
).fetchone()
assert chinese == (2, 6)
assert db.execute("SELECT length(NULL)").fetchone() == (None,)
print("Chinese text/bytes:", chinese)
finally:
db.close()先同时检查内容、长度和比较结果
程序依次输出 round trip: 'AB\x00CD'、text length: 2、blob bytes: 5、hex: 4142004344、equal to AB: 0、NUL position: 3,最后是 Chinese text/bytes: (2, 6)。repr 将不可见字符写成转义形式,避免终端把它显示成空白后误导观察。
4142004344 中间的 00 正是 NUL;43 和 44 表明 C、D 仍在后面。比较结果零说明整个值不等于 AB。由此能同时排除“后缀真的没存进去”和“所有操作都只看前两个字符”这两种猜测。不同函数有各自的文本处理规则。
instr 返回三,因为 SQL 字符位置从一开始;它可以用来标记含有 NUL 的数据。诊断时宜保留原值,再报告命中位置,不要看到控制字符就直接删掉后缀。删尾可能把两条原本不同的编号合并成同一个编号。
字节长度也不能冒充字符总数
CAST 为 BLOB 后的 length 统计字节,本例的 ASCII 样本因此得到五。最后的甲乙得到字符二、字节六,提醒我们这只是当前默认 UTF-8 数据库中的字节结果。换成别的数据库编码,字节数还可能变化;不能把 BLOB 长度当成通用的文本字符数。
如果输入契约要求普通标识符,应该在入库前明确拒绝 NUL 并指出位置。若字段本来装的是任意二进制载荷,就使用 BLOB 和对应的字节接口。选择类型应按数据含义决定,不宜为了让一个 length 结果好看而临时改列类型。
NULL 的 length 返回 NULL,代码以断言另行验证,Python 驱动将它表示为 None。它和空字符串的零、嵌入 NUL 文本的局部计数是三种情况。数据质量报告应分别列出,尤其不要把缺失字段与内容异常一起算成“长度为零”。
这个实验没有证明所有客户端都能正确展示 NUL。导出、日志和界面仍需独立验收。排查异常编号时,优先比较参数化读取结果与明确编码的字节表示,再判断应该修正输入、存储类型还是显示方式。
资料核对日期:2026年10月2日。最终展示代码在 Python 3.12.14 / SQLite 3.53.1 独立运行并通过全部断言;结果对应文中固定输入。


