🦏 第9章 列的属性
📄 一句话概括

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 秒实验

  1. 表里 status 有 DEFAULT 0,插入时显式写 NULL,存进去的是什么?
  2. DEFAULT (datetime('now')) 少写外面那层括号会怎样?
  3. 给一张有 10 万行的表加 ALTER TABLE 表 ADD COLUMN s TEXT NOT NULL,会成功吗?

常见误区

  1. 以为「有默认值就不会为空」。 显式给 NULL、或者 update 改成 NULL,默认值都管不了。 要非空得加 NOT NULL。
  2. 程序拼 SQL 时空值拼成 NULL。 这就绕过了默认值,该省略这一列,而不是给它一个 NULL。
  3. 默认值写 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 非空——顺便说清「空串不是空」。

相关笔记