在关系型数据库里,DEFAULT约束用来给列指定一个当插入操作未显式提供该列值时的替代值。它不改变表结构的主外键逻辑,但能显著降低应用层拼装SQL的复杂度,也能避免某些列因空值引发后续查询或统计异常。理解DEFAULT约束的定义方式、生效时机以及各数据库的差异,是写出健壮数据层代码的基础。
一、DEFAULT约束的基本定义
DEFAULT约束可以在创建表时直接挂在列定义后面,也可以在表创建后通过ALTER TABLE语句追加。它的核心语义是:当INSERT语句没有为该列提供值,或者显式写入DEFAULT关键字时,数据库使用约束中指定的值或表达式结果填充该列。
需要注意的是,DEFAULT并不会在更新操作中自动补值,也不会把已有的NULL强行替换成默认值,除非你显式执行UPDATE。另外,如果列被声明为NOT NULL且没有DEFAULT,插入时漏掉该列就会直接报错,这也是很多新手在迁移旧表结构时容易踩的坑。
1.1 建表时定义DEFAULT
下面以MySQL为例,在用户表中给注册时间、状态字段设置默认值。注册时间使用函数获取当前时间,状态用字面量字符串表示正常。
CREATE TABLE user_account (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL,
status VARCHAR(20) DEFAULT 'active',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
上述代码中,status列默认填入active,created_at列默认填入当前时间。插入时若只写username,其余两列会自动采用默认值,避免应用层每次都手动拼接时间函数。
1.2 使用ALTER TABLE追加DEFAULT
当表已经存在且前期设计遗漏了默认值,可以通过修改表结构补充。以下语句为已有表的remark列增加默认值空字符串。
ALTER TABLE user_account ALTER COLUMN remark SET DEFAULT '';
在SQL Server中语法略有不同,需要使用ADD CONSTRAINT来命名约束;而在PostgreSQL里则用ALTER COLUMN ... SET DEFAULT。跨数据库迁移脚本时要特别注意这种语法差异,否则会导致部署失败。
二、DEFAULT约束中的函数与表达式
很多开发者以为DEFAULT只能写固定值,其实主流数据库都支持函数甚至简单表达式。合理使用能让默认值随上下文动态生成,减少冗余代码。
例如在PostgreSQL中,可以使用nextval()从序列取号,或用now()获取事务时间;在MySQL 8.0之后也支持将表达式如(price * 0.9)作为默认值。但要注意,表达式默认值通常要求是确定性的或数据库明确支持的非确定性函数,否则建表会被拒绝。
2.1 表达式默认值示例
下面PostgreSQL示例给订单表的增加一个自动计算到期日的列,默认是当前时间加七天。
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
amount NUMERIC(10,2),
created_at TIMESTAMP DEFAULT now(),
expire_at TIMESTAMP DEFAULT (now() + INTERVAL '7 days')
);
这样每次插入订单,expire_at都会自动算出七天后的时间,不必在业务代码里反复计算。如果后期规则改为十五天,只需ALTER COLUMN重新设置DEFAULT即可,历史数据不受影响。
2.2 函数默认值的限制
并非所有函数都能用在DEFAULT里。MySQL早期版本只允许CURRENT_TIMESTAMP、CURRENT_DATE等少数函数;SQL Server的GETDATE()可以用,但用户自定义函数若包含访问其他表的逻辑通常会被禁止。设计时应优先选用数据库内置的轻量函数。
-- MySQL 5.7 合法写法
CREATE TABLE log_table (
id INT PRIMARY KEY AUTO_INCREMENT,
log_time DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- MySQL 5.7 非法写法(不支持自定义函数作默认值)
CREATE TABLE bad_table (
id INT PRIMARY KEY,
val INT DEFAULT my_custom_func()
);
上面第二段在旧版本MySQL中会报语法错误,因为DEFAULT子句不支持调用用户函数。升级到支持表达式的版本或改用触发器才是可行方案。
三、DEFAULT约束失效的常见场景
即便正确定义了DEFAULT,实际开发中仍会出现默认值没起作用的情况。厘清这些场景能帮你快速定位问题,而不是盲目怀疑数据库有Bug。
最常见的原因是INSERT语句显式写入了NULL。DEFAULT只在“未提供列”时生效,如果你写的是NULL,数据库会尊重你的显式NULL而跳过默认值。此外,某些ORM框架为了映射对象完整性,会自动把所有字段拼进SQL并赋NULL,这也会让DEFAULT形同虚设。
3.1 显式NULL覆盖默认值
观察下面两条语句的差异,第一条漏掉status因此填入默认值,第二条明确写NULL因此该列就是NULL。
-- 使用默认值
INSERT INTO user_account (username) VALUES ('alice');
-- 显式NULL,默认值不生效
INSERT INTO user_account (username, status) VALUES ('bob', NULL);
如果业务上不允许NULL,建议把列设为NOT NULL配合DEFAULT,这样第二条语句会直接报错,从而暴露代码里的逻辑漏洞,而不是悄悄存入空值。
3.2 ORM框架导致的陷阱
以Java的MyBatis为例,如果实体类字段默认是null且XML映射写了全字段插入,生成的SQL就会带NULL。此时应改为动态SQL,只插入非null字段,或给实体类赋上对应默认值,才能让数据库DEFAULT参与工作。
<insert id="insertUser">
INSERT INTO user_account
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="username != null">username,</if>
<if test="status != null">status,</if>
</trim>
<trim prefix="VALUES (" suffix=")" suffixOverrides=",">
<if test="username != null">#{username},</if>
<if test="status != null">#{status},</if>
</trim>
</insert>
这段XML利用if标签动态拼接,只有status不为null时才写该列,从而让数据库的DEFAULT在status缺失时生效。如果图省事写死所有列,DEFAULT约束就完全没机会执行。
四、DEFAULT与其他约束的协作
DEFAULT常和NOT NULL、CHECK配合形成完整的数据校验链。单独的DEFAULT只解决缺值问题,加上NOT NULL才能杜绝空值,加上CHECK可限制默认值本身及后续写入的合法范围。
例如在金额表里面,把余额默认值设为0且声明NOT NULL,再附加CHECK(balance >= 0),既能保证新用户有初始零余额,也能阻止负余额写入。这种组合在金融、库存类系统中非常普遍。
4.1 组合约束建表示例
下面SQL展示一个带DEFAULT、NOT NULL和CHECK的账户表,安全级别比单写DEFAULT高很多。
CREATE TABLE wallet (
user_id INT PRIMARY KEY,
balance DECIMAL(12,2) NOT NULL DEFAULT 0,
CONSTRAINT chk_balance_non_neg CHECK (balance >= 0)
);
插入时不写balance,它会自动变成0且不可能为NULL;如果有人试图写负余额,CHECK立即拦截。这样即使应用层校验疏忽,数据库依然守住最后一道防线。
4.2 删除与修改DEFAULT
当业务规则变化,可能需要去掉或替换默认值。各数据库都提供相应语法,例如MySQL用ALTER COLUMN ... DROP DEFAULT,PostgreSQL用ALTER COLUMN ... DROP DEFAULT。修改则是先删后设,或者直接SET DEFAULT新值覆盖旧值。
-- PostgreSQL修改默认值 ALTER TABLE wallet ALTER COLUMN balance SET DEFAULT 10; -- MySQL删除默认值 ALTER TABLE wallet ALTER COLUMN balance DROP DEFAULT;
操作前建议确认线上是否有依赖旧默认值的写入逻辑,尤其当默认值是状态标识或时间字段时,突然变更可能导致统计口径混乱。最好在测试环境跑一遍存量数据影响评估再上生产。
五、不同数据库语法对照
虽然DEFAULT概念一致,但具体关键字和函数支持度有差别。下面用表格归纳常见数据库在设置与修改默认值时的写法,方便跨库开发时查阅。
| 数据库 | 建表设默认值 | 修改默认值 | 支持表达式 |
|---|---|---|---|
| MySQL | 列后写 DEFAULT 值 | ALTER COLUMN 列 SET DEFAULT 值 | 8.0+支持 |
| PostgreSQL | 列后写 DEFAULT 表达式 | ALTER COLUMN 列 SET DEFAULT 表达式 | 支持 |
| SQL Server | 列后写 DEFAULT 值或ADD CONSTRAINT | ADD/DROP CONSTRAINT | 有限支持 |
| Oracle | 列后写 DEFAULT 值 | MODIFY 列 DEFAULT 值 | 11g+部分支持 |
从表中可以看出,PostgreSQL对表达式默认值最友好,而Oracle和旧版MySQL在动态默认值上相对保守。团队做多库兼容时,应尽量把默认值逻辑收敛到应用层或使用数据库原生支持的特性,减少维护分支。
总体来看,DEFAULT约束是SQL里成本低、收益高的基础能力。只要在建模阶段想清楚哪些列必须有初始状态,并配合NOT NULL与CHECK,就能让数据质量在源头得到保障,也减轻业务代码的边界判断负担。