一次「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
  • 联合索引:中间断列 = 后面全废
  • 前导列一致时:索引越短越容易被选中
相关推荐
Leon-Ning Liu37 分钟前
Oracle UNDO表空间文件误删除故障恢复
数据库·oracle
2301_776508721 小时前
用Python生成艺术:分形与算法绘图
jvm·数据库·python
cxr8281 小时前
PaperclipAI 组织关系与智能体协作指南
数据库·人工智能·架构·ai智能体·openclaw
@insist1232 小时前
数据库系统工程师-Armstrong 公理系统:函数依赖推理与候选码求解核心方法论(重点)
数据库·软考·软件设计师·软件水平考试
山峰哥2 小时前
查询优化案例:从慢查询到闪电般的查询速度
数据库·sql·性能优化·编辑器·深度优先
杨云龙UP2 小时前
Oracle ASM磁盘组空间分配与冗余理解
linux·运维·数据库·sql·oracle
微学AI3 小时前
一款数据库SQL防火墙:可以拦截99.99%,可以阻止恶意SQL
数据库·sql
2401_884563243 小时前
Python Lambda(匿名函数):简洁之道
jvm·数据库·python
haixingtianxinghai4 小时前
Redis真的是单线程吗?
数据库·redis·缓存
FirstFrost --sy4 小时前
MySQL复合查询
数据库·mysql