SQLite 默认值与NULL:表里写了DEFAULT,为什么显式传空仍然没有补上
表中给state写了默认值new,接口却仍能存进去NULL。很多问题出在应用把“用户没有提交这个字段”统一变成了一个空参数,导致插入语句其实明确给出了NULL。默认值何时生效由SQL语句决定,数据库不会根据字段看起来是否为空替应用猜测意图。
本例在Linux、CPython 3.12.14与SQLite 3.53.1实跑,使用Python标准库作为SQL实验入口。数据库仅存在于内存中,连接在finally里关闭。程序不读取或修改磁盘数据库,不涉及真实业务数据。
AI生成的概念示意图:未填写的格子收到预设色块,明确放入空白标记的格子保持原样,表示省略与显式空值走不同路径;不是真实软件界面或运行截图。
完整程序与实际输出
保存为demo.py,执行python3 demo.py。代码与输出分列如下。
import sqlite3
con = sqlite3.connect(':memory:')
try:
con.execute("CREATE TABLE job(name TEXT, state TEXT DEFAULT 'new', retries INTEGER NOT NULL DEFAULT 3)")
con.execute('INSERT INTO job(name) VALUES (?)', ('omitted',))
con.execute('INSERT INTO job(name,state) VALUES (?,?)', ('null', None))
con.execute('INSERT INTO job(name,state) VALUES (?,?)', ('empty', ''))
con.execute('INSERT INTO job DEFAULT VALUES')
rows = con.execute('SELECT name,state,retries FROM job ORDER BY rowid').fetchall()
assert rows == [('omitted', 'new', 3), ('null', None, 3), ('empty', '', 3), (None, 'new', 3)]
for row in rows:
print(row)
try:
con.execute('INSERT INTO job(name,retries) VALUES (?,?)', ('bad', None))
except sqlite3.IntegrityError as error:
print('explicit NULL retries:', error.sqlite_errorname)
else:
raise AssertionError('NULL unexpectedly accepted')
state, display = con.execute("SELECT state, coalesce(state,'new') FROM job WHERE name='null'").fetchone()
assert state is None and display == 'new'
print('stored/display:', state, display)
print('row count:', con.execute('SELECT count(*) FROM job').fetchone()[0])
finally:
con.close()本次实际标准输出:
('omitted', 'new', 3)
('null', None, 3)
('empty', '', 3)
(None, 'new', 3)
explicit NULL retries: SQLITE_CONSTRAINT_NOTNULL
stored/display: None new
row count: 4四种插入从列清单开始区分
第一条只指定name,state与retries都不在列清单中,所以得到new和三。第二条明确指定state并传入Python的None,绑定后就是SQL NULL,查询结果仍是None。第三条传入空字符串,结果是空字符串,它与NULL又是另一种状态。
第四条使用DEFAULT VALUES,整行各列都取各自默认值。name没有显式默认值,所以为NULL,另外两列分别得到new和三。程序按rowid排序只为稳定展示插入顺序,并用完整元组列表断言结果,避免仅检查某个字段就漏掉其它列的变化。
有默认值不代表禁止空值
state列允许NULL,所以显式传空可以成功。retries同时有NOT NULL与默认值,显式传None时却得到SQLITE_CONSTRAINT_NOTNULL。默认值没有在普通INSERT中自动替换这次非法输入,约束照样拒绝了它。表定义中的默认与约束分别承担不同职责。
示例捕获该次完整性错误,然后核对行数仍为四,证明失败记录没有被补成默认值后插入。这里没有使用特殊冲突处理策略,也没有触发器;修改这些条件可能改变行为,不能把本例结论机械套到带有额外修复逻辑的数据库上。
查询时兜底不会改写已存内容
最后同时查询state与coalesce结果,打印None和new。前者揭示原始存储仍是NULL,后者只是这次查询把空值显示成new。若只看界面上的new,容易误以为数据已经被清理,实际其它查询仍会读到NULL。
展示兜底可以让页面更易读,但应该清楚它是否隐藏了上游缺字段问题。若确实需要迁移旧数据,应安排单独、可审计的更新流程,并先确认NULL在业务中是否表示未知、继承或刻意清空。本文没有执行任何旧数据迁移。
在接口层保留字段是否出现的信息
接收JSON或表单时,应区分键未出现、键出现且值为null、键出现且值为空字符串。若业务规定未出现时使用数据库默认值,生成INSERT时应省略对应列,而不是无条件给所有列绑定None。列名来自固定允许清单,具体值仍使用参数绑定。若使用ORM,也应查看它实际发出的列清单与绑定值,因为对象属性未填写不一定等于最终SQL省略了该列。
测试应覆盖省略字段、显式null、空串、零值和正常值,并检查实际落库结果。零次重试可能是合法配置,不能用简单真假判断把它替换成三。默认值适合表达创建记录时的缺省选择,完整的数据合法性仍需类型、范围与跨字段规则共同约束。
参考资料与验证记录
官方资料核验于2026年10月3日。本次完整程序退出码为0,标准错误为空。


