一次「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
  • 联合索引:中间断列 = 后面全废
  • 前导列一致时:索引越短越容易被选中
相关推荐
逻极8 小时前
数据分析项目:Pandas + SQLAlchemy,从数据库到DataFrame的丝滑实战
python·mysql·数据分析·pandas·sqlalchemy
tqs_123458 小时前
事务消息实现
数据库·java-rocketmq
luoluoal8 小时前
基于python的英汉电子词典软件(源码+文档)
python·mysql·django·毕业设计·源码
言之。8 小时前
DDIA第四章 数据库存储引擎与索引技术深度解析
数据库·ddia
山沐与山8 小时前
【Redis】读写锁实战详解:读多写少场景的性能优化利器
数据库·redis·性能优化
memgLIFE8 小时前
SQL 优化方法详解(1)
java·数据库·sql
UCH1HA8 小时前
MySQL主从复制与读写分离
linux·mysql·集群
小宇的天下8 小时前
Calibre 3Dstack--每日一个命令day 6 [process和export layout](3-6)
java·前端·数据库
自燃人~9 小时前
为什么MySQL用b+不用B数
数据库·mysql
做cv的小昊9 小时前
【TJU】信息检索与分析课程笔记和练习(6)英文数据库检索—web of science
大数据·数据库·笔记·学习·全文检索