跳到主要内容

默认值

默认值(default value)用于回答一个问题:插入新行时,如果没有为某列提供值,数据库应写入什么? 它能减少重复输入,并把稳定的数据规则放在所有写入入口都能执行的数据库层。

默认值不是校验规则,也不会在普通更新时自动重新计算。设计默认值时,最重要的是分清“未提供值”“明确写入 NULL”和“值必须满足约束”这三件事。

本文以通用 SQL 概念为主,示例采用 PostgreSQL 可执行的语法。其他数据库对函数、序列和修改默认值的写法可能不同。

定义默认值​

在列定义后使用 DEFAULT:

CREATE TABLE orders (
order_id BIGINT GENERATED ALWAYS AS IDENTITY,
status TEXT NOT NULL DEFAULT 'pending',
item_count INTEGER NOT NULL DEFAULT 1,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
note TEXT DEFAULT NULL
);

默认值可以是:

  • 与列类型兼容的常量,如 'pending'、0、FALSE;
  • 数据库函数或表达式,如 CURRENT_TIMESTAMP;
  • PostgreSQL 序列调用等数据库专有表达式。

没有显式默认值的列,其隐式默认值通常是 NULL。如果该列同时声明了 NOT NULL,插入时既没有提供值、又没有可用默认值,语句就会失败。

默认值何时生效​

默认值在写入时按需使用,而不是建表时提前保存一个固定结果。

省略目标列​

INSERT INTO orders (note)
VALUES ('首次下单');

这里 status、item_count 和 created_at 都使用各自的默认值。

显式使用 DEFAULT​

INSERT INTO orders (status, item_count)
VALUES (DEFAULT, DEFAULT);

也可以让所有列都按默认规则处理:

INSERT INTO orders DEFAULT VALUES;

更新已有行时,PostgreSQL 允许把某列恢复为当前定义的默认值:

UPDATE orders
SET status = DEFAULT
WHERE order_id = 1001;

这不是“恢复历史初始值”,而是重新计算并写入该列现在的默认表达式。

默认值与 NULL​

省略列和显式写入 NULL 含义不同:

-- 使用默认值 pending
INSERT INTO orders (note)
VALUES ('省略状态');

-- 尝试写入 NULL;因为 status 是 NOT NULL,所以失败
INSERT INTO orders (status, note)
VALUES (NULL, '明确的空值');

默认值不会把所有 NULL 自动替换掉。若业务上不允许空值,应同时使用 NOT NULL;若输入中的 NULL 应被转换为某个值,应在写入语句或应用逻辑中明确处理,例如 COALESCE(:status, 'pending')。

表达式会在什么时候求值​

默认表达式通常在每次插入需要它时求值:

CREATE TABLE events (
event_id BIGINT GENERATED ALWAYS AS IDENTITY,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
random_key DOUBLE PRECISION NOT NULL DEFAULT random()
);

random() 通常会为不同行产生不同结果。时间函数的精确语义则取决于数据库:在 PostgreSQL 中,CURRENT_TIMESTAMP 表示当前事务开始时间,不一定等于每一行实际执行的墙上时钟时间。需要语句时间或实时钟表时间时,应选择符合语义的函数并核对版本文档。

默认表达式不能引用同一行的其他列。需要根据其他列计算结果时,应考虑生成列、触发器,或在写入语句中直接计算。

使用序列作为默认值​

PostgreSQL 早期常用序列生成编号:

CREATE SEQUENCE invoice_number_seq;

CREATE TABLE invoices (
invoice_id BIGINT PRIMARY KEY
DEFAULT nextval('invoice_number_seq'),
amount NUMERIC(12, 2) NOT NULL
);

现代新表通常优先使用标识列,因为标识列会把序列与列的关系表达得更清楚:

invoice_id BIGINT GENERATED ALWAYS AS IDENTITY

无论使用序列默认值还是标识列,编号都不保证连续。事务回滚、并发插入和序列缓存都可能留下空号。

修改或删除默认值​

ALTER TABLE 可以修改已有列的默认规则:

ALTER TABLE orders
ALTER COLUMN status SET DEFAULT 'confirmed';

删除默认值:

ALTER TABLE orders
ALTER COLUMN status DROP DEFAULT;

这些操作影响之后的写入,不会自动改写历史数据:

UPDATE orders
SET status = 'confirmed'
WHERE status = 'pending';

是否应该更新历史行是独立的业务决定。结构迁移和数据迁移最好分开评估、分步执行。

默认值、生成列与触发器​

三者解决的问题不同:

机制适合解决的问题普通更新时自动变化
默认值插入时未提供值,应该采用什么初始值否
生成列某列始终由当前行其他列的表达式计算得到是
触发器需要过程逻辑、跨表操作或审计副作用可配置

例如,created_at DEFAULT CURRENT_TIMESTAMP 适合记录创建时间;它不能自动维护 updated_at。更新时间通常由应用显式写入,或由数据库触发器维护。

数据库默认值与应用默认值​

数据库默认值覆盖所有写入入口,包括应用、脚本、导入工具和管理后台,因此适合长期稳定的规则。应用默认值则能利用请求上下文,但只有经过该应用的写入才会执行。

可以同时设置两层默认值,但要确保含义一致。否则应用升级、批处理绕过应用或数据库迁移后,可能出现同一列由不同入口写入不同默认值的问题。

以下值通常适合数据库默认值:

  • 新记录的稳定状态;
  • 创建时间;
  • 布尔开关的初始值;
  • 数量的自然起始值。

以下值通常不应被一个简单默认值掩盖:

  • 当前登录用户、租户等必须由可信上下文确定的值;
  • 依赖远程服务的结果;
  • 会频繁变化的复杂业务规则;
  • 实际上必须由调用方明确选择的关键字段。

常见误区​

默认值会修复历史空值​

不会。设置或修改默认值只影响以后使用默认规则的写入,已有 NULL 需要另行回填。

有默认值就不需要 NOT NULL​

错误。调用方仍可能显式写入 NULL。若列必须有值,应同时声明 NOT NULL。

时间默认值会随时间自动更新​

不会。默认表达式只在插入或显式写 DEFAULT 时求值,不是持续计算的公式。

自动编号必须连续​

默认值或序列只负责生成值,不承诺无空洞。发票等依法要求连续的业务编号需要单独设计分配、锁定和作废记录流程。

设计检查清单​

  1. 该值是否真的能在调用方省略时被安全推断;
  2. 显式 NULL 应被拒绝、保留,还是转换;
  3. 默认表达式是否与列类型兼容;
  4. 表达式是每行、每语句还是每事务求值;
  5. 修改默认值后,历史数据是否需要单独迁移;
  6. 应用层与数据库层的默认规则是否一致;
  7. 该需求实际上是否更适合生成列、约束或触发器。

默认值的价值在于提供清晰、稳定的初始规则。它应该让缺省写入更安全,而不应掩盖调用方遗漏了关键业务数据这一事实。