时间范围查询数据不准确案例

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、时间类型比较避坑指南

  1. 日期比较大小时,需要保证比较的两个日期都是同一类型的,否则会出现错误。
  2. 日期比较大小时,需要注意日期格式的一致性,否则会出现比较结果不准确的情况。
  3. 日期比较大小时,需要考虑时区的影响,尤其是在跨时区的应用中
  4. MySQL 5.6 后,TIME, DATETIME, TIMESTAMP值提供小数秒支持,精度高达秒的小数后6位。
  5. 将带有秒小数部分的时间类型做比较时,小数位数较少的列中会导致四舍五入

2、MySQL 时间类型怎么选

有关 date、datetime、time、year、timestamp 的支持范围和格式就不在老生常谈了。详见:https://dev.mysql.com/doc/refman/5.7/en/date-and-time-type-syntax.html 。

直接说结论:

  1. datetime类型格式是 YYYY-MM-DD HH:MM:SS,需要精确到时分秒的数据,建议直接上 datetime 类型。
  2. MySQL 5.6 版本开始, TIMESTAMP 类型也能支持毫秒。DATETIME 不同的是,若带有毫秒时, TIMESTAMP 类型占用 7 个字节,而 DATETIME 无论是否存储毫秒信息,都占用 8 个字节。
  3. 目前的趋势下,建议所有的新表上线,可以直接用 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 类型可以做比较吗

可以。

  1. 当传入DATA类型时间与DATATIME类型时间进行比较的时候,DATA类型默认转化成更长的DATATIME类型时间,比如我传入一个DATA型时间为:2018/07/03,在与DATATIME类型时间比较的时候,应该变成:2018/07/03 00:00:00,然后再去比较。
  2. 当传入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:转载请注明出处,谢谢。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值