一次「varchar 时间字段」导致的 MySQL 索引选择问题实战分析

背景

在不允许修改 索引结构字段类型 的前提下,优化一个千万级数据表的查询性能。


一、问题背景

某业务表按 城市 + 小区 + 月份 维度存储统计数据,字段 data_timevarchar 存储月份(如 2025-01)。

查询目标

查询 2025 年某小区的月度数据


二、精简后的表结构(仅保留相关字段)

sql 复制代码
CREATE TABLE t_zhuge_project (
  id INT PRIMARY KEY AUTO_INCREMENT,
  data_time VARCHAR(12),      -- 月份,如 2025-01
  city_name VARCHAR(100),
  district_name VARCHAR(64),
  project_name VARCHAR(64),
  date_level VARCHAR(64),     -- month / year

  KEY index_sort (data_time, city_name),
  KEY index_mom_near (data_time, city_name, district_name, project_name, date_level),
  KEY index_data_time (data_time(7))
);

三、最初的 SQL:范围查询(未走索引)

sql 复制代码
SELECT *
FROM t_zhuge_project
WHERE data_time >= '2025-01'
  AND data_time <= '2025-12'
  AND city_name = '上海'
  AND project_name = '露香苑'
  AND date_level = 'month';

执行计划关键结果

复制代码
type: ALL
rows: 12224888
Extra: Using where

问题现象

  • 全表扫描
  • 扫描行数 1200 万+
  • 查询性能极差

四、为什么 varchar 的 BETWEEN 会失效?

核心原因

data_timevarchar 类型BETWEEN 变成了字符串范围比较

经过编码之后,MySQL 无法保证:'2025-01' ~ '2025-12' 在字符串排序规则下等价于"连续时间区间"。

优化器判断

👉 使用索引收益不确定,全表扫描更稳定


五、优化手段:用 IN 替代 BETWEEN

不能改字段类型、不改索引 的前提下,将范围查询改为 等值枚举

sql 复制代码
SELECT *
FROM t_zhuge_project
WHERE data_time IN (
  '2025-01','2025-02','2025-03','2025-04','2025-05','2025-06',
  '2025-07','2025-08','2025-09','2025-10','2025-11','2025-12'
)
AND city_name = '上海'
AND project_name = '露香苑'
AND date_level = 'month';

六、优化后的执行计划

复制代码
type: range
key: index_sort
rows: 342
Extra: Using index condition; Using where

优化效果

指标 优化前 优化后
扫描行数 12,224,888 342
执行方式 全表扫描 索引扫描
性能 极慢 毫秒级

七、为什么走了索引,却没走 index_mom_near

这是最容易被误解、但最关键的一点


1️⃣ 联合索引的最左前缀原则被「中断」

index_mom_near 结构:

text 复制代码
(data_time, city_name, district_name, project_name, date_level)

WHERE 条件中 缺少 district_name,即不满足最左匹配原则:

text 复制代码
data_time ✅
city_name ✅
district_name ❌
project_name ❌(失效)
date_level ❌(失效)

👉 一旦中断,后续列全部无法使用


2️⃣ 实际可用列对比

索引 实际能用的列
index_sort data_time + city_name
index_mom_near data_time + city_name

3️⃣ MySQL 为什么选 index_sort?

原因很现实:

  • index_sort 索引更短
  • B+Tree 层级更浅
  • IO 成本更低

👉 在"可用列一样"的情况下,MySQL 一定选更瘦的索引


4️⃣ ICP(Index Condition Pushdown)加成

执行计划中出现:

复制代码
Using index condition

说明:

  • MySQL 在索引层就做了条件过滤
  • 进一步降低了回表成本
  • index_sort 的性价比更高

八、在不能改索引的情况下,可选方案总结

✅ 方案 1:IN + 索引收敛(已采用)

  • 强烈推荐
  • 可控、可解释、无副作用

🧠 方案 2:子查询先用索引收敛,再精确过滤

sql 复制代码
SELECT *
FROM t_zhuge_project t
JOIN (
  SELECT id
  FROM t_zhuge_project
  WHERE data_time IN (...)
    AND city_name = '上海'
) s ON t.id = s.id
WHERE t.project_name = '露香苑'
  AND t.date_level = 'month';

九、总结(可以直接背下来)

MySQL 选索引,不看"业务合理性",只看"IO 成本"

  • varchar 时间字段:IN > BETWEEN
  • 联合索引:中间断列 = 后面全废
  • 前导列一致时:索引越短越容易被选中
相关推荐
剩下了什么15 小时前
MySQL JSON_SET() 函数
数据库·mysql·json
山峰哥16 小时前
数据库工程与SQL调优——从索引策略到查询优化的深度实践
数据库·sql·性能优化·编辑器
较劲男子汉16 小时前
CANN Runtime零拷贝传输技术源码实战 彻底打通Host与Device的数据传输壁垒
运维·服务器·数据库·cann
java搬砖工-苤-初心不变16 小时前
MySQL 主从复制配置完全指南:从原理到实践
数据库·mysql
WangYaolove131417 小时前
基于python的在线水果销售系统(源码+文档)
python·mysql·django·毕业设计·源码
山岚的运维笔记18 小时前
SQL Server笔记 -- 第18章:Views
数据库·笔记·sql·microsoft·sqlserver
roman_日积跬步-终至千里19 小时前
【LangGraph4j】LangGraph4j 核心概念与图编排原理
java·服务器·数据库
汇智信科19 小时前
打破信息孤岛,重构企业效率:汇智信科企业信息系统一体化运营平台
数据库·重构
野犬寒鸦19 小时前
从零起步学习并发编程 || 第六章:ReentrantLock与synchronized 的辨析及运用
java·服务器·数据库·后端·学习·算法
霖霖总总19 小时前
[小技巧66]当自增主键耗尽:MySQL 主键溢出问题深度解析与雪花算法替代方案
mysql·算法