MySQL优化的5个维度

面试官如果问你:你会从哪些维度进行MySQL性能优化?你会怎么回答?

所谓的性能优化,一般针对的是MySQL查询的优化。既然是优化查询,我们自然要先知道查询操作要经过哪些环节,然后思考可以在哪些环节进行优化。

我之前写过一条SQL查询语句是如何执行的?,感兴趣的朋友可以阅读一下,我用其中的一张图展示查询操作需要经历的基本环节。

SQL查询的环节

下面从5个角度介绍一下MySQL优化的一些策略。

image-20220405204100602

  1. 连接配置优化

处理连接是MySQL客户端和MySQL服务端亲热的步,步都迈不好,也就别谈后来的故事了。

既然连接是双方的事情,我们自然从服务端和客户端两个方面来进行优化喽。

1.1 服务端配置

服务端需要做的就是尽可能地多接受客户端的连接,或许你遇到过error 1040: Too many connections的错误?就是服务端的胸怀不够宽广导致的,格局太小!

感谢媳妇儿给画的图

我们可以从两个方面解决连接数不够的问题:

  • 增加可用连接数,修改环境变量max_connections,默认情况下服务端的大连接数为151
  • mysql> show variables like 'max_connections';+-----------------+-------+| Variable_name | Value |+-----------------+-------+| max_connections | 151 |+-----------------+-------+1 row in set (.01 sec)

  • 架构优化

    2.1 使用缓存

    系统中难免会出现一些比较慢的查询,这些查询要么是数据量大,要么是查询复杂(关联的表多或者是计算复杂),使得查询会长时间占用连接。

    如果这种数据的实效性不是特别强(不是每时每刻都会变化,例如每日报表),我们可以把此类数据放入缓存系统中,在数据的缓存有效期内,直接从缓存系统中获取数据,这样就可以减轻数据库的压力并提升查询效率。

    缓存的使用

    2.2 读写分离(集群、主从复制)

    项目的初期,数据库通常都是运行在一台服务器上的,用户的所有读写请求会直接作用到这台数据库服务器,单台服务器承担的并发量毕竟是有限的。

    针对这个问题,我们可以同时使用多台数据库服务器,将其中一台设置为为小组长,称之为master节点,其余节点作为组员,叫做slave。用户写数据只往master节点写,而读的请求分摊到各个slave节点上。这个方案叫做读写分离。给组长加上组员组成的小团体起个名字,叫集群。

    这就是集群

    注:很多开发者不满master-slave这种具有侵犯性的词汇(因为他们认为会联想到种族歧视、黑人奴隶等),所以发起了一项更名运动。

    受此影响MySQL也会逐渐停用masterslave等术语,转而用sourcereplica替代,大家碰到的时候明白即可。

    使用集群必然面临一个问题,就是多个节点之间怎么保持数据的一致性。毕竟写请求只往master节点上发送了,只有master节点的数据是新数据,怎么把对master节点的写操作也同步到各个slave节点上呢?

    主从复制技术来了!我在一条SQL更新语句是如何执行的?中粗浅地介绍了一下binlog日志,我直接搬过来了。

    binlog是实现MySQL主从复制功能的核心组件。master节点会将所有的写操作记录到binlog中,slave节点会有专门的I/O线程读取master节点的binlog,将写操作同步到当前所在的slave节点。

    主从复制

    这种集群的架构对减轻主数据库服务器的压力有非常好的效果,但是随着业务数据越来越多,如果某张表的数据量急剧增加,单表的查询性能就会大幅下降,而这个问题是读写分离也无法解决的,毕竟所有节点存放的是一模一样的数据啊,单表查询性能差,说的自然也是所有节点性能都差。

    这时我们可以把单个节点的数据分散到多个节点上进行存储,这就是分库分表。

    2.3 分库分表

    分库分表中的节点的含义比较宽泛,要是把数据库作为节点,那就是分库;如果把单张表作为节点,那就是分表。

    大家都知道分库分表分成垂直分库、垂直分表、水平分库和水平分表,但是每次都记不住这些概念,我就给大家详细说一说,帮助大家理解。

    2.3.1 垂直分库

    垂直分库

    在单体数据库的基础上垂直切几刀,按照业务逻辑拆分成不同的数据库,这就是垂直分库啦。

    垂直分库

    2.3.2 垂直分表

    垂直分表

    垂直分表就是在单表的基础上垂直切一刀(或几刀),将一个表的多个字短拆成若干个小表,这种操作需要根据具体业务来进行判断,通常会把经常使用的字段(热字段)分成一个表,不经常使用或者不立即使用的字段(冷字段)分成一个表,提升查询速度。

    垂直分表

    拿上图举例:通常情况下商品的详情信息都比较长,而且查看商品列表时往往不需要立即展示商品详情(一般都是点击详情按钮才会进行显示),而是会将商品更重要的信息(价格等)展示出来,按照这个业务逻辑,我们将原来的商品表做了垂直分表。

    2.3.3 水平分表

    把单张表的数据按照一定的规则(行话叫分片规则)保存到多个数据表上,横着给数据表来一刀(或几刀),就是水平分表了。

    水平分表水平分表

    2.3.4 水平分库

    水平分库就是对单个数据库水平切一刀,往往伴随着水平分表。

    水平分库水平分库

    2.3.5 总结

    水平分,主要是为了解决存储的瓶颈;垂直分,主要是为了减轻并发压力。

    2.4 消息队列削峰

    通常情况下,用户的请求会直接访问数据库,如果同一时刻在线用户数量非常庞大,极有可能压垮数据库(参考明星出轨或公布恋情时微博的状态)。

    这种情况下可以通过使用消息队列降低数据库的压力,不管同时有多少个用户请求,先存入消息队列,然后系统有条不紊地从消息队列中消费请求。

    队列削峰

  • 优化器——SQL分析与优化

    处理完连接、优化完缓存等架构的事情,SQL查询语句来到了解析器和优化器的地盘了。在这一步如果出了任何问题,那就只能是SQL语句的问题了。

    只要你的语法不出问题,解析器就不会有问题。此外,为了防止你写的SQL运行效率低,优化器会自动做一些优化,但如果实在是太烂,优化器也救不了你了,只能眼睁睁地看着你的SQL查询沦为慢查询。

    3.1 慢查询

    慢查询就是执行地很慢的查询(这句话说得跟废话似的。。。),只有知道MySQL中有哪些慢查询我们才能针对性地进行优化。

    因为开启慢查询日志是有性能代价的,因此MySQL默认是关闭慢查询日志功能,使用以下命令查看当前慢查询状态

    mysql> show variables like 'slow_query%';+---------------------+--------------------------------------+| Variable_name | Value |+---------------------+--------------------------------------+| slow_query_log | OFF || slow_query_log_file | /var/lib/mysql/9e74f9251f6c-slow.log |+---------------------+--------------------------------------+2 rows in set (0.00 sec)

  • 存储引擎与表结构

    4.1 选择存储引擎

    一般情况下,我们会选择MySQL默认的存储引擎存储引擎InnoDB,但是当对数据库性能要求精益求精的时候,存储引擎的选择也成为一个关键的影响因素。

    建议根据不同的业务选择不同的存储引擎,例如:

    • 查询操作、插入操作多的业务表,推荐使用MyISAM
    • 临时表使用Memory
    • 并发数量大、更新多的业务选择使用InnoDB
    • 不知道选啥直接默认。

    4.2 优化字段

    字段优化的终原则是:使用可以正确存储数据的小的数据类型。

    4.2.1 整数类型

    MySQL提供了6种整数类型,分别是

    • tinyint
    • smallint
    • mediumint
    • int
    • integer
    • bigint

    不同的存储类型的大存储范围不同,占用的存储的空间自然也不同。

    例如,是否被删除的标识,建议选用tinyint,而不是bigint

    4.2.2 字符类型

    你是不是直接把所有字符串的字段都设置为varchar格式了?甚至怕不够,还会直接设置成varchar(1024)的长度?

    如果不确定字段的长度,肯定是要选择varchar,但是varchar需要额外的空间来记录该字段目前占用的长度;因此如果字段的长度是固定的,尽量选用char,这会给你节约不少的内存空间。

    4.2.3 非空

    非空字段尽量设置成NOT NULL,并提供默认值,或者使用特殊值代替NULL

    因为NULL类型的存储和优化都会存在性能不佳的问题,具体原因在这里就不展开了。

    4.2.4 不要用外键、触发器和视图功能

    这也是「阿里巴巴开发手册」中提到的原则。原因有三个:

  • 降低了可读性,检查代码的同时还得查看数据库的代码;
  • 把计算的工作交给程序,数据库只做好存储的工作,并把这件事情做好;
  • 数据的完整性校验的工作应该由开发者完成,而不是依赖于外键,一旦用了外键,你会发现测试的时候随便删点垃圾数据都变得异常艰难。
  • 4.2.5 图片、音频、视频存储

    不要直接存储大文件,而是要存储大文件的访问地址。

    4.2.6 大字段拆分和数据冗余

    大字段拆分其实就是前面说过的垂直分表,把不常用的字段或者数据量较大的字段拆分出去,避免列数过多和数据量过大,尤其是习惯编写SELECT *的情况下,列数多和数据量大导致的问题会被严重放大!

    字段冗余原则上不符合数据库设计范式,但是却非常有利于快速检索。比如,合同表中存储客户id的同时可以冗余存储客户姓名,这样查询时就不需要再根据客户id获取用户姓名了。因此针对业务逻辑适当做一定程度的冗余也是一种比较好的优化技巧。

    5. 业务优化

    严格来说,业务方面的优化已经不算是MySQL调优的手段了,但是业务的优化却能非常有效地减轻数据库访问压力,这方面一个典型例子就是淘宝,下面举几个简单例子给大家提供一下思路:

  • 以往都是双11当晚开始买买买的模式,近几年双11的预售战线越拉越长,提前半个多月就开始了,而且各种定金红包模式丛出不穷,这种方式叫做预售分流。这样做可以分流客户的服务请求,不必等到双十一的凌晨一股脑地集体下单;
  • 双十一的凌晨你或许想查询当天之外的订单,但是却查询失败;甚至支付宝里的小鸡的口粮都被延迟发放了,这是一种降级策略,集结不重要的服务的计算资源,用来保证当前核心的业务;
  • 双十一的时候支付宝极力推荐使用花呗支付,而不是银行卡支付,虽然一部分考量是提高软件粘性,但是另一方面,使用余额宝实际使用的阿里内部服务器,访问速度快,而使用银行卡,需要调用银行接口,相比之下操作要慢了许多。
  • MySQL优化的总结写到此就结束了,其中有不少细节没有提及,多少让我感觉这篇文章不完美。但是有些知识点掰开讲又太多了,不可能一下子全部写下,之后再好好写吧。