MySQL遷移至GreatSQL后,timestamp字段插入報錯解析
MySQL遷移至GreatSQL后,timestamp字段插入報錯解析
背景描述
某業務系統進行國產化適配,將MySQL的數據遷移到 GreatSQL 后,執行 INSERT INTO ,update_time傳參為空時報錯,報錯信息為:ERROR 1048 (23000): Column 'update_time' cannot be null ,而原來舊的MySQL環境中沒有這個問題。
greatsql> INSERT INTO `t_interface`
(`user_id`, `department_id`, `update_time`, `is_deleted`) VALUES (9, 18, null, 0);
ERROR 1048 (23000): Column 'update_time' cannot be null
問題分析
1、在 GreatSQL 進行復現
CREATE TABLE `t_interface` (
`interfacer_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增id',
`user_id` int(11) NOT NULL COMMENT '用戶id(部門接口人)',
`department_id` int(11) NOT NULL COMMENT '部門id',
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新時間',
`remark` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
`is_deleted` tinyint(4) NOT NULL DEFAULT '0' COMMENT '是否刪除(0未刪除 1已刪除)',
PRIMARY KEY (`interfacer_id`) USING BTREE,
KEY `user_id` (`user_id`) USING BTREE,
KEY `department_id` (`department_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1162 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT;
INSERT INTO `t_interface`
(`user_id`, `department_id`, `update_time`, `is_deleted`) VALUES (9, 18, null, 0);
greatsql> INSERT INTO `t_interface`
(`user_id`, `department_id`, `update_time`, `is_deleted`) VALUES (9, 18, null, 0);
ERROR 1048 (23000): Column 'update_time' cannot be null
在 GreatSQL 中,INSERT 語句確實報錯了。
2、在MySQL 8.0.32中進行復現
mysql> SELECT version();
+-----------+
| version() |
+-----------+
| 8.0.32 |
+-----------+
1 row in set (0.00 sec)
mysql> CREATE TABLE `t_interface` (
-> `interfacer_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增id',
-> `user_id` int(11) NOT NULL COMMENT '用戶id(部門接口人)',
-> `department_id` int(11) NOT NULL COMMENT '部門id',
-> `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新時間',
-> `remark` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
-> `is_deleted` tinyint(4) NOT NULL DEFAULT '0' COMMENT '是否刪除(0未刪除 1已刪除)',
-> PRIMARY KEY (`interfacer_id`) USING BTREE,
-> KEY `user_id` (`user_id`) USING BTREE,
-> KEY `department_id` (`department_id`) USING BTREE
-> ) ENGINE=InnoDB AUTO_INCREMENT=1162 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT;
INSERT INTO `t_interface`
Query OK, 0 rows affected, 7 warnings (0.04 sec)
mysql> INSERT INTO `t_interface`
-> (`user_id`, `department_id`, `update_time`, `is_deleted`) VALUES (9, 18, null, 0);
ERROR 1048 (23000): Column 'update_time' cannot be null
在MySQL 8.0.32中,INSERT 語句也報錯了。
3、在MySQL 5.7.30中進行復現
mysql> SELECT version();
+------------+
| version() |
+------------+
| 5.7.30-log |
+------------+
1 row in set (0.00 sec)
mysql> CREATE TABLE `t_interface` (
-> `interfacer_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增id',
-> `user_id` int(11) NOT NULL COMMENT '用戶id(部門接口人)',
-> `department_id` int(11) NOT NULL COMMENT '部門id',
-> `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新時間',
-> `remark` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
-> `is_deleted` tinyint(4) NOT NULL DEFAULT '0' COMMENT '是否刪除(0未刪除 1已刪除)',
-> PRIMARY KEY (`interfacer_id`) USING BTREE,
-> KEY `user_id` (`user_id`) USING BTREE,
-> KEY `department_id` (`department_id`) USING BTREE
-> ) ENGINE=InnoDB AUTO_INCREMENT=1162 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT;
Query OK, 0 rows affected (0.01 sec)
mysql> INSERT INTO `t_interface`
-> (`user_id`, `department_id`, `update_time`, `is_deleted`) VALUES (9, 18, null, 0);
Query OK, 1 row affected (0.00 sec)
mysql> SELECT * from t_interface;
+---------------+---------+---------------+---------------------+--------+------------+
| interfacer_id | user_id | department_id | update_time | remark | is_deleted |
+---------------+---------+---------------+---------------------+--------+------------+
| 1162 | 9 | 18 | 2025-07-08 10:34:43 | NULL | 0 |
+---------------+---------+---------------+---------------------+--------+------------+
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相關的系統參數
mysql> SELECT version();
+------------+
| version() |
+------------+
| 5.7.30-log |
+------------+
1 row in set (0.00 sec)
mysql>SHOW variables LIKE '%timestamp%';
+---------------------------------+-------------------+
| Variable_name | Value |
+---------------------------------+-------------------+
| explicit_defaults_for_timestamp | OFF |
| log_timestamps | SYSTEM |
| timestamp | 1751270610.230160 |
+---------------------------------+-------------------+
3 rows in set (0.01 sec)
mysql> SELECT VERSION();
+-----------+
| VERSION() |
+-----------+
| 8.0.32 |
+-----------+
1 row in set (0.00 sec)
mysql> SHOW variables like '%timestamp%';
+---------------------------------+-------------------+
| Variable_name | Value |
+---------------------------------+-------------------+
| explicit_defaults_for_timestamp | ON |
| log_timestamps | SYSTEM |
| original_commit_timestamp | 36028797018963968 |
| timestamp | 1751270143.113409 |
+---------------------------------+-------------------+
4 rows in set (0.01 sec)
greatsql>SELECT version();
+-----------+
| version() |
+-----------+
| 8.0.32-26 |
+-----------+
1 row in set (0.00 sec)
greatsql>SHOW variables like '%timestamp%';
+---------------------------------+-------------------+
| Variable_name | Value |
+---------------------------------+-------------------+
| explicit_defaults_for_timestamp | ON |
| log_timestamps | SYSTEM |
| original_commit_timestamp | 36028797018963968 |
| timestamp | 1751271661.160386 |
+---------------------------------+-------------------+
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
greatsql>INSERT INTO `t_interface`
-> (`user_id`, `department_id`, `update_time`, `is_deleted`) VALUES (9, 18, null, 0);
ERROR 1048 (23000): Column 'update_time' cannot be null
greatsql>set explicit_defaults_for_timestamp=OFF;
Query OK, 0 rows affected, 1 warning (0.00 sec)
greatsql>INSERT INTO `t_interface` (`user_id`, `department_id`, `update_time`, `is_deleted`) VALUES (9, 18, null, 0);
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列的處理方式沒有區別。
greatsql>SET explicit_defaults_for_timestamp=OFF;
Query OK, 0 rows affected, 1 warning (0.00 sec)
greatsql>CREATE TABLE t1( time1 timestamp, time2 timestamp null, time3 timestamp default '2025-01-01 00:00:00', name varchar(100) );
Query OK, 0 rows affected (0.03 sec)
greatsql>INSERT INTO t1 (`time1`, `time2`, `time3`, name) VALUES (null, null, null, 't1');
Query OK, 1 row affected (0.02 sec)
greatsql>SELECT * FROM t1;
+---------------------+-------+---------------------+------+
| time1 | time2 | time3 | name |
+---------------------+-------+---------------------+------+
| 2025-07-08 13:53:58 | NULL | 2025-07-08 13:53:58 | t1 |
+---------------------+-------+---------------------+------+
1 row in set (0.00 sec)
greatsql>SHOW CREATE TABLE t1\G
*************************** 1. row ***************************
Table: t1
Create Table: CREATE TABLE `t1` (
`time1` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`time2` timestamp NULL DEFAULT NULL,
`time3` timestamp NOT NULL DEFAULT '2025-01-01 00:00:00',
`name` varchar(100) COLLATE utf8mb4_general_ci DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
1 row in set (0.00 sec)
greatsql>SET explicit_defaults_for_timestamp=ON;
Query OK, 0 rows affected (0.00 sec)
greatsql>CREATE TABLE t2( time1 timestamp, time2 timestamp null, time3 timestamp default '2025-01-01 00:00:00', name varchar(100) );
Query OK, 0 rows affected (0.02 sec)
greatsql>INSERT INTO t2 (`time1`, `time2`, `time3`, name) VALUES (null, null, null, 't2');
Query OK, 1 row affected (0.01 sec)
greatsql>SELECT * FROM t2;
+-------+-------+-------+------+
| time1 | time2 | time3 | name |
+-------+-------+-------+------+
| NULL | NULL | NULL | t2 |
+-------+-------+-------+------+
1 row in set (0.00 sec)
greatsql>SHOW CREATE TABLE t2\G
*************************** 1. row ***************************
Table: t2
Create Table: CREATE TABLE `t2` (
`time1` timestamp NULL DEFAULT NULL,
`time2` timestamp NULL DEFAULT NULL,
`time3` timestamp NULL DEFAULT '2025-01-01 00:00:00',
`name` varchar(100) COLLATE utf8mb4_general_ci DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
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。
INSERT INTO `t_interface`
(`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
Enjoy GreatSQL ??
關于 GreatSQL
GreatSQL是適用于金融級應用的國內自主開源數據庫,具備高性能、高可靠、高易用性、高安全等多個核心特性,可以作為MySQL或Percona Server的可選替換,用于線上生產環境,且完全免費并兼容MySQL或Percona Server。
相關鏈接: GreatSQL社區 Gitee GitHub Bilibili
GreatSQL社區:
社區博客有獎征稿詳情:https://greatsql.cn/thread-100-1-1.html

技術交流群:
微信:掃碼添加
GreatSQL社區助手微信好友,發送驗證信息加群。


浙公網安備 33010602011771號