MySQL“被动”性能优化汇总!

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"年少不知优化苦,遇坑方知优化难。 ——村口王大爷"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本文内容导图如下:"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/72/72b6843fc546b594f619612bbccb35d5.png","alt":"image.png","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我之前有很多文章都在讲性能优化的问题,比如下面这些:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":"1","normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://mp.weixin.qq.com/s/icjByl5g3GvkgqLGIYakgQ","title":""},"content":[{"type":"text","text":"《switch 的性能提升了 3 倍,我只用了这一招!》"}]}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://mp.weixin.qq.com/s/KQEWT-GAUqRvSv6sDVkUxw","title":""},"content":[{"type":"text","text":"《String性能提升10倍的几个方法!(源码+原理分析)》"}]}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://mp.weixin.qq.com/s/O08zPGhgnWFEBIqh5oUmWw","title":""},"content":[{"type":"text","text":"《局部变量竟然比全局变量快 5 倍?》"}]}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://mp.weixin.qq.com/s/ZraWOaOdYAJA7TV3Zx60Xw","title":""},"content":[{"type":"text","text":"《池化技术到达有多牛?看了线程和线程池的对比吓我一跳!》"}]}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":5,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://mp.weixin.qq.com/s/4O9MNTqS8eulExeY-RFKag","title":""},"content":[{"type":"text","text":"《链表竟然比数组慢了1000多倍?(动图+性能评测)》"}]}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":6,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://mp.weixin.qq.com/s/Zz6mofCtmYpABDL1ap04ow","title":""},"content":[{"type":"text","text":"《HashMap 的 7 种遍历方式与性能分析!》"}]}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":7,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://mp.weixin.qq.com/mp/appmsgalbum?_biz=MzU1NTkwODE4Mw==&action=getalbum&albumid=1337160373983690752&subscene=158&scenenote=https%3A%2F%2Fmp.weixin.qq.com%2Fs%3F__biz%3DMzU1NTkwODE4Mw%3D%3D%26mid%3D2247485176%26idx%3D1%26sn%3D961fcc0e4dd05a505ef0fc1eb5b10ac4%26chksm%3Dfbcc6bc0","title":""},"content":[{"type":"text","text":"更多性能优化文章"}]}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"当然,本篇也是关于性能优化的,"},{"type":"text","marks":[{"type":"strong"}],"text":"那性能优化就应该一把梭子吗?还是要符合一些规范和原则呢?"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/91/915bb95edff2e56f73605d0b6ba5d735.jpeg","alt":"代码一把梭.jpg","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以,在开始之前(MySQL 优化),咱们先来聊聊性能优化的一些原则。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"性能优化原则和分类"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"性能优化一般可以分为:"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"主动优化"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"被动优化"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所谓的主动优化是指不需要外力的推动而自发进行的一种行为,比如当服务没有明显的卡顿、宕机或者硬件指标异常的情况下,自我出发去优化的行为,就可以称之为主动优化。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/af/af04953632db9918b9f161728e788c0a.gif","alt":null,"title":"","style":[{"key":"width","value":"50%"},{"key":"bordertype","value":"boxShadow"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"而被动优化刚好与主动优化相反,它是指在发现了服务器卡顿、服务异常或者物理指标异常的情况下,才去优化的这种行为。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"性能优化原则"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"无论是主动优化还是被动优化都要符合以下性能优化的原则:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":"1","normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"优化不能改变服务运行的逻辑,要保证服务的"},{"type":"text","marks":[{"type":"strong"}],"text":"正确性"},{"type":"text","text":";"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"优化的过程和结果都要保证服务的"},{"type":"text","marks":[{"type":"strong"}],"text":"安全性"},{"type":"text","text":";"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"要保证服务的"},{"type":"text","marks":[{"type":"strong"}],"text":"稳定性"},{"type":"text","text":",不能为了追求性能牺牲程序的稳定性。比如不能为了提高 Redis 的运行速度,而关闭持久化的功能,因为这样在 Redis 服务器重启或者掉电之后会丢失存储的数据。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/8d/8d4bccea52352ad2b9e69f8b1fd1ae5b.png","alt":"image.png","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"以上原则看似都是些废话,但却给了我们一个启发,那就是我们"},{"type":"text","marks":[{"type":"strong"}],"text":"性能优化手段应该是:预防性能问题为主+被动优化为辅"},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"也就是说,我们应该"},{"type":"text","marks":[{"type":"strong"}],"text":"以预防性能问题为主"},{"type":"text","text":",在开发阶段尽可能的规避性能问题,而"},{"type":"text","marks":[{"type":"strong"}],"text":"在正常情况下,应尽量避免主动优化,以防止未知的风险"},{"type":"text","text":"(除非是为了 KPI,或者是闲的没事),尤其对生产环境而言更是如此,最后才是考虑"},{"type":"text","marks":[{"type":"strong"}],"text":"被动优化"},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"PS:当遇到性能缓慢下降、或硬件指标缓慢增加的情况,如今天内存的占用率是 50%,明天是 70%,后天是 90% ,并且丝毫没有收回的迹象时,我们应该提早发现并处理此类问题(这种情况也属于被动优化的一种)。"}]}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"MySQL 被动性能优化"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以我们本文会"},{"type":"text","marks":[{"type":"strong"}],"text":"重点介绍 MySQL 被动性能优化的知识,根据被动性能优化的知识,你就可以得到预防性能问题发生的一些方法,从而规避 MySQL 的性能问题"},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本文我们会从问题入手,然后考虑这个问题产生的原因以及相应的优化方案。我们在实际开发中,通常会遇到以下 3 个问题:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":"1","normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"单条 SQL 运行慢;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"部分 SQL 运行慢;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"整个 SQL 运行慢。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/16/1692812895f5eaf97cb25e0e330d40d4.png","alt":"image.png","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"问题 1:单条 SQL 运行慢"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"问题分析"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"造成单条 SQL 运行比较慢的常见原因有以下两个:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":"1","normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"未正常创建或使用索引;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"表中数据量太大。"}]}]}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"解决方案 1:创建并正确使用索引"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"索引"},{"type":"text","text":"是一种能帮助 MySQL 提高查询效率的主要手段,因此一般情况下我们遇到的单条 SQL 性能问题,通常都是由于未创建或为正确使用索引而导致的,所以在遇到单条 SQL 运行比较慢的情况下,你"},{"type":"text","marks":[{"type":"strong"}],"text":"首先要做的就是检查此表的索引是否正常创建"},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果表的索引已经创建了,"},{"type":"text","marks":[{"type":"strong"}],"text":"接下来就要检查一下此 SQL 语句是否正常触发了索引查询"},{"type":"text","text":",如果发生以下情况那么 MySQL 将不能正常的使用索引:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":"1","normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"在 where 子句中使用 != 或者 <> 操作符,查询引用会放弃索引而进行全表扫描;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"不能使用前导模糊查询,也就是 '%XX' 或 '%XX%',由于前导模糊不能利用索引的顺序,必须一个个去找,看是否满足条件,这样会导致全索引扫描或者全表扫描;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"如果条件中有 or 即使其中有条件带索引也不会正常使用索引,要想使用 or 又想让索引生效,只能将 or 条件中的每个列都加上索引才能正常使用;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"在 where 子句中对字段进行表达式操作。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"因此你要尽量避免以上情况"},{"type":"text","text":",除了正常使用索引之外,我们也可以"},{"type":"text","marks":[{"type":"strong"}],"text":"使用以下技巧来优化索引的查询速度"},{"type":"text","text":":"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":"1","normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"尽量使用主键查询,而非其他索引,因为主键查询不会触发回表查询;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"查询语句尽可能简单,大语句拆小语句,减少锁时间;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"用 exists 替代 in 查询;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":5,"align":null,"origin":null},"content":[{"type":"text","text":"避免在索引列上使用 is null 和 is not null。"}]}]}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"回表查询:普通索引查询到主键索引后,回到主键索引树搜索的过程,我们称为回表查询。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"解决方案 2:数据拆分"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"当表中数据量太大时 SQL 的查询会比较慢,你可以考虑拆分表,让每张表的数据量变小,从而提高查询效率。"}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"1.垂直拆分"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"指的是将表进行拆分,把一张列比较多的表拆分为多张表。比如,用户表中一些字段经常被访问,将这些字段放在一张表中,另外一些不常用的字段放在另一张表中,插入数据时,使用事务确保两张表的数据一致性。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"垂直拆分的原则:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"把不常用的字段单独放在一张表;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"把 text,blob 等大字段拆分出来放在附表中;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"经常组合查询的列放在一张表中。"}]}]}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"2.水平拆分"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"指的是将数据表行进行拆分,表的行数超过200万行时,就会变慢,这时可以把一张的表的数据拆成多张表来存放。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通常情况下,我们使用取模的方式来进行表的拆分,比如,一张有 400W 的用户表 users,为提高其查询效率我们把其分成 4 张表 users1,users2,users3,users4,然后通过用户 ID 取模的方法,同时查询、更新、删除也是通过取模的方法来操作。"}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"表的其他优化方案:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":"1","normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"使用可以存下数据最小的数据类型;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"使用简单的数据类型,int 要比 varchar 类型在 MySQL 处理简单;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"尽量使用 tinyint、smallint、mediumint 作为整数类型而非 int;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"尽可能使用 not null 定义字段,因为 null 占用 4 字节空间;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":5,"align":null,"origin":null},"content":[{"type":"text","text":"尽量少用 text 类型,非用不可时最好考虑分表;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":6,"align":null,"origin":null},"content":[{"type":"text","text":"尽量使用 timestamp,而非 datetime;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":7,"align":null,"origin":null},"content":[{"type":"text","text":"单表不要有太多字段,建议在 20 个字段以内。"}]}]}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"问题 2:部分 SQL 运行慢"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"问题分析"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"部分 SQL 运行比较慢,我们首先要做的就是先定位出这些 SQL,然后再看这些 SQL 是否正确创建并使用索引。也就是说,我们先要使用慢查询工具定位出具体的 SQL,然后再使用问题 1 的解决方案处理慢 SQL。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"解决方案:慢查询分析"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"MySQL 中自带了慢查询日志的功能,开启它就可以用来记录在 MySQL 中响应时间超过阀值的语句,具体指运行时间超过 long"},{"type":"text","marks":[{"type":"italic"}],"text":"query"},{"type":"text","text":"time 值的 SQL,则会被记录到慢查询日志中。long"},{"type":"text","marks":[{"type":"italic"}],"text":"query"},{"type":"text","text":"time 的默认值为 10,意思是运行 10S 以上的语句。默认情况下,MySQL 数据库并不启动慢查询日志,需要我们手动来设置这个参数,如果不是调优需要的话,一般不建议启动该参数,因为开启慢查询日志会给 MySQL 服务器带来一定的性能影响。慢查询日志支持将日志记录写入文件,也支持将日志记录写入数据库表。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"使用 "},{"type":"codeinline","content":[{"type":"text","text":"mysql> show variables like '%slow_query_log%';"}]},{"type":"text","text":" 来查询慢查询日志是否开启,执行效果如下图所示:"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/17/17b2cba222c0b98186a7860a39d9a566.png","alt":"image.png","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"slow"},{"type":"text","marks":[{"type":"italic"}],"text":"query"},{"type":"text","text":"log 的值为 OFF 时,表示未开启慢查询日志。"}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"开启慢查询日志"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"开启慢查询日志,可以使用如下 MySQL 命令:"}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"mysql> set global slow"},{"type":"text","marks":[{"type":"italic"}],"text":"query"},{"type":"text","text":"log=1"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"不过这种设置方式,只对当前数据库生效,如果 MySQL 重启也会失效,如果要永久生效,就必须修改 MySQL 的配置文件 my.cnf,配置如下:"}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"slow"},{"type":"text","marks":[{"type":"italic"}],"text":"query"},{"type":"text","text":"log =1"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"slow"},{"type":"text","marks":[{"type":"italic"}],"text":"query"},{"type":"text","text":"log_file=/tmp/mysql_slow.log"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"当你开启慢查询日志之后,所有的慢查询 SQL 都会被记录在 slow_query_log_file 参数配置的文件内,默认是 /tmp/mysql_slow.log 文件"},{"type":"text","text":",此时我们就可以打开日志查询到所有慢 SQL 进行逐个优化。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"问题 3:整个 SQL 运行慢"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"问题分析"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"当出现整个 SQL 都运行比较慢就说明目前数据库的承载能力已经到了峰值,因此我们需要使用一些数据库的扩展手段来缓解 MySQL 服务器了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"解决方案:读写分离"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一般情况下对数据库而言都是“读多写少”,换言之,数据库的压力多数是因为大量的读取数据的操作造成的,我们可以采用数据库集群的方案,使用一个库作为主库,负责写入数据;其他库为从库,负责读取数据。这样可以缓解对数据库的访问压力。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"MySQL 常见的读写分离方案有以下两种:"}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","marks":[{"type":"italic"}],"text":"1.应用层解决方案"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以通过应用层对数据源做路由来实现读写分离,比如,使用 SpringMVC + MyBatis,可以将 SQL 路由交给 Spring,通过 AOP 或者 Annotation 由代码显示的控制数据源。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"优点:路由策略的扩展性和可控性较强。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"缺点:需要在 Spring 中添加耦合控制代码。"}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","marks":[{"type":"italic"}],"text":"2.中间件解决方案"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通过 MySQL 的中间件做主从集群,比如:Mysql Proxy、Amoeba、Atlas 等中间件都能符合需求。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"优点:与应用层解耦。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"缺点:增加一个服务维护的风险点,性能及稳定性待测试,需要支持代码强制主从和事务。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"扩展知识:SQL 语句分析"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 MySQL 中我们可以使用 explain 命令来分析 SQL 的执行情况,比如:"}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"explain select * from t where id=5;"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如下图所示:"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/86/8608d550fe3f3400480a43eab62836bc.png","alt":"image.png","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其中:"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"id — 选择标识符,id 越大优先级越高,越先被执行;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"select_type — 表示查询的类型;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"table — 输出结果集的表;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"partitions — 匹配的分区;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"type — 表示表的连接类型;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"possible_keys — 表示查询时,可能使用的索引;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"key — 表示实际使用的索引;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"key_len — 索引字段的长度;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"ref—  列与索引的比较;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"rows — 大概估算的行数;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"filtered — 按表条件过滤的行百分比;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Extra — 执行情况的描述和说明。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其中最重要的就是 type 字段,type 值类型如下:"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"all — 扫描全表数据;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"index — 遍历索引;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"range — 索引范围查找;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"index_subquery — 在子查询中使用 ref;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"unique"},{"type":"text","marks":[{"type":"italic"}],"text":"subquery — 在子查询中使用 eq"},{"type":"text","text":"ref;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"ref"},{"type":"text","marks":[{"type":"italic"}],"text":"or"},{"type":"text","text":"null — 对 null 进行索引的优化的 ref;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"fulltext — 使用全文索引;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"ref — 使用非唯一索引查找数据;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"eq_ref — 在 join 查询中使用主键或唯一索引关联;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"const — 将一个主键放置到 where 后面作为条件查询, MySQL 优化器就能把这次查询优化转化为一个常量,如何转化以及何时转化,这个取决于优化器,这个比 eq_ref 效率高一点。"}]}]}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"总结"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本文我们介绍了 MySQL 性能优化的原则和分类,MySQL 的性能优化可分为:主动优化和被动优化,但无论何种优化都要保证服务的正确性、安全性和稳定性。它带给我们的启发是应该采用:预防 + 被动优化的方案来确保 MySQL 服务器的稳定性,而被动优化常见的问题是:"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"单条 SQL 运行慢;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"部分 SQL 运行慢;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"整个 SQL 运行慢。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因此我们给出了每种被动优化方案的问题分析和解决方案,希望本文可以帮助到你。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章