记录MySQL今天又一个新的问题:
场景:nodejs后台+容器部署
问题原因:纯属好心办坏事,由于考虑了时区(现在看来纯属多余),在写入时间时使用了time_str.toLocaleString("chinese", { timeZone: timeZone })
方法进行转换,并将该结果写入数据库。之所以出现问题,是因为在本地测试中完全没有问题,但当部署后,写入时间就报了错误。
当时使用的格式化方法是:
// 获取中国时区的时间戳
function getUTC8TimeStamp(time?: string) {
let time_str = time ? new Date(time) : new Date() // 时间
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone // 获取时区
let temp = time_str.toLocaleString("chinese", { timeZone: timeZone }) // 时区转换
return temp.replace(/\//g,"-"); // /替换为-
}
报错如下:
Error: update `ac_table_id` set `expire_at` = '11/9/2023, 10:38:12 AM', `rotate` = 1 where `client_id` = 'xxx' - Incorrect datetime value: '11/9/2023, 10:38:12 AM' for column 'expire_at' at row 23
at Packet.asError (/app/node_modules/mysql2/lib/packets/packet.js:728:17)
at Query.execute (/app/node_modules/mysql2/lib/commands/command.js:29:26)
at Connection.handlePacket (/app/node_modules/mysql2/lib/connection.js:478:34)
at PacketParser.onPacket (/app/node_modules/mysql2/lib/connection.js:97:12)
at PacketParser.executeStart (/app/node_modules/mysql2/lib/packet_parser.js:75:16)
at Socket.<anonymous> (/app/node_modules/mysql2/lib/connection.js:104:25)
at Socket.emit (node:events:512:28)
at Socket.emit (node:domain:489:12)
at addChunk (node:internal/streams/readable:324:12)
at readableAddChunk (node:internal/streams/readable:297:9) {
code: 'ER_TRUNCATED_WRONG_VALUE',
errno: 1292,
sqlState: '22007',
sqlMessage: "Incorrect datetime value: '11/9/2023, 10:38:12 AM' for column 'expire_at' at row 23",
sql: "update `ac_table_id` set `expire_at` = '11/9/2023, 10:38:12 AM', `rotate` = 1 where `test_id` = 'xxx'"
很显然,通过上述方法转换后的时间,在服务器上变成了11/9/2023, 10:38:12 AM
格式,这里报错就是expire_at
格式不正确导致的。
题外话:使用上述方式格式化的时间,在本地调试过程中打印出来全部始终都是2023-11-9 10:38:12
,不清楚为什么部署到服务器以后就会变得不一样。
经过一番调试,最后结论是:要么直接返回time_str
,或者使用moment
格式,返回moment(time_str).format('YYYY-MM-DD HH:mm:ss')
,都可以在数据库中成功写入。
比较
在解决问题的过程中,我们主要对数据库中展示的时间、从数据库获取到的时间和使用上述两种方式转换后的时间进行比较,最后得出的结论。
1. 数据库中显示的时间:
2.通过sql从数据库中查出来的:
可见,对于MySQL数据库,我们所看到的展示给我们的时间格式是YYYY-MM-DD HH:mm:ss
,是数据库根据当地时区进行了转换了展示给我们看的时间,而真实存储的是UTC时间。
3.使用time_str.toLocaleString("chinese", { timeZone: timeZone })
方法转换后的时间
其实问题点就出在这个地方,通过这个方法转换后的时间,在本地环境中是YYYY-MM-DD HH:mm:ss
这种格式,而在服务端就变成了11/9/2023, 10:38:12 AM
这种格式。
4.moment(time_str).format('YYYY-MM-DD HH:mm:ss')
转换后
很显然,它是YYYY-MM-DD HH:mm:ss
这种格式。
向数据库中写入数据
- 写入
YYYY-MM-DD HH:mm:ss
格式。成功 - 写入
11/9/2023, 10:38:12 AM
。报错,错误如上 - 写入
new Date()
格式。成功 - 写入
2023-11-13T08:28:43.000Z
格式。报错,错误如上
综上,对于数据库timestamp
格式的字段,来自前端通过各种方式格式化后的时间,服务端可以通过两种方式成功写入:
new Date(time)
- 使用
moment(time_str).format('YYYY-MM-DD HH:mm:ss')
转换
new Date()比较
同样的方法new Date()
,在服务端和浏览器的不同表现:
所以,如果遇到时间格式的问题,应该在浏览器和服务端各自分别测试。
结论:
- 向服务器提交时间,一律使用
new Date()
格式。 - 数据库会根据数据库当时所在的地区的时区,自动对时间做转换,只是展示出来的是经过格式化后的时间。所以,向数据库中写入时间,不需要进行格式转换,写入
new Date()
即可。 - 数据库存储时间,可以使用
timestamp
类型,并且写入时间不需要做转换。 - 由于数据库存储的时间是
timestamp
类型,所以,无论我们看到的是什么格式,通过sql从数据库中获取到的时间,都是2023-11-13T08:28:43.000Z
这种格式,前端使用的时候需要进行时区转换,如使用moment()
的方法等。 - 像需要通过
Intl.DateTimeFormat().resolvedOptions().timeZone
这种方式获取时区的场景,只适用于需要在两个时区之间进行转换的场景,如东八区到东七区。并且,通过time_str.toLocaleString("chinese", { timeZone: timeZone })
这种方式获取到字符串后,写入数据库还需要使用new Date()
方法进行转换成timestamp格式。