|
一张 5000 万行的亿数订单表,走主键查询,据秒级查MySQL 单实例能压到 1 毫秒以内。亿数走二级索引加回表,据秒级查大概 3 到 5 毫秒。亿数但如果你的据秒级查查询条件没建对索引,或者用了函数、亿数隐式类型转换、据秒级查前导通配符,亿数那对不起,据秒级查全表扫描,亿数几十分钟都跑不完。据秒级查 我见过一个真实案例。亿数某电商平台的据秒级查订单表,数据量 1.2 亿行,亿数查询最近 7 天某个用户的订单列表,SQL 写的是 WHERE DATE(create_time) > '2024-01-01' AND user_id = 12345。create_time 上有索引,user_id 上也有索引,但 MySQL 选了 user_id 索引,然后回表过滤日期。问题是这个用户有 3000 多笔订单,回表 3000 次,每次随机 IO,耗时 800 毫秒。后来改成联合索引 (user_id, create_time),查询直接降到 2 毫秒。差别在哪?覆盖索引。查询只需要 user_id 和 create_time 两个字段,联合索引里全有,不需要回表。 索引不是越多越好,但关键查询必须有覆盖索引 上亿数据量的表,B+ 树高度大概 4 到 5 层。主键查询走聚簇索引,4 次页读取,每次页在内存里的话就是微秒级。问题从来不在主键查询上。真正慢的是范围查询、排序、分组、多条件组合。这些操作一旦不能靠索引完成,MySQL 就会启用临时表或者文件排序,磁盘 IO 直接爆炸。 我的经验是:单表超过 2000 万行,就不要指望通用索引能解决所有查询。你得针对 Top 20 的慢查询逐一设计覆盖索引。比如 WHERE status = 1 AND create_time > '2024-06-01' ORDER BY id DESC LIMIT 100,建 (status, create_time, id) 联合索引,id 已经在二级索引里了,因为 InnoDB 二级索引叶子节点存主键值。这样排序和分页都不需要回表。 分区表能帮忙,但别指望它解决所有问题 有个做物联网的朋友,设备上报数据表 8 亿行。按天分区,每天 200 万行左右。查询某设备某天的数据,分区裁剪后只扫一个分区,200 万行里走 (device_id, report_time) 索引,毫秒级返回。但如果查询跨 30 天,分区裁剪只能裁到 30 个分区,每个分区扫一遍,还是慢。他的做法是冷热分离:最近 7 天数据留在 MySQL,历史数据归档到 ClickHouse。MySQL 只处理热数据,单表控制在 1500 万行以内,所有查询都在 10 毫秒内完成。 查询缓存和连接池的坑 MySQL 8.0 已经移除了查询缓存。别再想着开 query_cache 了。但应用层的缓存该做还得做。Redis 缓存热点 key,比如商品详情、用户基本信息。数据库只扛穿透的流量。连接池方面,HikariCP 的 maximumPoolSize 别拍脑袋设 100。上亿数据量的库,并发查询一多,连接数暴涨,每个连接都要消耗内存和 CPU 上下文。我一般按 CPU 核数 * 2 + 磁盘数 来估,再压测微调。 一个反直觉的结论 上亿数据秒级查询,很多时候瓶颈不在 MySQL 本身。我压测过,单表 1 亿行,联合索引覆盖查询,QPS 能到 8000 以上,平均延迟 1.2 毫秒。但应用层用 ORM 框架,比如 Hibernate 或者 MyBatis 的动态 SQL,生成一堆 WHERE 1=1 AND ...,再嵌套子查询,再 N+1 查询,延迟直接飙到 200 毫秒。所以先看慢查询日志,再看执行计划,最后才去调 MySQL 参数。参数调优的收益,远不如加一个正确的联合索引。 另外,别忽视硬件。NVMe SSD 的随机读 IOPS 是 SATA SSD 的 10 倍以上。同样的索引结构,同样的数据量,换块盘,查询耗时可能从 50 毫秒降到 5 毫秒。上亿数据量,存储层别省钱。 |