一次「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
  • 联合索引:中间断列 = 后面全废
  • 前导列一致时:索引越短越容易被选中
相关推荐
广州灵眸科技有限公司9 小时前
xfce桌面触摸校准:基于灵眸科技EASY-EAl-Orin-Nano
数据库·windows·科技
Cloud云卷云舒12 小时前
云卷云舒:从Oracle/MySQL迁移到HaishanDB:迁移评估工具核心技术拆解
mysql·oracle·ai-native·haishandb·信创数据库替换
憧憬成为web高手12 小时前
皮卡丘靶场速通--sql 2
数据库·sql·mybatis
段一凡-华北理工大学12 小时前
向量数据库实战:选型、调优与落地~系列文章12:文本分块策略实战:chunk_size 怎么选?重叠多少?
开发语言·数据库·后端·oracle·rust·工业智能体·高炉智能化
oradh14 小时前
Oracle XTTS实现跨版本迁移和升级(Oracle 11g单库升级至19C RAC集群)
数据库·oracle·11g升级19c·xtts跨版本迁移和升级
用户713335851562415 小时前
MySQL - 事务、redo/undo 日志与 MVCC
mysql
z1234567898615 小时前
2026最新两款AI编程工具深度对比实测
java·数据库·ai编程
程序猿DD15 小时前
一个 API Key,统一调用大模型、生图和联网搜索
数据库·网关
java_logo15 小时前
Docker Compose 部署 ClickHouse:快速搭建高性能列式数据库
数据库·clickhouse·docker·列式数据库·轩辕镜像·高性能数据库·clickhouse部署教程
用户713335851562416 小时前
MySQL - InnoDB 的 Buffer Pool
mysql