问题
现有如下sql,在满足条件数据不足limit限制时超时
sql
select * from `tms_order` where create_time<'2024-03-17' and biz_type in ('DO','DO_COLD') order by id asc limit 1000
大致表结构如下:表数据2000万
sql
CREATE TABLE `tms_order` (
`id` bigint(19) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id',
`biz_id` varchar(100) NOT NULL COMMENT '业务单号',
`biz_type` varchar(30) NOT NULL COMMENT '单据类型,如常温出库单、退货单 冷链 门店 鲲鹏订单 调拨单等',
......
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_by` varchar(50) NOT NULL DEFAULT 'wms' COMMENT '更新者',
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`is_delete` int(11) NOT NULL DEFAULT '0' COMMENT '是否删除 0-否 1-是',
`
`predict_package_count` int(11) DEFAULT '0' COMMENT 'WMS转DO预计包裹数',
PRIMARY KEY (`id`),
UNIQUE KEY `uniq_biz_id_type` (`biz_id`, `biz_type`),
KEY `idx_parent_id` (`parent_id`),
KEY `idx_create_time` (`create_time`)
) ENGINE = InnoDB AUTO_INCREMENT = 41025046 DEFAULT CHARSET = utf8mb4 COMMENT = '运输订单信息表'
分析
当查询 tms_order 表(2000万数据)且没有符合条件的数据时发生超时,主要原因是索引选择不当导致需要扫描大量数据才能确认无结果。具体分析如下:
1. 查询条件与现有索引不匹配
- 条件 :
create_time < '2024-03-17'和biz_type IN ('DO','DO_COLD') - 排序 :
ORDER BY id ASC - 现有索引 :
idx_create_time(只包含create_time),没有biz_type的单独索引,也没有覆盖id的复合索引。
2. 执行计划推测
优化器可能选择使用 idx_create_time 索引:
- 通过索引快速定位所有
create_time < '2024-03-17'的记录(可能范围极大,比如覆盖了大部分数据)。 - 对每条索引对应的行进行回表 (访问聚簇索引获取完整行数据),然后检查
biz_type是否在集合中。 - 由于排序要求
ORDER BY id,且id与create_time无直接顺序关系,MySQL 需要将满足条件的所有行(如果有)暂存,然后进行文件排序(filesort),最后取前1000条。
3. 为何"查不到数据"比"查到数据"更慢?
- 有数据时 :如果满足
biz_type条件的记录较多,可能在扫描idx_create_time的早期就找到了1000条,即使需要回表和排序,也能通过LIMIT 1000提前终止扫描(但实际需要根据优化器策略判断是否提前终止,某些情况下仍需获取全部候选行后再排序)。 - 无数据时 :没有任何记录满足
biz_type条件,但idx_create_time的范围扫描仍需执行完毕(直到索引末尾),并且每条索引都要回表检查biz_type。若create_time < '2024-03-17'覆盖了例如1500万行,则需要1500万次回表操作(随机I/O),导致巨大的 I/O 开销和 CPU 消耗,最终超时。
4. 其他潜在原因
- 锁竞争:如果表上有频繁的写操作,查询可能被阻塞,但通常与是否有结果无关。
- 临时表/文件排序:即使无数据,如果优化器预估错误,可能仍会创建临时表,但无数据时开销较小。
优化建议
-
创建复合索引 ,同时覆盖查询和排序字段,避免回表:
sql-- 索引1:以 create_time 为前列,包含 biz_type 和 id(覆盖索引) ALTER TABLE tms_order ADD INDEX idx_create_time_biz_type_id (create_time, biz_type, id); -- 或者根据实际数据分布,优先过滤性强的字段: ALTER TABLE tms_order ADD INDEX idx_biz_type_create_time_id (biz_type, create_time, id);这样查询可直接在索引上完成条件过滤和排序,无需回表,且能快速定位到无数据的边界。
-
如果数据量极大 ,考虑按时间分区(如按
create_time分区),查询时仅扫描相关分区。 -
分析数据分布 ,若
biz_type取值很少,可考虑改用 EXISTS 或联合查询等方式。
通过以上优化,查询可以在索引遍历过程中快速判断无数据,避免大量回表,从而防止超时。