Rainman's Blog - 性能优化
type: channel
link: https://blog.x2boot.com/tag/%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96/
items: 细数MySQL千万级大表的优化策略！
items: 线上服务频繁Full GC该如何优化？
items: 索引优化的10个小技巧
items: 百亿数据如何毫秒返回？
items: 百万excel数据导入功能如何实现？（知识星球专享）
items: 用explain如何优化SQL性能？
items: 明明是同一条SQL，为什么有时候走索引a，有时候却走索引b ？
items: 微服务性能如何优化？
items: 常见 MySQL 的慢查询优化方式！
items: 如何医治一条慢SQL?

细数MySQL千万级大表的优化策略！
type: item
date: 2026-06-06T14:37:04+08:00
description: 千万级大表如何优化，这是一个很有技术含量的问题，通常我们的直觉思维都会跳转到拆分或者数据分区，在此我想做一些补充和梳理，想和大家做一些这方面的经验总结，也欢迎大家提出建议。 从一开始脑海里火光四现，到不断的自我批评，后来也参考了一些团队的经验，我整理了下面的大纲内容。既然要吃透这个问题，我们势必要回到本源，我把这个问题分为三部分：“千万级”，“大表”，“优化”，也分别对应我们在图中标识的“数据量”，“对象”和“目标”。我来逐步展开说明一下，从而给出一系列的解决方案。数据量：千万级千万级其实只是一个感官的数字，就是我们印象中的数据量大。这里我们需要把这个概念细化，因为随着业务和时间的变化，数据量也会有变化，我们应该是带着一种动态思维来审视这个指标，从而对于不同的场景我们应该有不同的处理策略。①数据量为千万级，可能达到亿级或者更高通常是一些数据流水，日志记录的业务，里面的数据随着时间的增长会逐步增多，超过千万门槛是很容易的一件事情。②数据量为千万级，是一个相对稳定的数据量如果数据量相对稳定，通常是在一些偏向于状态的数据，比如有 1000 万用户，那么这些用户的信息在表中都有相应的一行数据记录，随着业务的增长，这个量级相对是比较稳定的。③数据量为千万级，不应该有这么多的数据这种情况是我们被动发现的居多，通常发现的时候已经晚了，比如你看到一个配置表，数据量上千万；或者说一些表里的数据已经存储了很久，99% 的数据都属于过期数据或者垃圾数据。数据量是一个整体的认识，我们需要对数据做更近一层的理解，这就可以引出第二个部分的内容。对象：数据表数据操作的过程就好比数据库中存在着多条管道，这些管道中都流淌着要处理的数据，这些数据的用处和归属是不一样的。一般根据业务类型把数据分为三种：①流水型数据流水型数据是无状态的，多笔业务之间没有关联，每次业务过来的时候都会产生新的单据。比如交易流水、支付流水，只要能插入新单据就能完成业务，特点是后面的数据不依赖前面的数据，所有的数据按时间流水进入数据库。②状态型数据状态型数据是有状态的，多笔业务之间依赖于有状态的数据，而且要保证该数据的准确性，比如充值时必须要拿到原来的余额，才能支付成功。③配置型数据此类型数据数据量较小，而且结构简单，一般为静态数据，变化频率很低。至此，我们可以对整体的背景有一个认识了，如果要做优化，其实要面对的是这样的 33 的矩阵，如果要考虑表的读写比例（读多写少，读少写多...），那么就会是 33*4=24 种，显然做穷举是不显示的，而且也完全没有必要，可以针对不同的数据存储特性和业务特点来指定不同的业务策略。对此我们采取抓住重点的方式，把常见的一些优化思路梳理出来，尤其是里面的核心思想，也是我们整个优化设计的一把尺子，而难度决定了我们做这件事情的动力和风险。而对于优化方案，我想采用面向业务的维度来进行阐述。目标：优化在这个阶段，我们要说优化的方案了，总结的有点多，相对来说是比较全了。整体分为五个部分：其实我们通常所说的分库分表等方案只是其中的一小部分，如果展开之后就比较丰富了。不难理解，我们要支撑的表数据量是千万级别，相对来说是比较大了，DBA 要维护的表肯定不止一张，如何能够更好的管理，同时在业务发展中能够支撑扩展，同时保证性能，这是摆在我们面前的几座大山。我们分别来说一下这五类改进方案：规范设计业务层优化架构层优化数据库优化管理优化规范设计在此我们先提到的是规范设计，而不是其他高大上的设计方案。黑格尔说：秩序是自由的第一条件。在分工协作的工作场景中尤其重要，否则团队之间互相牵制太多，问题多多。我想提到如下的几个规范，其实只是属于开发规范的一部分内容，可以作为参考。规范的本质不是解决问题，而是有效杜绝一些潜在问题，对于千万级大表要遵守的规范，我梳理了如下的一些细则，基本可以涵盖我们常见的一些设计和使用问题。比如表的字段设计不管三七二十一，都是 varchar（500），其实是很不规范的一种实现方式，我们来展开说一下这几个规范。配置规范：MySQL 数据库默认使用 InnoDB 存储引擎。保证字符集设置统一，MySQL 数据库相关系统、数据库、表的字符集都使用 UTF8，应用程序连接、展示等可以设置字符集的地方也都统一设置为 UTF8 字符集。注：UTF8 格式是存储不了表情类数据，需要使用 UTF8MB4，可在 MySQL 字符集里面设置。在 8.0 中已经默认为 UTF8MB4，可以根据公司的业务情况进行统一或者定制化设置。MySQL 数据库的事务隔离级别默认为 RR（Repeatable-Read），建议初始化时统一设置为 RC（Read-Committed），对于 OLTP 业务更适合。数据库中的表要合理规划，控制单表数据量，对于 MySQL 数据库来说，建议单表记录数控制…

线上服务频繁Full GC该如何优化？
type: item
date: 2026-06-06T14:36:56+08:00
description: 前言我们在面试时，经常会被面试官问到：线上服务频繁Full GC该如何优化？今天这篇文章跟大家一起聊聊这个话题，希望对你会有所帮助。1. 什么是Full GC？当老年代空间不足时，JVM会触发Stop-The-World的全局回收（Full GC），暂停所有应用线程。致命危害（生产环境实测）：对象的晋升之路流程图：关键代码：年龄计数器// HotSpot虚拟机源码片段（objectMonitor.cpp） void ObjectSynchronizer::fast_enter(Handle obj, BasicLock* lock) { if (obj->age() >= MaxTenuringThreshold) { // 年龄阈值检查 promote_to_old_gen(obj); // 晋升老年代 } }2.如何排查定位问题？2.1 实时监控：GC健康度速诊jstat -gcutil <pid> 1000 # 每秒输出GC数据关键指标解读：OU：老年代使用率 > 90% = 危险区FGCT：Full GC总耗时 > 应用运行时间10% = 严重问题2.2. 堆内存转储：揪出内存黑洞jmap -dump:live,format=b,file=heap.bin <pid> # 生产环境慎用live2.3 MAT深度分析：解剖内存泄漏3.优化方案方案1：对象池化——大对象的救赎场景：高频创建10MB的文件缓存// 反例：每次请求创建新对象 public void processRequest(Request req) { byte[] buffer = newbyte[10 * 1024 * 1024]; // 10MB // ...处理逻辑 } // 优化：对象池复用 privatestaticfinal ObjectPool<byte[]> pool = new GenericObjectPool<>( new BasePooledObjectFactory<byte[]>() { @Override publicbyte[] create() { returnnewbyte[10 * 1024 * 1024]; } } ); public void processRequest(Request req) throws Exception { byte[] buffer = pool.borrowObject(); try { // ...处理逻辑 } finally { pool.returnObject(buffer); } }效果：老年代分配速率下降85%方案2：手动控制晋升问题：Survivor区过小导致对象提前晋升优化参数：-XX:TargetSurvivorRatio=60 # Survivor区使用阈值 -XX:MaxTenuringThreshold=15 # 最大晋升年龄 -XX:+NeverTenure # 若Survivor足够，永不晋升（慎用！）晋升原理：方案3：合理分配堆空间经典误区：-Xmx4g -Xms4g # 错误！未配置新生代优化公式：新生代大小 = 总堆 * 3/8 Eden:Survivor = 8:1:1 正确配置：-Xmx8g -Xms8g -Xmn3g # 新生代3G (8*3/8≈3) -XX:SurvivorRatio=8 # Eden:Survivor=8:1:1方案4：卸载无用类场景：热部署频繁的应用（如JRebel）诊断命令：jcmd <pid> VM.class_stats # JDK8+ jcmd <pid> GC.class_stats # JDK11+根治代码：// 自定义类加载器必须实现close() public class HotSwapClassLoader extends URLClassLoader { @Override public void close() throws IOException { // 1. 停止新请求 // 2. 卸载所有类 // 3. 关闭资源 } }方案5：颠覆传统的ZGC传统GC痛点：CMS：内存碎片问题G1：Mixed GC不可控ZGC迁移步骤：升级JDK至17+添加参数：-XX:+UseZGC -XX:ZAllocationSpikeTolerance=5.0 # 容忍内存分配速率波动 -Xmx16g -Xlog:gc*:file=gc.log效果对比：方案6：堆外内存治理现象：堆内存正常，但Full GC频繁根源：DirectByteBuffer的清理依赖Full GC防御方案：// 方案1：限制堆外内存 -XX:MaxDirectMemorySize=512m // 方案2：主动调用Cleaner ByteB…

索引优化的10个小技巧
type: item
date: 2026-06-06T14:36:44+08:00
description: 前言不管你是面试还是日常调优，只要聊到数据库性能，“索引”几乎一定是绕不开的话题。很多小伙伴知道索引能加速查询，但问到“怎么建索引才能让查询最快”时，往往只能答出“给 where 后面的列加索引”。其实，索引优化远没有这么简单。今天这篇文章我就把工作中最常用的10个索引优化技巧总结出来，希望对你会有所帮助。技巧一：选择高选择性的列作为索引1.1 问题现象有些小伙伴习惯性地给所有出现在 WHERE 子句中的列都加上索引，结果发现加了索引反而更慢。比如给“性别”列加索引，查询男性用户时，依然扫描了大量数据。1.2 什么是选择性？选择性 = COUNT(DISTINCT col) / COUNT(*)，比值越接近 1，选择性越高。选择性高的列能快速过滤掉大量数据。-- 查看选择性 SELECT COUNT(DISTINCT gender) / COUNT(*) AS gender_selectivity, COUNT(DISTINCT user_id) / COUNT(*) AS user_selectivity FROM users;低选择性：性别（0.5 左右），索引几乎没用。高选择性：用户 ID、订单号（接近 1），索引价值巨大。1.3 底层原理InnoDB 的 B+Tree 索引结构，每个叶子节点存储一个键值 + 行指针。当选择性很低时（比如性别只有男/女），索引树高度虽然低，但每个键值对应海量行，依然需要回表读取大量数据，代价甚至超过全表扫描。优化器会选择全表扫描。1.4 正确做法只在高选择性列上建索引。若业务必须过滤低选择性字段（如 status），可将其放在联合索引的末尾，作为二次筛选项。-- 不推荐 ALTER TABLE orders ADD INDEX idx_status (status); -- 推荐：status 作为联合索引的后续列 ALTER TABLE orders ADD INDEX idx_user_status (user_id, status);1.5 适用场景与优缺点优点：高选择性索引能迅速定位少量数据，极大减少 IO。缺点：低选择性索引浪费空间，且写入时需要维护。适用场景：用户 ID、订单号、手机号等唯一或接近唯一的列。技巧二：联合索引遵循最左前缀法则2.1 问题现象创建了 INDEX(a, b, c)，却写出 WHERE b = 1 AND c = 2，结果不走索引，查询极慢。2.2 最左前缀原则联合索引 (a, b, c) 本质上是一颗按 a 排序，a 相同再按 b 排序，b 相同再按 c 排序的 B+Tree。查询条件必须包含最左边的列，才能用到索引。2.3 正确与错误示例-- 正确：用到索引所有列 SELECT * FROM t WHERE a = 1 AND b = 2 AND c = 3; -- 正确：用到 a 和 b，c 无法完全匹配，但已过滤大量数据 SELECT * FROM t WHERE a = 1 AND b = 2; -- 正确：只用 a，依然能用索引 SELECT * FROM t WHERE a = 1; -- 错误：跳过 a，完全无法使用索引 SELECT * FROM t WHERE b = 2 AND c = 3; -- 错误：范围查询后的列无法使用索引（后面会讲） SELECT * FROM t WHERE a = 1 AND b > 2 AND c = 3; -- c 不会走索引，因为 b 是范围2.4 优化建议把等值查询的列放在最左边，范围查询（>、<、between）的列放在右边。尽量保证查询条件覆盖最左前缀，避免跳过前列。2.5 适用场景多条件组合查询频繁的表。需要排序、分组、过滤的综合业务。技巧三：尽量使用覆盖索引，避免回表3.1 什么是回表？InnoDB 的二级索引叶子节点存储的是主键值。当你通过二级索引找到主键后，还需要回聚簇索引查找完整行，这个过程叫“回表”。回表是随机 IO，极其昂贵。3.2 覆盖索引如果索引中已经包含了查询需要的所有列，MySQL 就不需要回表，直接从索引中获取数据，称为“覆盖索引”。-- 低效：需要回表读取 name 和 age SELECT name, age FROM user WHERE id_card = 'xxx'; -- 仅有 idx_id_card，需回表 -- 高效：覆盖索引 ALTER TABLE user ADD INDEX idx_id_card_name_age (id_card, name, age); SELECT name, age FROM user WHERE id_card = 'xxx'; -- 索引已包含所需列，直接返回，不回表3.3 优缺点优点：大幅减少随机 IO，查询速度…

百亿数据如何毫秒返回？
type: item
date: 2026-06-06T14:36:37+08:00
description: 近年来公司业务迅猛发展，数据量爆炸式增长，随之而来的的是海量数据查询等带来的挑战，我们需要数据量在十亿，甚至百亿级别的规模时依然能以秒级甚至毫秒级的速度返回，这样的话显然离不开搜索引擎的帮助，在搜索引擎中，ES（ElasticSearch）毫无疑问是其中的佼佼者，连续多年在 DBRanking 的搜索引擎中评测中排名第一，也是绝大多数大公司的首选，那么它与传统的 DB 如 MySQL 相比有啥优势呢，ES 的数据又是如何生成的，数据达到 PB 时又是如何保证 ES 索引数据的实时性以更好地满足业务的需求的呢。本文会结合我司在 ES 上的实践经验与大家谈谈如何构建准实时索引的一些思路，希望对大家有所启发。本文目录如下为什么要用搜索引擎，传统 DB 如 MySQL 不香吗MySQL 的不足ES 简介ES 索引数据构建PB 级的 ES 准实时索引数据构建之道为什么要用搜索引擎，传统 DB 如 MySQL 不香吗MySQL 的不足MySQL 架构天生不适合海量数据查询，它只适合海量数据存储，但无法应对海量数据下各种复杂条件的查询，有人说加索引不是可以避免全表扫描，提升查询速度吗，为啥说它不适合海量数据查询呢，有两个原因：_1、_加索引确实可以提升查询速度，但在 MySQL 中加多个索引最终在执行 SQL 的时候它只会选择成本最低的那个索引，如果没有索引满足搜索条件，就会触发全表扫描，而且即便你使用了组合索引，也要符合最左前缀原则才能命中索引，但在海量数据多种查询条件下很有可能不符合最左前缀原则而导致索引失效，而且我们知道存储都是需要成本的，如果你针对每一种情况都加索引，以 innoDB 为例，每加一个索引，就会创建一颗 B+ 树，如果是海量数据，将会增加很大的存储成本，之前就有人反馈说他们公司的某个表实际内容的大小才 10G, 而索引大小却有 30G！这是多么巨大的成本！所以千万不要觉得索引建得越多越好。_2、_有些查询条件是 MySQL 加索引都解决不了的，比如我要查询商品中所有 title 带有「格力空调」的关键词，如果你用 MySQL 写，会写出如下代码SELECT*FROMproductWHEREtitlelike'%格力空调%'这样的话无法命中任何索引，会触发全表扫描，而且你不能指望所有人都能输对他想要的商品，是人就会犯错误，我们经常会犯类似把「格力空调」记成「格空间」的错误，那么 SQL 语句就会变成：SELECT*FROMproductWHEREtitlelike'%格空调%'这种情况下就算你触发了全表扫描也无法查询到任何商品，综上所述，MySQL 的查询确实能力有限。ES 简介与其说上面列的这些点是 MySQL 的不足，倒不如说 MySQL 本身就不是为海量数据查询而设计的，术业有专攻，海量数据查询还得用专门的搜索引擎，这其中 ES 是其中当之无愧的王者，它是基于 Lucene 引擎构建的开源分布式搜索分析引擎，可以提供针对 PB 数据的近实时查询，广泛用在全文检索、日志分析、监控分析等场景。它主要有以下三个特点:轻松支持各种复杂的查询条件: 它是分布式实时文件存储，会把每一个字段都编入索引（倒排索引），利用高效的倒排索引，以及自定义打分、排序能力与丰富的分词插件等，能实现任意复杂查询条件下的全文检索需求可扩展性强：天然支持分布式存储，通过极其简单的配置实现几百上千台服务器的分布式横向扩容，轻松处理 PB 级别的结构化或非结构化数据。高可用，容灾性能好：通过使用主备节点，以及故障的自动探测与恢复，有力地保障了高可用我们先用与 MySQL 类比的形式来理解 ES 的一些重要概念通过类比的形式不难看出 ES 的以下几个概念_1、_MySQL 的数据库（DataBase）相当于 Index（索引），数据的逻辑集合，ES 的工作主要也是创建索引，查询索引。_2、_一个数据库里会有多个表，同样的一个 Index 也会有多个 type_3、_一个表会有多行（Row），同样的一个 Type 也会有多个 Document。_4、_Schema 指定表名，表字段，是否建立索引等，同样的 Mapping 也指定了 Type 字段的处理规则，即索引如何建立，是否分词，分词规则等_5、_在 MySQL 中索引是需要手动创建的，而在 ES 一切字段皆可被索引，只要在 Mapping 在指定即可那么 ES 中的索引为何如此高效，能在海量数据下达到秒级的效果呢？它采用了多种优化手段，最主要的原因是它采用了一种叫做倒排索引的方式来生成索引，避免了全文档扫描，那么什么是倒排索引呢，通过文档来查找关键词等数据的我们称为正排索引，返之，通过关键词来查找文档的形式我们称之为倒排索引，假设有以下三个文档（Document）要在其中找到含有 comming 的文…

百万excel数据导入功能如何实现？（知识星球专享）
type: item
date: 2026-06-06T14:36:28+08:00
description: 前言最近星球中有两位小伙伴问了我关于excel数据导入的问题：使用Java如何处理复杂大数据量的excel导入解析和导入表。导入千万级excel文件到数据库，有没有比较快的方法？这两个问题有些相似之处，业务场景都是需要导入大数据量的excel文件到数据库。今天就跟大家一起聊聊，千万级excel数据导入功能该如何实现，希望这篇文章对你会有所帮助。1. 问题对于千万级excel数据导入功能，可能会遇到一些问题，我们先聊聊会到哪些问题。1.1 单个Sheet存不了我们都知道excel的一个Sheet，对于2003版，最大行数是65536行，对于2007以上版本，最大行数是1048576行。由此可见，千万级的数据是没法保存到excel的单个Sheet当中的。那我们该怎么办呢？1.2 出现OOM问题即使我们想办法，把一千万的数据保存到excel当中了，但如果程序在导入数据时，将这些数据加载到内存中，内存会直接爆掉，出现OOM问题。我们又该怎么办呢？1.3 导入耗时太长如果你在程序中使用单个线程，在循环中一个个将excel的数据写入到数据库，会有大量的远程IO操作，极有可能会导致该excel程序导入耗时很长。如何优化excel的导入性能呢？1.4 丢数据了如果excel中有1000万的数据，但是你导入数据的数据只有995万，少了5万数据。如何快速查询哪些数据导入失败了呢？如何处理异常数据呢？1.5 CPU使用率过高如果我们将excel改成多线程导入，并且每个线程导入时间非常短，可能会导致线程之间在不停的切换上下文，可能会导致CPU使用率过高问题。这时又该怎么办呢？带着这些问题，开始今天的文章之旅。2. EasyExcel导入海量数据的Excel文件，最大的问题莫过于程序出现OOM了。一旦出现OOM，程序将会直接挂掉。为了防止出现OOM问题，建议使用阿里开源的excel导入导出工具：EasyExcel。EasyExcel采用流式读取方式，我们可以直接从Excel文件中进行读取数据，不需要加载整个Excel文件到内存中。例如下面这段代码：public void readExcel(String filePath) { EasyExcel.read(filePath, MySheet.class, new MySheetListener()).sheet().headRowNumber(1).doRead(); } public class MySheetListener extends AnalysisEventListener { private static final int DATA_SIZE = 100000; private List dataList = new ArrayList<>(); @Override public void invoke(MySheet data, AnalysisContext context) { dataList.add(data); if (dataList.size() >= DATA_SIZE) { saveData(dataList); dataList.clear(); } } @Override public void doAfterAllAnalysed(AnalysisContext context) { saveData(dataList); } private void saveData(List dataList) { //保存数据 } }在上面的这段代码中，DATA_SIZE为100000，即每读取100000条数据，就会调用一次MySheetListener的invoke()方法，并将数据加入到dataList中，最终通过saveData()方法进行批量保存。这样可以避免一次性将全部数据读取到内存中，降低内存使用的风险。对于百万以内的数据，放在excel的一个Sheet中问题不大。但如果有千万级的数据，放在一个Sheet显然是保存不下的，我们可以将数据保存到多个Sheet中，或者直接将数据保存CSV文件中。EasyExcel3.1.1版本已支持csv读取了。3. 性能问题通过前面使用EasyExcel能够保证读取excel，不会出现OOM问题。但可能会出现性能问题。那么该如何解决呢？3.1 批量保存数据我们的程序在读取到一批excel数据之后，可能会有些业务逻辑处理，然后将数据保存到数据库当中。你可以在一个循环中，每一条数据进行一次业务逻辑处理，然后保存数据。例如这样的：for(Entity entity: dataList) { //业务逻辑处理 doBusiness(entity); //将数据保存到数据库 save(entity); }如果是这样保存数据，性能可能…

用explain如何优化SQL性能？
type: item
date: 2026-06-06T14:36:20+08:00
description: 前言对于互联网公司来说，随着用户量和数据量的不断增加，慢查询是无法避免的问题。一般情况下如果出现慢查询，意味着接口响应慢、接口超时等问题。如果是高并发的场景，可能会出现数据库连接被占满的情况，直接导致服务不可用。慢查询的确会导致很多问题，我们要如何优化慢查询呢？主要解决办法有：监控sql执行情况，发邮件、短信报警，便于快速识别慢查询sql打开数据库慢查询日志功能简化业务逻辑代码重构、优化异步处理sql优化索引优化其他的办法先不说，后面有机会再单独介绍。今天我重点说说索引优化，因为它是解决慢查询sql问题最有效的手段。如何查看某条sql的索引执行情况呢？没错，在sql前面加上explain关键字，就能够看到它的执行计划，通过执行计划，我们可以清楚的看到表和索引执行的情况，索引有没有执行、索引执行顺序和索引的类型等。索引优化的步骤是：使用explain查看sql执行计划判断哪些索引使用不当优化sql，sql可能需要多次优化才能达到索引使用的最优值既然索引优化的第一步是使用explain，我们先全面的了解一下它。explain介绍先看看mysql的官方文档是怎么描述explain的：EXPLAIN可以使用于 SELECT， DELETE， INSERT， REPLACE，和 UPDATE语句。当EXPLAIN与可解释的语句一起使用时，MySQL将显示来自优化器的有关语句执行计划的信息。也就是说，MySQL解释了它将如何处理该语句，包括有关如何连接表以及以何种顺序连接表的信息。当EXPLAIN与非可解释的语句一起使用时，它将显示在命名连接中执行的语句的执行计划。对于SELECT语句， EXPLAIN可以显示的其他执行计划的警告信息。explain详解explain的语法：{EXPLAIN | DESCRIBE | DESC} tbl_name [col_name | wild] {EXPLAIN | DESCRIBE | DESC} [explain_type] {explainable_stmt | FORCONNECTION connection_id} explain_type: { EXTENDED | PARTITIONS | FORMAT = format_name } format_name: { TRADITIONAL | JSON } explainable_stmt: { SELECTstatement | DELETEstatement | INSERTstatement | REPLACEstatement | UPDATEstatement }用一条简单的sql看看使用explain关键字的效果：explain select * from test1;执行结果：从上图中看到执行结果中会显示12列信息，每列具体信息如下：说白了，我们要搞懂这些列的具体含义才能正常判断索引的使用情况。话不多说，直接开始介绍吧。id列该列的值是select查询中的序号，比如：1、2、3、4等，它决定了表的执行顺序。某条sql的执行计划中一般会出现三种情况：id相同id不同id相同和不同都有那么这三种情况表的执行顺序是怎么样的呢？1.id相同执行sql如下：explain select * from test1 t1 inner join test1 t2 on t1.id=t2.id结果：我们看到执行结果中的两条数据id都是1，是相同的。这种情况表的执行顺序是怎么样的呢？答案：从上到下执行，先执行表t1，再执行表t2。执行的表要怎么看呢？答案：看table字段，这个字段后面会详细解释。2.id不同执行sql如下：explain select * from test1 t1 where t1.id = (select id from test1 t2 where t2.id=2);结果：我们看到执行结果中两条数据的id不同，第一条数据是1，第二条数据是2。这种情况表的执行顺序是怎么样的呢？答案：序号大的先执行，这里会从下到上执行，先执行表t2，再执行表t1。3.id相同和不同都有执行sql如下：explain select t1.* from test1 t1 inner join (select max(id) mid from test1 group by id) t2 on t1.id=t2.mid结果：我们看到执行结果中三条数据，前面两条数据的的id相同，第三条数据的id跟前面的不同。这种情况表的执行顺序又是怎么样的呢？答案：先执行序号大的，先从下而上执行。遇到序号相同时，再从上而下执行。所以这个列子中表的顺序顺序是：test1、t1、也许你会在这里心生疑问：是什么鬼？它表示派生表，别急后面会讲的。还有一个问题：id列的值允许…

明明是同一条SQL，为什么有时候走索引a，有时候却走索引b ？
type: item
date: 2026-06-06T14:36:12+08:00
description: 前言想象你是一家餐厅的服务员，面前有两个菜单：菜单A：按菜品分类排列（前菜、主菜、甜点）菜单B：按价格从低到高排列当顾客说："我要最便宜的川菜"。你会：先用菜单B找到所有低价菜从中筛选川菜或者：先用菜单A找到所有川菜再按价格排序这就是MySQL优化器的日常决策！明明是同一条SQL，有时候走的索引a，而有时候走的索引b，就是它的锅。今天这篇文章跟大家一起聊聊，MySQL选错索引的问题，希望对你会有所帮助。1 一个让程序员崩溃的案例现在有个需求：查询今年开始已付款的前100个订单。给status字段创建了索引idx_status。给create_time字段创建了索引idx_create_time。查询订单的sql如下：SELECT * FROM orders WHERE status = 'paid' -- 状态条件 AND create_time > '2025-01-01' -- 时间条件 ORDER BY amount DESC LIMIT 100;周一执行计划如下：使用索引：idx_status（状态索引） 扫描行数：500行 耗时：0.1秒周二执行计划如下：使用索引：idx_create_time（时间索引） 扫描行数：50万行 耗时：8秒周一只扫描了500行数据，而周二却扫描了50万行数据。周一耗时0.1秒，而周二耗时却又8秒。同一SQL在不同时间性能差异80倍！让我们拆解背后的原因。2 揭秘优化器的"决策三步曲"MySQL优化器的决策流程如下：成本计算示例：idx_status50万50万次需要排序1050分idx_create_time5万5万次无需排序600分根据扫描行数、回表次数、排序成本，计算一个总成本的分数。优化器会选择总成本更低的idx_create_time索引。3 导致索引切换的四大真凶真凶1：数据分布变化场景还原：周一数据：已支付订单5万条，其中2025年的5万条周二数据：已支付订单50万条，其中2025年的50万条这个例子中数据分布变化很大，周二的数据，比周一的数据一下子多了45万。可能会影响总成本的分数。我们可以通过下面的SQL查看数据分布：SELECT COUNT(*) AS total, SUM(status='paid') AS paid_count, SUM(create_time>'2023-01-01') AS new_orders FROM orders;真凶2：统计信息过期统计信息过期，就像用去年的地图导航，新修的路不会出现在地图上。MySQL的"地图"就是统计信息。我们可以通过ANALYZE TABLE ... DELETE STATISTICS命令删除统计信息：ANALYZE TABLE orders DELETE STATISTICS;这时候查询可能变成全表扫描：EXPLAIN SELECT...显示type: ALL那么，如何解决这个问题呢？使用ANALYZE TABLE命令，刷新统计信息（相当于更新地图）：ANALYZE TABLE orders;真凶3：索引覆盖度差异点餐类比：菜单A能直接看到菜品价格 → 无需问厨师（覆盖索引）菜单B只能看到菜品名 → 需要问厨师详情（回表查询）下面的SQL会走idx_status（需要回表）：SELECT * FROM orders WHERE status='paid';下面的SQL会走idx_create_time（覆盖索引）：SELECT create_time FROM orders WHERE create_time>'2023-01-01';真凶4：索引碎片化索引碎片化就像书本的目录页被撕破，找内容变得困难。检查方法：SHOW TABLE STATUS LIKE 'orders';查看Data_free字段，值越大碎片越多。优化方案：使用ALTER TABLE命令重建索引。ALTER TABLE orders ENGINE=INNODB;4 问题排查四步法第一步：查看当前执行计划使用EXPLAIN查看当前SQL的执行计划：EXPLAIN SELECT * FROM orders WHERE status='paid' AND create_time>'2023-01-01';第二步：检查统计信息使用SHOW INDEX命令检查索引的统计信息：SHOW INDEX FROM orders;关注Cardinality字段，值越接近真实数据越好。第三步：分析数据分布使用下面的SQL分析数据分布:SELECT COUNT(*) AS total, AVG(LENGTH(status)) AS status_avg_len FROM orders;第四步：追踪优化器思考过程SET optimizer_trace="enabled…

微服务性能如何优化？
type: item
date: 2026-06-06T14:35:59+08:00
description: 一、何为压力测试1.1、 大白话解释性能压测是什么：就是考察当前软件和硬件环境下，系统所能承受的最大负荷，并帮助找出系统的瓶颈所在。性能压测的目的：为了系统在线上的处理能力和稳定性维持在一个标准范围内，做到知己知彼，百战不殆。还可以发现内存泄漏、并发与同步的问题。1.2、性能指标RepsonseTime - RT：响应时间，用户从客户端发起一个请求开始计算，到客户端接收到服务端的响应结束，整个过程所耗费的时间。Hits Per Second - HPS：用户每秒点击次数，也就是每秒向后台发送的请求次数。QPS：系统每秒内处理查询的次数。MaxRT：最大响应时间，指用户发出请求到服务端返回响应的最大时间。MiniRT：最少响应时间，指用户发出请求到服务端返回响应的最少时间。90%响应时间：将所有用户的响应时间进行升序排序，取 90 % 的位置。性能测试关注点：吞吐量：每秒钟系统能处理的请求数、任务数。响应时间：服务处理一个请求或一个任务的耗时。错误率：一批请求中结果出过错的请求所占比例。二、Jmeter 压测工具1、Jmeter 工具下载和安装 Jmeter 工具下载地址：https://jmeter.apache.org/download_jmeter.cgi我下载的版本是 apache-jmeter-5.3运行 JMeter 程序打开批处理文件：\apache-jmeter-5.3\bin[jmeter.bat](http://jmeter.bat/)。如下图所示：添加线程组，如下图所示：添加线程组1s 内启动 200 个线程，循环次数 100 次。2 w 个请求。如下图所示：测试 HTTP 请求，如下图所示：配置要测试的协议、服务器地址、端口号。配置信息如下：协议：使用 http 协议。服务器名称或 IP: www.baidu.com (只是为了演示)。端口号：80 端口。如下图所示：添加查看结果树、汇总报告和聚合报告。如下图所示：开始压力测试。点击播放按钮就开始启动了。注意启动之前需要先设置线程组的参数配置和 HTTP 请求的配置。如下图所示：查看每个请求结果，如下图所示：查看汇总报告。主要关心平均值和吞吐量。200 个线程，每个线程调用 100 次，总共 2 w 次，可以看到下图中表格中的样本列也是 2 w，请求所耗费的时间是 151 ms，吞吐量是 880 个请求每秒。如下图所示：查看聚合报告。主要看中位数和90%百分位，中位数是 59 ms，说明大部分请求的响应时间是 59 ms。90 % 的请求 都是在 271 ms 以内响应完成的。异常 0.41% 说明 2 w 个请求中有 82 个请求异常（20000 * 0.0041 = 82 ）。吞吐量 880.2/sec 说明百度这个网站每秒能处理 880 个请求，性能一般（可能跟本地机器性能有关）。如下图所示：查看汇总图。查看汇总图时，需要先勾选想要查看的信息，如下图所示：然后查看图形汇总：可以看到勾选的几列在图表中是用不同颜色表示的，比如绿色的柱状条就是 90 % 百分位。我们来测试下 PassJava（佳必过）的管理后台的性能，吞吐量接近 2000/s。如下图所示：三、性能监控之 jconsolejconsole 和 jvisualvm 是 Java JDK 的两个小工具，用来监控内存泄漏、跟踪垃圾回收、执行时的内存情况、对 CPU 进行分析、线程的分析。都可以通过命令行启动，而且可以监控本地和远程应用。而 jvisualvm 是升级版的 jconsole。我们先来看下 jconsole 的使用。首先用 cmd 命令行的方式启动 jconsole。启动 jconsole命令行启动 jconsole选择监控哪个应用然后选择 passjava 项目的 question 服务。选择 passjava-question 微服务对应的就是下面这个微服务：passjava-question。对应 passjava-question 微服务概览从监控界面上有 6 个菜单，首先看到的是概览功能，上面有堆内存使用量、线程数、类的使用情况、CPU 占用率，都是用趋势图来表示的，能很方便的看出当前性能的概览。注意：这些监控都是实时的。概览内存下面是内存的使用情况，可以从下图中看到有个下拉框，里面可以选择不同的内存维度，然后下面的图标和柱状图也会跟着选择的维度而展示不同。内存下面是线程的使用情况，可以看到线程峰值和活动线程的总数量，目前看到的峰值是59，活动线程数是 57。下半部分可以看到具体是哪些线程，以及线程的堆栈信息，非常详细。线程使用情况类下面是类的加载和卸载情况，已加载类总数是 10679，而已卸载的类是 1 个，所以当前已加装当前类的总数是 10679 - 1 = 10678…

常见 MySQL 的慢查询优化方式！
type: item
date: 2026-06-06T14:35:40+08:00
description: 慢查询日志概念MySQL的慢查询日志是MySQL提供的一种日志记录，它用来记录在MySQL中响应时间超过阀值的语句，具体指运行时间超过long_query_time值的SQL，则会被记录到慢查询日志中。long_query_time的默认值为10，意思是运行10S以上的语句。默认情况下，Mysql数据库并不启动慢查询日志，需要我们手动来设置这个参数，当然，如果不是调优需要的话，一般不建议启动该参数，因为开启慢查询日志会或多或少带来一定的性能影响。慢查询日志支持将日志记录写入文件，也支持将日志记录写入数据库表。慢查询日志相关参数MySQL 慢查询的相关参数解释：slow_query_log ：是否开启慢查询日志，1表示开启，0表示关闭。log-slow-queries ：旧版（5.6以下版本）MySQL数据库慢查询日志存储路径。可以不设置该参数，系统则会默认给一个缺省的文件host_name-slow.logslow-query-log-file：新版（5.6及以上版本）MySQL数据库慢查询日志存储路径。可以不设置该参数，系统则会默认给一个缺省的文件host_name-slow.loglong_query_time ：慢查询阈值，当查询时间多于设定的阈值时，记录日志。log_queries_not_using_indexes：未使用索引的查询也被记录到慢查询日志中（可选项）。log_output：日志存储方式。log_output='FILE'表示将日志存入文件，默认值是'FILE'。log_output='TABLE'表示将日志存入数据库，这样日志信息就会被写入到mysql.slow_log表中。MySQL数据库支持同时两种日志存储方式，配置的时候以逗号隔开即可，如：log_output='FILE,TABLE'。日志记录到系统的专用日志表中，要比记录到文件耗费更多的系统资源，因此对于需要启用慢查询日志，又需要能够获得更高的系统性能，那么建议优先记录到文件。一，第一步.开启mysql慢查询方式一：修改配置文件 在 my.ini 增加几行: 主要是慢查询的定义时间，以及慢查询log日志记录（ slow_query_log）方法二：通过MySQL数据库开启慢查询:二，分析慢查询日志直接分析mysql慢查询日志 ,利用explain关键字可以模拟优化器执行SQL查询语句，来分析sql慢查询语句例如：执行EXPLAIN SELECT * FROM res_user ORDER BYmodifiedtime LIMIT 0,1000得到如下结果：显示结果分析： table | type | possible_keys | key |key_len | ref | rows | Extra EXPLAIN列的解释：table 显示这一行的数据是关于哪张表的type 这是重要的列，显示连接使用了何种类型。从最好到最差的连接类型为const、eq_reg、ref、range、indexhe和ALLrows 显示需要扫描行数key 使用的索引三，常见的慢查询优化（1）索引没起作用的情况使用LIKE关键字的查询语句在使用LIKE关键字进行查询的查询语句中，如果匹配字符串的第一个字符为“%”，索引不会起作用。只有“%”不在第一个位置索引才会起作用。使用多列索引的查询语句MySQL可以为多个字段创建索引。一个索引最多可以包括16个字段。对于多列索引，只有查询条件使用了这些字段中的第一个字段时，索引才会被使用。（2）优化数据库结构合理的数据库结构不仅可以使数据库占用更小的磁盘空间，而且能够使查询速度更快。数据库结构的设计，需要考虑数据冗余、查询和更新的速度、字段的数据类型是否合理等多方面的内容。将字段很多的表分解成多个表对于字段比较多的表，如果有些字段的使用频率很低，可以将这些字段分离出来形成新表。因为当一个表的数据量很大时，会由于使用频率低的字段的存在而变慢。增加中间表对于需要经常联合查询的表，可以建立中间表以提高查询效率。通过建立中间表，把需要经常联合查询的数据插入到中间表中，然后将原来的联合查询改为对中间表的查询，以此来提高查询效率。（3）分解关联查询将一个大的查询分解为多个小查询是很有必要的。很多高性能的应用都会对关联查询进行分解，就是可以对每一个表进行一次单表查询，然后将查询结果在应用程序中进行关联，很多场景下这样会更高效，例如：SELECT * FROM tagJOIN tag_post ON tag_id = tag.idJOIN post ON tag_post.post_id = post.idWHERE tag.tag = 'mysql';分解为：SELECT * FROM tag WHERE tag = 'mysql';SELE…

如何医治一条慢SQL?
type: item
date: 2026-06-06T14:35:32+08:00
description: 前言"苏工，订单列表又崩了！"接到电话时，我对着监控大屏上999ms的SQL响应时间哭笑不得。几年来，我发现一个定律：所有SQL问题都是在凌晨三点爆发！今天抽丝剥茧，教你用架构师的思维给慢SQL开刀手术。希望对你会有所帮助。1 术前检查：找准病灶1.1 EXPLAIN 查看执行计划使用EXPLAIN查看SQL语句的执行计划，相当于给SQL拍了张X光。下面是一个典型的SQL问题，它是某电商平台历史订单查询的SQL语句：SELECT * FROM orders o LEFTJOINusers u ON o.user_id = u.id LEFTJOIN products p ON o.product_id = p.id WHERE o.create_time > '2023-01-01' AND u.vip_level > 3 AND p.category_id IN (5,8) ORDERBY o.amount DESC LIMIT1000,20;使用EXPLAIN关键字查看执行计划的结果如下：+----+-------------+-------+------+---------------+------+---------+------+---------+---------------------------------+ | id | select_type | table | type | possible_keys | key | rows | Extra| key_len | +----+-------------+-------+------+---------------+------+---------+------+---------+---------------------------------+ | 1 | SIMPLE | o | ALL | idx_user_time | NULL | 1987400 | Using where; Using filesort | | 1 | SIMPLE | u | ALL | PRIMARY | NULL | 100000 | Using where | | 1 | SIMPLE | p | ALL | PRIMARY | NULL | 50000 | Using where | +----+-------------+-------+------+---------------+------+---------+------+---------+---------------------------------+诊断报告：全表扫描三连击（type=ALL）filesort暴力排序（内存警告）索引全军覆没2 手术方案：精准打击2.1 单表代谢手术如果通过执行计划查到是索引有问题，我们就需要单独优化索引。病根：JSON字段索引失效错误用法：ALTER TABLE users ADD INDEX idx_extend ((extend_info->'$.is_vip')); extend_info字段是JSON类型的字段，即使创建了索引，索引也会丢失。正解姿势（MySQL 8.0+）：ALTER TABLE users ADD INDEX idx_vip_level (vip_level); ALTER TABLE orders ADD INDEX idx_create_user (create_time, user_id) COMMENT '组合索引覆盖查询';创建组合索引覆盖查询。2.2 血管疏通术卡点分析：原始join顺序是：orders → users → products优化后的方案：(子查询过滤users) → products → orders调整执行顺序，用小表驱动大表。重写后的SQL：SELECT o.* FROM products p INNERJOIN ( SELECT o.id, o.amount, o.create_time FROM orders o WHERE o.create_time > '2023-01-01' ) o ON p.id = o.product_id INNERJOIN ( SELECTid FROMusers WHERE vip_level > 3 ) u ON o.user_id = u.id WHERE p.category_id IN (5,8) ORDERBY o.amount DESC LIMIT1000,20;术后效果：先扫小表（users过滤后只有100条）消除冗余字段传输减少Join时临时表生成2.3 开颅手术通过执行计划锁定了问题，走错索引了，该怎么处理呢？可以通过FORCE INDEX强制指…