在数据库表设计中,日期时间字段几乎无处不在。业务表中常常会有一些允许为NULL的可选字段,同时又希望created_at这类时间字段在插入时自动记录当前时间,而不需要应用程序显式传入。MySQL对这种场景有原生支持,核心就是DEFAULT CURRENT_TIMESTAMP。下面从建表、插入、参数影响和常见坑几个方面展开说明。

一、建表时如何定义自动填充的时间列
要让某个时间列在插入时自动写入当前日期时间,需要在建表语句中为该列设置默认值。MySQL支持两种常用类型:timestamp和datetime。其中timestamp从早期版本就支持CURRENT_TIMESTAMP作为默认值,datetime则需要MySQL 5.6.5及以上版本。下面是一个典型的建表语句:
CREATE TABLE t_order (
order_id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
remark VARCHAR(500) DEFAULT NULL,
discount DECIMAL(10,2) DEFAULT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
这个表结构中,remark和discount两个列允许为NULL,插入时可以不提供值或者显式写入NULL。created_at在插入时会自动填充当前时间,updated_at除了插入时填充外,每次更新行记录时也会自动刷新。这样应用程序的INSERT语句就完全不需要关心时间字段的赋值问题。
二、插入NULL值时时间列的自动填充行为
理解默认值生效的条件非常关键。MySQL的规则是:只有当INSERT语句没有为该列提供任何值时,DEFAULT CURRENT_TIMESTAMP才会生效。如果显式给时间列写入了NULL,行为则取决于列是否允许NULL值。看下面几个插入语句的区别:
-- 写法一:只插入业务列,remark和discount为NULL,时间列自动填充
INSERT INTO t_order (remark, discount) VALUES ('加急订单', NULL);
-- 写法二:显式列出时间列并传NULL,由于created_at是NOT NULL,会直接报错
-- INSERT INTO t_order (remark, created_at) VALUES ('测试', NULL);
-- 写法三:省略全部可选列,全部走默认值
INSERT INTO t_order () VALUES ();
写法一是最推荐的实践方式。列表中不出现created_at和updated_at,MySQL会自动用当前时间填充,同时业务列可以自由传入NULL。写法二会报错,因为设置了DEFAULT CURRENT_TIMESTAMP的timestamp列默认是NOT NULL的,显式插入NULL不被允许。写法则说明即使不写任何列名,整行插入也能让所有字段按默认规则处理。
如果确实希望时间列也能接受NULL值,可以把定义改为created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,加上NULL关键字后该列就允许显式插入NULL了。不过一般不建议这样做,创建时间字段保持NOT NULL更符合审计需求。
三、explicit_defaults_for_timestamp参数的影响
这个系统变量直接决定了timestamp列的默认行为,也是很多人踩坑的地方。在MySQL 5.7及之前,该参数默认关闭,此时建表时如果表中含有timestamp列且没有显式声明默认值,第一个timestamp列会被自动附加DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP属性,这种隐式行为常常导致意外的时间自动更新。从MySQL 8.0开始,该参数默认开启,不再有隐式附加属性,所有默认值都必须显式声明。
可以通过下面的语句查看当前设置:
SHOW VARIABLES LIKE 'explicit_defaults_for_timestamp';
在显式默认值模式下,timestamp列如果没有声明NOT NULL和默认值,插入NULL时该列会存储NULL而不是当前时间。因此升级数据库版本后,如果发现时间字段不再自动填充,优先检查这个参数是否发生了变化,并在建表语句中显式写清楚默认值。
四、timestamp与datetime如何选择
两者都能配合CURRENT_TIMESTAMP实现自动填充,但底层机制不同。timestamp存储时会自动转换成UTC时间,读取时再按会话时区转换回来,占4个字节,范围只到2038年。datetime则存储字面值不做时区转换,占5个字节(MySQL 5.6.4之后),范围可达9999年。对比如下:
| 特性 | timestamp | datetime |
|---|---|---|
| 存储空间 | 4字节 | 5字节 |
| 时间范围 | 1970至2038年 | 1000至9999年 |
| 时区处理 | 随会话时区转换 | 原样存储 |
| 支持自动默认值 | 全版本 | 5.6.5起 |
如果应用存在跨时区场景,用户分布在不同时区,timestamp的自动时区转换会方便很多。如果只面向单一时区且数据需要长期保存,datetime避免了2038年问题,是更稳妥的选择。无论选哪种,自动填充的写法完全一致,可以随时根据业务调整。
五、常见报错与处理办法
最常见的一个错误是 Incorrect specification of timestamp value,通常发生在建表时使用引号包裹默认值,例如写成DEFAULT 'CURRENT_TIMESTAMP',或者给timestamp列设置了超出范围的默认时间。解决办法是保证CURRENT_TIMESTAMP不加引号,并且不要在同一个默认值表达式里混用引号。
另一个高频问题是 Invalid use of NULL value,报错原因是列声明为NOT NULL却没有默认值,而INSERT语句没有提供该列。很多ORM框架在生成SQL时会跳过值为NULL的属性,如果实体类中的时间属性为NULL且列定义不允许NULL,就会触发这个错误。给时间列加上DEFAULT CURRENT_TIMESTAMP后,框架跳过该列时数据库会自动补上当前时间,问题随之解决。
还有一种情况是同一张表中多个timestamp列都想自动填充,在MySQL 5.6.5之前的版本会报错,因为老版本一张表只允许一个自动更新的timestamp列。升级到新版本即可,或者只保留一个自动更新列,其余时间列改用datetime类型。
MySQL自动插入时间NULL值插入DEFAULT CURRENT_TIMESTAMP修改时间:2026-09-14 22:55:01