DEFAULT 值 只在「这条语句根本没提这一列」时生效。
显式写 NULL 那就是「我就是要空」,数据库照办。
默认值可以是表达式,但 DEFAULT (datetime('now')) 的括号不能省,
省了就成了字符串 'now'。 而且它给的是 UTC,比本地早 8 小时。
默认值完全不管 update。 update 把列改成 NULL 一句都不拦——
要管这件事得靠 NOT NULL。
第 9.1 篇把七个属性过了一遍,从这一篇开始一个一个细看。 先讲默认值,因为它最容易「看起来生效了、其实没有」。 默认值的所有坑都出在「什么时候算没给」这个判断上。
一、不给值就用默认值
建表时 DEFAULT 值,插入时省掉这一列,它就自动填上。 Demo 里只给了 id,其余三列全走默认值:
性别「未知」、分数 0、时间自动填。
这是最常见的用途:
状态位默认「未处理」、分数默认 0、创建时间默认当前时间。 业务代码就不用每次都显式写一遍。
二、关键:显式给 NULL,默认值不生效
insert into 表 (id, gender) values (2, null)——第二行性别是 NULL,不是「未知」。
DEFAULT 只在「这条语句里根本没提这一列」时生效。
你显式写了 NULL,那就是「我就是要空」,数据库照办。
程序里最容易踩这个坑:
变量是空的时候,拼出来的 SQL 带了个 NULL,而不是省略这一列。
结果就是「默认值好像没生效」——其实它生效了,只是你没给它机会。
三、默认值可以是表达式,括号不能省
DEFAULT (datetime('now')) 外面那层括号是必须的。
省了以后它被当成字符串常量,每行都存着 now 三个字母,建表还不报错。
表达式默认值适合「跟此刻有关」的东西:当前时间、随机数、DEFAULT 0 这种常量反而不需要表达式。
MySQL 那边写 DEFAULT CURRENT_TIMESTAMP,不用括号。
四、时区这个坑绕不开
datetime('now') 给的是 UTC,
所以 Demo 里那个时间比本地时间早 8 小时。 要本地时间得写 datetime('now', 'localtime')。
MySQL 的 CURRENT_TIMESTAMP 用的是服务器时区,
两台服务器时区不一样,同一句建表语句填出来的时间就不一样。
所以「靠默认值取当前时间」在跨库、跨机器时是不可靠的。 稳妥做法是统一存 UTC、显示时再转,
或者干脆由应用层给时间,数据库只负责存。
五、默认值完全不管 update
update 表 set gender = null 一句也不拦。
默认值只在插入时参与,改数据时它一点作用都没有。
这是很多人对默认值的误解:以为「有默认值就不会空」。
不会空要靠 NOT NULL,那是下一篇。 两个属性配合才是「一定有值」:
status INTEGER NOT NULL DEFAULT 0。
六、加列时默认值会补到老行上
alter table 表 add column 列 类型 default 值,
已有行的这一列会一次性填上那个默认值。
第 8.5 篇 Demo 里加 age INTEGER DEFAULT 18,三行全变成 18。
反过来,加「非空且无默认值」的列会直接失败,
因为老行没法填。 报错是 Cannot add a NOT NULL column with default value NULL。
所以给老表加列,默认值几乎必写。
七、动手 Demo:默认值什么时候生效、什么时候不生效
Demo 五段:先建一张带三个默认值的表,然后依次验证「不给值」「给 NULL」「表达式默认值」「update」。 第二段和第四段是这一篇的重点——它们说明默认值的作用范围比想象中窄。
import _q46 as Q
c = Q.blank()
Q.run(c, "CREATE TABLE demo_def (id INTEGER PRIMARY KEY, "
"gender TEXT DEFAULT '未知', "
"score INTEGER DEFAULT 0, "
"created TEXT DEFAULT (datetime('now')))")
# 只给 id,其余走默认值
Q.run(c, "INSERT INTO demo_def (id) VALUES (1)")
Q.show(c, "SELECT id, gender, score, created FROM demo_def")
# 显式给 NULL:默认值不生效
Q.run(c, "INSERT INTO demo_def (id, gender) VALUES (2, NULL)")
# 默认值管不到 update
Q.run(c, "UPDATE demo_def SET gender = NULL WHERE id = 1")
实跑输出:
--------------------------------------------------------------------
第四十三课:默认值——只在「没给」的时候生效
--------------------------------------------------------------------
不给值就用默认值
SQL> CREATE TABLE demo_def (id INTEGER PRIMARY KEY, gender TEXT DEFAULT '未知',
score INTEGER DEFAULT 0, created TEXT DEFAULT (datetime('now')))
完成
SQL> INSERT INTO demo_def (id) VALUES (1)
影响 1 行
SQL> SELECT id, gender, score, created FROM demo_def
id gender score created
------------------------------------------------
1 未知 0 2026-10-29 23:43:18
(1 行)
**只给了 `id`,其余三列全走默认值**:性别「未知」、分数 0、时间自动填上。
关键:显式给 NULL,默认值不生效
SQL> INSERT INTO demo_def (id, gender) VALUES (2, NULL)
影响 1 行
SQL> SELECT id, gender FROM demo_def
id gender
--------------
1 未知
2 NULL
(2 行)
**第二行的性别是 NULL,不是「未知」。** **这是默认值最容易被误解的地方。**
**`DEFAULT` 只在「这条语句里根本没提这一列」时生效。**
**你显式写了 `NULL`,那就是「我就是要空」,数据库照办。** **程序里拼 `insert` 时最容易踩这个坑**
——**变量是空的时候拼成了 `NULL` 而不是省略这一列。**
默认值可以是表达式,但要注意时区
**`DEFAULT (datetime('now'))` 里那个括号不能省**,**省了就变成字符串 `'now'`**——建表不报错,
之后每行都存着「now」三个字母。
SQL> SELECT id, created FROM demo_def
id created
---------------------------
1 2026-10-29 23:43:18
2 2026-10-29 23:43:18
(2 行)
**注意这个时间:它比本地时间早 8 小时**,
**因为 `datetime('now')` 给的是 UTC。** **要本地时间得用 `datetime('now', 'localtime')`。**
**MySQL 那边是 `DEFAULT CURRENT_TIMESTAMP`**,**用的是服务器时区**——两台服务器时区不一样,
同一句建表语句填出来的时间就不一样。 **所以跨库对齐时间,靠「默认值取当前时间」是不可靠的。**
默认值对 update 完全不管
SQL> UPDATE demo_def SET gender = NULL WHERE id = 1
影响 1 行
SQL> SELECT id, gender FROM demo_def
id gender
--------------
1 NULL
2 NULL
(2 行)
**`update` 把性别改成 NULL,一句也不拦。** **默认值只在插入时参与,改数据时它一点作用都没有。**
**要「不许为空」,得靠 `NOT NULL`——那是下一课。**
默认值的两个常见用途
**一是状态位**:`status INTEGER DEFAULT 0`,**新记录默认「未处理」,业务代码不用每次显式写。**
**二是时间戳**:`created` 默认当前时间、
**`updated` 默认当前时间并在每次 `update` 时手动改。**
**注意后半句得自己写,默认值不会在 update 时自动刷新。**
第一段只给 id,其余三列拿到「未知」、0、当前时间。
第二段显式给 NULL,第二行是 NULL 而不是「未知」——默认值被绕过了。
第三段看那个时间:2026-10-29 23:42:47,UTC,比本地时间早 8 小时。
第四段 update 改成 NULL,一句不拦,两行都空了。
第五段没有 SQL,讲的是默认值的两个常见用途和「update 不刷新」这件事。
30 秒实验
- 表里
status有DEFAULT 0,插入时显式写NULL,存进去的是什么? DEFAULT (datetime('now'))少写外面那层括号会怎样?- 给一张有 10 万行的表加
ALTER TABLE 表 ADD COLUMN s TEXT NOT NULL,会成功吗?
常见误区
- 以为「有默认值就不会为空」。 显式给 NULL、或者
update改成 NULL,默认值都管不了。 要非空得加NOT NULL。 - 程序拼 SQL 时空值拼成
NULL。 这就绕过了默认值,该省略这一列,而不是给它一个 NULL。 - 默认值写
DEFAULT datetime('now')不带括号。 建表不报错,之后每行都存着「now」这三个字母。
自测三问(点开对答案)
1. 默认值在什么时候生效? —— 只在插入语句里根本没提这一列的时候。显式给 NULL 或 update 都不触发。 2. 为什么 `datetime('now')` 存进去的时间比本地早 8 小时? —— 它给的是 UTC。要本地时间得用 `datetime('now', 'localtime')`。 3. 给老表加一列 `NOT NULL` 又不给默认值,会怎样? —— 直接失败,报 `Cannot add a NOT NULL column with default value NULL`,因为老行没法填。默认值管不了「不许为空」。下一篇 9.3 非空——顺便说清「空串不是空」。
- ☐ (本篇完,下一篇 9.3 非空)