找回密码
 立即注册
首页 业界区 安全 MySQL迁移至GreatSQL后,timestamp字段插入报错解析 ...

MySQL迁移至GreatSQL后,timestamp字段插入报错解析

伯斌 2025-10-1 18:09:09
MySQL迁移至GreatSQL后,timestamp字段插入报错解析

背景描述

某业务系统进行国产化适配,将MySQL的数据迁移到 GreatSQL 后,执行 INSERT INTO ,update_time传参为空时报错,报错信息为:ERROR 1048 (23000): Column 'update_time' cannot be null ,而原来旧的MySQL环境中没有这个问题。
  1. greatsql> INSERT INTO `t_interface`
  2. (`user_id`, `department_id`, `update_time`, `is_deleted`) VALUES (9, 18, null, 0);
  3. ERROR 1048 (23000): Column 'update_time' cannot be null
复制代码
问题分析

1、在 GreatSQL 进行复现
  1. CREATE TABLE `t_interface` (
  2.   `interfacer_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增id',
  3.   `user_id` int(11) NOT NULL COMMENT '用户id(部门接口人)',
  4.   `department_id` int(11) NOT NULL COMMENT '部门id',
  5.   `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  6.   `remark` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
  7.   `is_deleted` tinyint(4) NOT NULL DEFAULT '0' COMMENT '是否删除(0未删除 1已删除)',
  8.   PRIMARY KEY (`interfacer_id`) USING BTREE,
  9.   KEY `user_id` (`user_id`) USING BTREE,
  10.   KEY `department_id` (`department_id`) USING BTREE
  11. ) ENGINE=InnoDB AUTO_INCREMENT=1162 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT;
  12. INSERT INTO `t_interface`
  13. (`user_id`, `department_id`, `update_time`, `is_deleted`) VALUES (9, 18, null, 0);
  14. greatsql> INSERT INTO `t_interface`
  15.       (`user_id`, `department_id`, `update_time`, `is_deleted`) VALUES (9, 18, null, 0);
  16. ERROR 1048 (23000): Column 'update_time' cannot be null
复制代码
在 GreatSQL 中,INSERT 语句确实报错了。
2、在MySQL 8.0.32中进行复现
  1. mysql> SELECT  version();
  2. +-----------+
  3. | version() |
  4. +-----------+
  5. | 8.0.32    |
  6. +-----------+
  7. 1 row in set (0.00 sec)
  8. mysql> CREATE TABLE `t_interface` (
  9.     ->   `interfacer_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增id',
  10.     ->   `user_id` int(11) NOT NULL COMMENT '用户id(部门接口人)',
  11.     ->   `department_id` int(11) NOT NULL COMMENT '部门id',
  12.     ->   `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  13.     ->   `remark` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
  14.     ->   `is_deleted` tinyint(4) NOT NULL DEFAULT '0' COMMENT '是否删除(0未删除 1已删除)',
  15.     ->   PRIMARY KEY (`interfacer_id`) USING BTREE,
  16.     ->   KEY `user_id` (`user_id`) USING BTREE,
  17.     ->   KEY `department_id` (`department_id`) USING BTREE
  18.     -> ) ENGINE=InnoDB AUTO_INCREMENT=1162 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT;
  19. INSERT INTO `t_interface`
  20. Query OK, 0 rows affected, 7 warnings (0.04 sec)
  21. mysql> INSERT INTO `t_interface`
  22.     -> (`user_id`, `department_id`, `update_time`, `is_deleted`) VALUES (9, 18, null, 0);
  23. ERROR 1048 (23000): Column 'update_time' cannot be null
复制代码
在MySQL 8.0.32中,INSERT 语句也报错了。
3、在MySQL 5.7.30中进行复现
  1. mysql> SELECT  version();
  2. +------------+
  3. | version()  |
  4. +------------+
  5. | 5.7.30-log |
  6. +------------+
  7. 1 row in set (0.00 sec)
  8. mysql> CREATE TABLE `t_interface` (
  9.     ->   `interfacer_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增id',
  10.     ->   `user_id` int(11) NOT NULL COMMENT '用户id(部门接口人)',
  11.     ->   `department_id` int(11) NOT NULL COMMENT '部门id',
  12.     ->   `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  13.     ->   `remark` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
  14.     ->   `is_deleted` tinyint(4) NOT NULL DEFAULT '0' COMMENT '是否删除(0未删除 1已删除)',
  15.     ->   PRIMARY KEY (`interfacer_id`) USING BTREE,
  16.     ->   KEY `user_id` (`user_id`) USING BTREE,
  17.     ->   KEY `department_id` (`department_id`) USING BTREE
  18.     -> ) ENGINE=InnoDB AUTO_INCREMENT=1162 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT;
  19. Query OK, 0 rows affected (0.01 sec)
  20. mysql> INSERT INTO `t_interface`
  21.     -> (`user_id`, `department_id`, `update_time`, `is_deleted`) VALUES (9, 18, null, 0);
  22. Query OK, 1 row affected (0.00 sec)
  23. mysql> SELECT   * from t_interface;
  24. +---------------+---------+---------------+---------------------+--------+------------+
  25. | interfacer_id | user_id | department_id | update_time         | remark | is_deleted |
  26. +---------------+---------+---------------+---------------------+--------+------------+
  27. |          1162 |       9 |            18 | 2025-07-08 10:34:43 | NULL   |          0 |
  28. +---------------+---------+---------------+---------------------+--------+------------+
  29. 1 row in set (0.00 sec)
复制代码
在 MySQL 5.7.30 中,INSERT 语句可以正常执行。
4、问题排查

查看表的字段定义:
update_time:timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
建表语句里update_time字段类型为timestamp,NOT NULL的限制,默认值为 CURRENT_TIMESTAMP。
字段有 NOT NULL 的限制,不让插入NULL值,理论上是正确的。但 MySQL 5.7 为什么能插入成功呢?
查询系统timestamp相关的系统参数
  1. mysql> SELECT  version();
  2. +------------+
  3. | version()  |
  4. +------------+
  5. | 5.7.30-log |
  6. +------------+
  7. 1 row in set (0.00 sec)
  8. mysql>SHOW  variables LIKE '%timestamp%';
  9. +---------------------------------+-------------------+
  10. | Variable_name                   | Value             |
  11. +---------------------------------+-------------------+
  12. | explicit_defaults_for_timestamp | OFF               |
  13. | log_timestamps                  | SYSTEM            |
  14. | timestamp                       | 1751270610.230160 |
  15. +---------------------------------+-------------------+
  16. 3 rows in set (0.01 sec)
  17. mysql> SELECT VERSION();
  18. +-----------+
  19. | VERSION() |
  20. +-----------+
  21. | 8.0.32    |
  22. +-----------+
  23. 1 row in set (0.00 sec)
  24. mysql> SHOW variables like '%timestamp%';
  25. +---------------------------------+-------------------+
  26. | Variable_name                   | Value             |
  27. +---------------------------------+-------------------+
  28. | explicit_defaults_for_timestamp | ON                |
  29. | log_timestamps                  | SYSTEM            |
  30. | original_commit_timestamp       | 36028797018963968 |
  31. | timestamp                       | 1751270143.113409 |
  32. +---------------------------------+-------------------+
  33. 4 rows in set (0.01 sec)
  34. greatsql>SELECT  version();
  35. +-----------+
  36. | version() |
  37. +-----------+
  38. | 8.0.32-26 |
  39. +-----------+
  40. 1 row in set (0.00 sec)
  41. greatsql>SHOW variables like '%timestamp%';
  42. +---------------------------------+-------------------+
  43. | Variable_name                   | Value             |
  44. +---------------------------------+-------------------+
  45. | explicit_defaults_for_timestamp | ON                |
  46. | log_timestamps                  | SYSTEM            |
  47. | original_commit_timestamp       | 36028797018963968 |
  48. | timestamp                       | 1751271661.160386 |
  49. +---------------------------------+-------------------+
  50. 4 rows in set (0.02 sec)
复制代码
可以看到在MySQL 5.7中 explicit_defaults_for_timestamp =OFF,
在MySQL 8.0.32和 GreatSQL 中 explicit_defaults_for_timestamp =ON
  1. greatsql>INSERT INTO `t_interface`
  2.     -> (`user_id`, `department_id`, `update_time`, `is_deleted`) VALUES (9, 18, null, 0);
  3. ERROR 1048 (23000): Column 'update_time' cannot be null
  4. greatsql>set explicit_defaults_for_timestamp=OFF;
  5. Query OK, 0 rows affected, 1 warning (0.00 sec)
  6. greatsql>INSERT INTO `t_interface`  (`user_id`, `department_id`, `update_time`, `is_deleted`) VALUES (9, 18, null, 0);
  7. Query OK, 1 row affected (0.00 sec)
复制代码
在greatsql中,设置explicit_defaults_for_timestamp =OFF后,INSERT 语句可以正常执行。
5、参数说明

explicit_defaults_for_timestamp 这个系统变量决定服务器是否为TIMESTAMP列中的默认值和 空值处理启用某些非标准行为。 默认情况下,MySQL5.7禁用explicit_defaults_for_timestamp, 它启用非标准行为。MySQL8.启用explicit_defaults_for_timestamp,禁用非标准行为。
如果explicit_defaults_for_timestamp被禁用,服务器将启用非标准行为并按如下方式处理TIMESTAMP列:
1、未显式声明NULL属性的TIMESTAMP列将自动声明not NULL属性。允许将这样的列赋值为NULL,并将列设置为当前时间戳。
2、表中的第一个TIMESTAMP列,如果没有显式地使用NULL属性或显式地使用DEFAULT或ON UPDATE属性声明,则会自动使用DEFAULT CURRENT_TIMESTAMP和ON UPDATE CURRENT_TIMESTAMP属性声明。
3、第一个之后的TIMESTAMP列,如果没有显式地使用NULL属性或显式的DEFAULT属性声明,将自动声明为DEFAULT '0000-00-00 00:00:00'(“零”时间戳)。对于没有为这样的列指定显式值的插入行,将为该列分配‘0000-00-00 00:00:00’,并且不会出现警告。
4、根据是否启用了strict SQL模式或NO_ZERO_DATE SQL模式,默认值'0000-00-00 00:00:00'可能无效。请注意,TRADITIONAL SQL模式包括严格模式和NO_ZERO_DATE。
非标准行为在MySQL的未来版本中会被删除。
如果启用了explicit_defaults_for_timestamp,服务器将禁用非标准行为并按如下方式处理TIMESTAMP列:
1、不会将TIMESTAMP NULL列设置为当前时间戳。要分配当前时间戳,请将列设置为CURRENT_TIMESTAMP或NOW()之类的同义词。
2、未显式声明not NULL属性的TIMESTAMP列将自动声明NULL属性并允许NULL值。将这样的列赋值为NULL将其设置为NULL,而不是当前的时间戳。
3、用NOT NULL属性声明的TIMESTAMP列不允许NULL值。对于为这样的列指定NULL的插入,如果启用了严格的SQL模式,则结果是单行插入错误,如果禁用了严格的SQL模式,则会插入'0000-00-00 00:00:00'。在任何情况下,将列赋值为NULL都不会将其设置为当前时间戳。
4、使用NOT NULL属性显式声明且没有显式DEFAULT属性的TIMESTAMP列被视为没有默认值。对于没有为这样的列指定显式值的插入行,结果取决于SQL模式。如果启用了严格SQL模式,则会出现错误。如果没有启用严格的SQL模式,则使用隐式默认值'0000-00-00 00:00:00'声明列,并出现警告。这类似于MySQL处理其他时间类型(如DATETIME)的方式。
5、没有时间戳列被自动声明为默认的CURRENT_TIMESTAMP或ON UPDATE CURRENT_TIMESTAMP属性。这些属性必须显式指定。
6、表中的第一个TIMESTAMP列与第一个后面的TIMESTAMP列的处理方式没有区别。
  1. greatsql>SET explicit_defaults_for_timestamp=OFF;
  2. Query OK, 0 rows affected, 1 warning (0.00 sec)
  3. greatsql>CREATE TABLE t1( time1 timestamp, time2 timestamp null, time3 timestamp default '2025-01-01 00:00:00', name varchar(100) );
  4. Query OK, 0 rows affected (0.03 sec)
  5. greatsql>INSERT INTO t1  (`time1`, `time2`, `time3`, name) VALUES (null, null, null, 't1');
  6. Query OK, 1 row affected (0.02 sec)
  7. greatsql>SELECT  * FROM t1;
  8. +---------------------+-------+---------------------+------+
  9. | time1               | time2 | time3               | name |
  10. +---------------------+-------+---------------------+------+
  11. | 2025-07-08 13:53:58 | NULL  | 2025-07-08 13:53:58 | t1   |
  12. +---------------------+-------+---------------------+------+
  13. 1 row in set (0.00 sec)
  14. greatsql>SHOW CREATE TABLE t1\G
  15. *************************** 1. row ***************************
  16.        Table: t1
  17. Create Table: CREATE TABLE `t1` (
  18.   `time1` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  19.   `time2` timestamp NULL DEFAULT NULL,
  20.   `time3` timestamp NOT NULL DEFAULT '2025-01-01 00:00:00',
  21.   `name` varchar(100) COLLATE utf8mb4_general_ci DEFAULT NULL
  22. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
  23. 1 row in set (0.00 sec)
  24. greatsql>SET explicit_defaults_for_timestamp=ON;
  25. Query OK, 0 rows affected (0.00 sec)
  26. greatsql>CREATE TABLE t2( time1 timestamp, time2 timestamp null, time3 timestamp default '2025-01-01 00:00:00', name varchar(100) );
  27. Query OK, 0 rows affected (0.02 sec)
  28. greatsql>INSERT INTO t2  (`time1`, `time2`, `time3`, name) VALUES (null, null, null, 't2');
  29. Query OK, 1 row affected (0.01 sec)
  30. greatsql>SELECT  * FROM t2;
  31. +-------+-------+-------+------+
  32. | time1 | time2 | time3 | name |
  33. +-------+-------+-------+------+
  34. | NULL  | NULL  | NULL  | t2   |
  35. +-------+-------+-------+------+
  36. 1 row in set (0.00 sec)
  37. greatsql>SHOW CREATE TABLE t2\G
  38. *************************** 1. row ***************************
  39.        Table: t2
  40. Create Table: CREATE TABLE `t2` (
  41.   `time1` timestamp NULL DEFAULT NULL,
  42.   `time2` timestamp NULL DEFAULT NULL,
  43.   `time3` timestamp NULL DEFAULT '2025-01-01 00:00:00',
  44.   `name` varchar(100) COLLATE utf8mb4_general_ci DEFAULT NULL
  45. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
  46. 1 row in set (0.00 sec)
复制代码
可以看到,在 explicit_defaults_for_timestamp 等于OFF的时候,不仅影响写入,还会影响表结构。  time1 timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,  创建表时该字段自动增加了NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP 虽然字段类型是timestamp not nul ,但可以插入 null,数据写入后变为了 CURRENT TIMESTAMP.
解决方法

问题原因
1、explicit_defaults_for_timestamp参数在不同的数据库版本中,默认值不一样。该参数可能导致在低版本的 MySQL 中能执行的语句,在高版本的 MySQL 中不能执行。
潜在影响
MySQL 5.7升级到MySQL 8.0后,某些SQL语句执行可能会报错。
解决方法
1、设置explicit_defaults_for_timestamp=OFF,使该值和MySQL 5.7一致
该参数为全局变量,修改后会影响所有timestamp字段的处理逻辑(如自动添加NOT NULL和默认值),可能引发其他表的兼容性问题,建议仅在全面评估后临时使用,建议优先调整表结构或 SQL 语句。
2、修改表结构
调整字段定义为timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,从根源避免NULL插入冲突;
3、修改SQL语句:将字段插入的null值改为CURRENT_TIMESTAMP。
  1. INSERT INTO `t_interface`
  2.       (`user_id`, `department_id`, `update_time`, `is_deleted`) VALUES (9, 18, CURRENT_TIMESTAMP, 0);
复制代码
MySQL 5.7升级到MySQL 8.0.x/GreatSQL 8.0.x的一些关键注意事项

从5.7版本升级到8.0,有以下相关注意事项,请认真核对是否产生冲突或不兼容:

  • 最好是先升级到5.7.x的最新版本,再升级到8.0.x的最新版本,不要从5.7的小版本直接升级到8.0,尤其是非GA的版本。
  • 在8.0中,除了 general_log 和 slow_log 之外,其他所有元数据的字典数据都存储在InnoDB引擎表中,不再采用MyISAM引擎表存储。
  • 在8.0中,默认采用 caching_sha2_password 密码插件,这可能导致部分版本较早的连接驱动、连接客户端无法连接8.0的服务端,也需要同步升级。
  • 在8.0中,默认采用 utf8mb4 字符集,而5.7版本默认字符集是 utf8(也是 utf8mb3),在做数据迁移时要注意前后对照校验。
  • 在8.0中,启动时务必先设定好 lower_case_table_names 选项值,且实例启动后不可再更改,在个别不区分大小写的旧系统中迁移时要特别谨慎。
  • 在8.0中,参数explicit_defaults_for_timestamp默认值为 ON,这可能会影响 timestamp 类型字段的默认行为。
  • 在8.0中,默认启用event_scheduler,建议在主从复制或MGR中,在所有从节点中都关闭它。
  • 在8.0中,分组查询GROUP BY的结果不再默认进行排序,需要显式加上ORDER BY才行。
  • 新增保留字、关键字,详情请见:2.6 保留字、关键字。
  • 除InnoDB、NDB外,其他引擎不再支持表分区。
  • SQL Mode不再支持 NO_AUTO_CREATE_USER,也就是不能直接利用 GRANT 创建新用户并授权,需要先 CREATE USER 创建用户,再授权。
  • 部分参数选项不再支持,例如:innodb_locks_unsafe_for_binlog, old_passwords, query cache相关参数等。
  • 部分功能、函数不再支持,例如:query cache, PASSWORD(), ENCODE(), DECODE(), ENCRYPT()等。
参考文档

https://greatsql.cn/docs/8.0.32-26/7-migrate-and-upgrade/1-upgrade-to-greatsql8.html
https://dev.mysql.com/doc/refman/5.7/en/server-system-ariables.html#sysvar_explicit_defaults_for_timestamp
https://dev.mysql.com/doc/refman/8.0/en/server-system-ariables.html#sysvar_explicit_defaults_for_timestamp

Enjoy GreatSQL
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册