一次「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
  • 联合索引:中间断列 = 后面全废
  • 前导列一致时:索引越短越容易被选中
相关推荐
0xDevNull13 小时前
MySQL数据冷热分离详解
后端·mysql
科技小花13 小时前
数据治理平台架构演进观察:AI原生设计如何重构企业数据管理范式
数据库·重构·架构·数据治理·ai-native·ai原生
一江寒逸13 小时前
零基础从入门到精通MySQL(中篇):进阶篇——吃透多表查询、事务核心与高级特性,搞定复杂业务SQL
数据库·sql·mysql
D4c-lovetrain13 小时前
linux个人心得22 (mysql)
数据库·mysql
阿里小阿希14 小时前
CentOS7 PostgreSQL 9.2 升级到 15 完整教程
数据库·postgresql
荒川之神14 小时前
Oracle 数据仓库雪花模型设计(完整实战方案)
数据库·数据仓库·oracle
做个文艺程序员14 小时前
MySQL安全加固十大硬核操作
数据库·mysql·安全
不吃香菜学java14 小时前
Redis简单应用
数据库·spring boot·tomcat·maven
一个天蝎座 白勺 程序猿15 小时前
Apache IoTDB(15):IoTDB查询写回(INTO子句)深度解析——从语法到实战的ETL全链路指南
数据库·apache·etl·iotdb
不知名的老吴15 小时前
Redis的延迟瓶颈:TCP栈开销无法避免
数据库·redis·缓存