MySQL 时间范围查询数据不准确案例
问题
业务小伙伴遇到时间范围查询数据不准确的问题。具体如下:
请教个问题
expiry_Date 是date类型
select count(1) from tablename where expiry_Date >= '2024-08-20T00:00'
and expiry_Date <= '2024-08-20T23:59:59.999999999';
这个sql查询 会把 2024-08-21号的数据也查出来
是因为我们没设置时区的问题?
咋一看,确实诡异啊!
明明只查1天的count(1)
,你却给我2天的,作孽啊。。救救我~
排查
将查询条件中=
去掉,改成 expiry_Date < '2024-08-20T23:59:59.999999999'
数据就是准确的。好像和日期精度有关。
从表结构开始排查~
#mysql5.7
CREATE TABLE `tablename` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
......
`expiry_Date` date DEFAULT NULL COMMENT '到期日',
PRIMARY KEY (`id`),
KEY `idx_expire_date` (`expiry_Date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='xxx'
结论
从表结构发现:时间字段类型和传入时间格式不一致。
expiry_Date
字段是 Date
类型的,该类型标准格式是 YYYY-MM-DD
。
查某天的,精确到天就可以了,比如
select count(1) from table_name where expiry_Date = '2024-08-20'
;
所以,该问题本质是传入了 DateTime 类型数据与Date 类型数据做比较,而传入数据 ‘2024-08-20T23:59:59.999999999’ 是精确到秒的小数后 9 位的,做时间范围比较时,MySQL 是将时间值四舍五入后作比较的。与时区无关。
可参考官方文档:https://dev.mysql.com/doc/refman/5.7/en/fractional-seconds.html 。
MySQL has fractional seconds support for TIME, DATETIME, and TIMESTAMP values, with up to microseconds (6 digits) precision.
Inserting a TIME, DATE, or TIMESTAMP value with a fractional seconds part into a column of the same type but having fewer fractional digits results in rounding.
测试复现:datetime 与 date 类型作比较
#mysql5.7
#datetime 类型与 date 类型做比较
mysql> desc zlz_date;
+-------+-----------+------+-----+-------------------+-----------------------------+
| Field | Type | Null | Key | Default | Extra |
+-------+-----------+------+-----+-------------------+-----------------------------+
| col1 | date | YES | | NULL | |
| col2 | datetime | YES | | NULL | |
| col3 | timestamp | NO | | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
+-------+-----------+------+-----+-------------------+-----------------------------+
3 rows in set (0.00 sec)
mysql> select * from zlz_date;
+------------+---------------------+---------------------+
| col1 | col2 | col3 |
+------------+---------------------+---------------------+
| 2024-08-21 | 2024-08-21 00:00:00 | 2024-08-21 00:00:00 |
| 2024-08-20 | 2024-08-20 00:00:01 | 2024-08-20 00:00:01 |
| 2024-08-20 | 2024-08-20 23:59:59 | 2024-08-20 23:59:59 |
+------------+---------------------+---------------------+
3 rows in set (0.00 sec)
# datetime 数据精确到秒的小数后6位
mysql> select * from zlz_date where col1>='2024-08-20 00:00:00' and col1 <='2024-08-20 23:59:59.999999';
+------------+---------------------+---------------------+
| col1 | col2 | col3 |
+------------+---------------------+---------------------+
| 2024-08-20 | 2024-08-20 00:00:01 | 2024-08-20 00:00:01 |
| 2024-08-20 | 2024-08-20 23:59:59 | 2024-08-20 23:59:59 |
+------------+---------------------+---------------------+
2 rows in set (0.00 sec)
# datetime 数据精确到秒的小数后9位
mysql> select * from zlz_date where col1>='2024-08-20 00:00:00' and col1 <='2024-08-20 23:59:59.999999999';
+------------+---------------------+---------------------+
| col1 | col2 | col3 |
+------------+---------------------+---------------------+
| 2024-08-21 | 2024-08-21 00:00:00 | 2024-08-21 00:00:00 |
| 2024-08-20 | 2024-08-20 00:00:01 | 2024-08-20 00:00:01 |
| 2024-08-20 | 2024-08-20 23:59:59 | 2024-08-20 23:59:59 |
+------------+---------------------+---------------------+
3 rows in set (0.00 sec)
总结
1、时间类型比较避坑指南
- 日期比较大小时,需要保证比较的两个日期都是同一类型的,否则会出现错误。
- 日期比较大小时,需要注意日期格式的一致性,否则会出现比较结果不准确的情况。
- 日期比较大小时,需要考虑时区的影响,尤其是在跨时区的应用中。
- MySQL 5.6 后,
TIME, DATETIME, TIMESTAMP
值提供小数秒支持,精度高达秒的小数后6位。 - 将带有秒小数部分的时间类型做比较时,小数位数较少的列中会导致四舍五入。
2、MySQL 时间类型怎么选
有关 date、datetime、time、year、timestamp
的支持范围和格式就不在老生常谈了。详见:https://dev.mysql.com/doc/refman/5.7/en/date-and-time-type-syntax.html 。
直接说结论:
datetime
类型格式是YYYY-MM-DD HH:MM:SS
,需要精确到时分秒的数据,建议直接上datetime
类型。- 从
MySQL 5.6
版本开始,TIMESTAMP
类型也能支持毫秒。与DATETIME
不同的是,若带有毫秒时,TIMESTAMP
类型占用 7 个字节,而DATETIME
无论是否存储毫秒信息,都占用 8 个字节。 - 目前的趋势下,建议所有的新表上线,可以直接用
datetime
替代timestamp
类型。涉及到跨时区的问题,放到应用层来做。
时间字段设置建表举个栗子:
CREATE TABLE `student_info` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
......
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '记录创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '记录更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='学生信息表'
3、DATA/DATATIME 类型可以做比较吗
可以。
- 当传入
DATA
类型时间与DATATIME
类型时间进行比较的时候,DATA
类型默认转化成更长的DATATIME
类型时间,比如我传入一个DATA
型时间为:2018/07/03
,在与DATATIME
类型时间比较的时候,应该变成:2018/07/03 00:00:00
,然后再去比较。 - 当传入
DATATIME
类型时间与较短的DATA
类型时间进行比较的时候,若带时间值带有秒后面的小数位,带有小数位的列会做四舍五入后再比较。
测试 date 与 datetime 类型做比较
mysql> desc zlz_date;
+-------+-----------+------+-----+-------------------+-----------------------------+
| Field | Type | Null | Key | Default | Extra |
+-------+-----------+------+-----+-------------------+-----------------------------+
| col1 | date | YES | | NULL | |
| col2 | datetime | YES | | NULL | |
| col3 | timestamp | NO | | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
+-------+-----------+------+-----+-------------------+-----------------------------+
3 rows in set (0.00 sec)
mysql> select * from zlz_date;
+------------+---------------------+---------------------+
| col1 | col2 | col3 |
+------------+---------------------+---------------------+
| 2024-08-21 | 2024-08-21 00:00:00 | 2024-08-21 00:00:00 |
| 2024-08-20 | 2024-08-20 00:00:01 | 2024-08-20 00:00:01 |
| 2024-08-20 | 2024-08-20 23:59:59 | 2024-08-20 23:59:59 |
| 2024-08-21 | 2024-08-21 00:00:01 | 2024-08-21 00:00:01 |
+------------+---------------------+---------------------+
4 rows in set (0.00 sec)
mysql> select * from zlz_date where col2<='2024-08-21';
+------------+---------------------+---------------------+
| col1 | col2 | col3 |
+------------+---------------------+---------------------+
| 2024-08-21 | 2024-08-21 00:00:00 | 2024-08-21 00:00:00 |
| 2024-08-20 | 2024-08-20 00:00:01 | 2024-08-20 00:00:01 |
| 2024-08-20 | 2024-08-20 23:59:59 | 2024-08-20 23:59:59 |
+------------+---------------------+---------------------+
3 rows in set (0.00 sec)
mysql> select * from zlz_date where col2>'2024-08-21';
+------------+---------------------+---------------------+
| col1 | col2 | col3 |
+------------+---------------------+---------------------+
| 2024-08-21 | 2024-08-21 00:00:01 | 2024-08-21 00:00:01 |
+------------+---------------------+---------------------+
1 row in set (0.00 sec)
参考文档
https://dev.mysql.com/doc/refman/5.7/en/fractional-seconds.html
PS:转载请注明出处,谢谢。