作者:京东健康 孟飞

1、 数据库性能优化的意义

业务发展初期,数据库中量一般都不高,也不太容易出一些性能问题或者出的问题也不大,但是当数据库的量级达到一定规模之后,如果缺失有效的预警、监控、处理等手段则会对用户的使用体验造成影响,严重的则会直接导致订单、金额直接受损,因而就需要时刻关注数据库的性能问题。

2、 性能优化的几个常见措施

数据库性能优化的常见手段有很多,比如添加索引、分库分表、优化连接池等,具体如下:

序号 类型 措施 说明
1 物理级别 提升硬件性能 将数据库安装到更高配置的服务器上会有立竿见影的效果,例如提高CPU配置、增加内存容量、采用固态硬盘等手段,在经费允许的范围可以尝试。
2 应用级别 连接池参数优化 我们大部分的应用都是使用连接池来托管数据库的连接,但是大部分都是默认的配置,因而配置好超时时长、连接池容量等参数就显得尤为重要。 1、 如果链接长时间被占用,新的请求无法获取到新的连接,就会影响到业务。 2、 如果连接数设置的过小,那么即使硬件资源没问题,也无法发挥其功效。之前公司做过一些压测,但就是死活不达标,最后发现是由于连接数太小。
3 单表级别 合理运用索引 如果数据量较大,但是又没有合适的索引,就会拖垮整个性能,但是索引是把双刃剑,并不是说索引越多越好,而是要根据业务的需要进行适当的添加和使用。 缺失索引、重复索引、冗余索引、失控索引这几类情况其实都是对系统很大的危害。
4 库表级别 分库分表 当数据量较大的时候,只使用索引就意义不大了,需要做好分库分表的操作,合理的利用好分区键,例如按照用户ID、订单ID、日期等维度进行分区,可以减少扫描范围。
5 监控级别 加强运维 针对线上的一些系统还需要进一步的加强监控,比如订阅一些慢SQL日志,找到比较糟糕的一些SQL,也可以利用业务内一些通用的工具,例如druid组件等。

3、 MySQL底层架构

首先了解一下数据的底层架构,也有助于我们做更好优化。

一次查询请求的执行过程

我们重点关注第二部分和第三部分,第二部分其实就是Server层,这层主要就是负责查询优化,制定出一些执行计划,然后调用存储引擎给我们提供的各种底层基础API,最终将数据返回给客户端。

4、MySQL索引构建过程

目前比较常用的是InnoDB存储引擎,本文讨论也是基于InnoDB引擎。我们一直说的加索引,那到底什么是索引、索引又是如何形成的呢、索引又如何应用呢?这个话题其实很大也很小,说大是因为他底层确实很复杂,说小是因为在大部分场景下程序员只需要添加索引就好,不太需要了解太底层原理,但是如果了解不透彻就会引发线上问题,因而本文平衡了大家的理解成本和知识深度,有一定底层原理介绍,但是又不会太过深入导致难以理解。

首先来做个实验:

创建一个表,目前是只有一个主键索引

CREATE TABLE t1(

a int NOT NULL,

b int DEFAULT NULL,

c int DEFAULT NULL,

d int DEFAULT NULL,

e varchar(20) DEFAULT NULL,

PRIMARYKEY(a)

)ENGINE=InnoDB

插入一些数据:

insert into test.t1 values(4,3,1,1,'d');

insert into test.t1 values(1,1,1,1,'a');

insert into test.t1 values(8,8,8,8,'h');

insert into test.t1 values(2,2,2,2,'b');

insert into test.t1 values(5,2,3,5,'e');

insert into test.t1 values(3,3,2,2,'c');

insert into test.t1 values(7,4,5,5,'g');

insert into test.t1 values(6,6,4,4,'f');

MYSQL从磁盘读取数据到内存是按照一页读取的,一页默认是16K,而一页的格式大概如下。

每一页都包括了这么几个内容,首先是页头、其次是页目录、还有用户数据区域。

1)刚才插入的几条数据就是放到这个用户数据区域的,这个是按照主键依次递增的单向链表。

2)页目录这个是用来指向具体的用户数据区域,因为当用户数据区域的数据变多的时候也就会形成分组,而页目录就会指向不同的分组,利用二分查找可以快速的定位数据。

当数据量变多的时候,那么这一页就装不下这么多数据,就要分裂页,而每页之间都会双向链接,最终形成一个双向链表。

页内的单向链表是为了查找快捷,而页间的双向链表是为了在做范围查询的时候提效,下图为示意图,其中其二页和第三页是复制的第一页,并不真实。

而如果数据还继续累加,光这几个页也不够了,那就逐步的形成了一棵树,也就是说索引B-Tree是随着数据的积累逐步构建出来的。

最下边的一层叫做叶子节点,上边的叫做内节点,而叶子节点中存储的是全量数据,这样的树就是聚簇索引。一直有同学的理解是说索引是单独一份而数据是一份,其实MySQL中有一个原则就是数据即索引、索引即数据,真实的数据本身就是存储在聚簇索引中的,所谓的回表就是回的聚簇索引

但是我们也不一定每次都按照主键来执行SQL语句,大部分情况下都是按照一些业务字段来,那就会形成别的索引树,例如,如果按照b,c,d来创建的索引就会长这样。

推荐1个网站,可以可视化的查看一些算法原型:

目录:

https://www.cs.usfca.edu/~galles/visualization/Algorithms.html

B+树

https://www.cs.usfca.edu/~galles/visualization/BPlusTree.html

而在MySQL官网上介绍的索引的叶子节点是双向链表。

关于索引结构的小结:

对于B-Tree而言,叶子节点是没有链接的,而B+Tree索引是单向链表,但是MySQL在B+Tree的基础之上加以改进,形成了双向链表,双向的好处是在处理> <,between and等'范围查询'语法时可以得心应手。

5、MySQL索引的一些使用规范

1、 只为用于搜索、排序或分组的列创建索引。

重点关注where语句后边的情况

2、 当列中不重复值的个数在总记录条数中的占比很大时,才为列建立索引。

例如手机号、用户ID、班级等,但是比如一张全校学生表,每条记录是一名学生,where语句是查询所有’某学校‘的学生,那么其实也不会提高性能。

3、 索引列的类型尽量小。

无论是主键还是索引列都尽量选择小的,如果很大则会占据很大的索引空间。

4、 可以只为索引列前缀创建索引,减少索引占用的存储空间。

alter table single_table add index idx_key1(key1(10))

5、 尽量使用覆盖索引进行查询,以避免回表操作带来的性能损耗。

select key1 from single_table order by key1

6、 为了尽可能的少的让聚簇索引发生页面分裂的情况,建议让主键自增。

7、 定位并删除表中的冗余和重复索引。

冗余索引:

单列索引:(字段1)

联合索引:(字段1 字段2)

重复索引:

在一个字段上添加了普通索引、唯一索引、主键等多个索引

6、 执行计划

其中常用的是:

possible_keys: 可能用到的索引

key: 实际使用的索引

rows:预估的需要读取的记录条数

7、 线上案例

案例1:

在建设互联网医院系统中,问诊单表当时量级23万左右,其中有一个business_id字符串字段,这个字段用来记录外部订单的ID,并且在该字段上也加了索引,但是'根据该ID查询详情'的SQL语句却总是时好时坏,性能不稳定,快则10ms,慢则2秒左右,SQL大体如下:

select 字段1、字段2、字段3 from nethp_diag where business_Id = ?

因为business_id是记录第三方系统的订单ID,为了兼容不同的第三方系统,因而设计成了字符串类型,但如果传入的是一个数字类型是无法使用索引的,因为MySQL只能将字符串转数字,而不能将数字转字符串,由于外部的ID有的是数字有的是字符串,因而导致索引一会可以走到,一会走不到,最终导致了性能的不稳定。

案例2:

在某次大促的当天,突然接到DBA运维的报警,说数据库突然流量激增,CPU也打到100%了,影响了部分线上功能和体验,遇到这种情况当时大部分人都比较紧张,下图为当时的数据库流量情况:

相关SQL语句:

select count(1)
from jdhe_medical_record
where status = 1 and is_test = #{isTest,jdbcType=INTEGER} and electric_medical_record_status in (2,3)

and patient_id = #

and doctor_pin = #

and created >#{dateStart,jdbcType=TIMESTAMP};

当时的索引情况

当时的执行计划

其实在patientId和doctor_pin两个字段上是有索引的,但是由于线上情况的改变,导致test判断没有进入,这样的通用查询导致这两个字段没有设置上,进而导致了数据库扫描的量激增,对数据库产生了很大压力。

案例3:

2020年某日上午收到数据库CPU异常报警,对线上有一定的影响,后续检查数据库CPU情况如下,从7点51分开始,CPU从8%瞬间达到99.92%,丝毫没有给程序员留任何情面。

当时的SQL语句:

select rx_id, rx_create_time from nethp_rx_info where rx_status = 5 and status = 1 and rx_product_type = 0 and (parent_rx_id = 0 or parent_rx_id is null) and business_type != 7 and vender_id = 8888 order by rx_create_time asc limit 1;

当时的索引情况:

PRIMARY KEY (id), UNIQUE KEY uniq_rx_id (rx_id), KEY idx_diag_id (diag_id), KEY idx_doctor_pin (doctor_pin) USING BTREE, KEY idx_rx_storeId (store_id), KEY idx_parent_rx_id (parent_rx_id) USING BTREE, KEY idx_rx_status (rx_status) USING BTREE, KEY idx_doctor_status_type (doctor_pin, rx_status, rx_type), KEY idx_business_store (business_type, store_id), KEY idx_doctor_pin_patientid (patient_id, doctor_pin) USING BTREE, KEY idx_rx_create_time (rx_create_time)

当时这张表量级2000多万,而当这条慢SQL执行较少的时候,数据库的CPU也就下来了,恢复到了49.91%,基本可以恢复线上业务,从而表象就是线上间歇性的一会可以开方一会不可以,这条SQL当时总共执行了230次,当时的CPU情况也是忽高忽低,伴随这条SQL语句的执行情况,从而最终证明CPU的飙升是由于这条慢SQL。当线上业务逻辑复杂的时候,你很难第一时间知道到底是由于那条SQL引起的,这个就需要对业务非常熟悉,对SQL很熟悉,否则就会白白浪费大量的排查时间。

最后的排查结果:

在头天晚上的时候添加了一条索引rx_create_time,当时没事,但是第二天却出了事故。

加索引前后走的索引不同,一个是走的rx_status(处方审核状态)单列索引,一个是走的rx_create_time(处方提交事件)单列索引,这个就要回到业务,因为处方状态是个枚举,且枚举范围不到10个,也就说线上29,000,000的数据量也就是被分成了不到10份,rx_status=5的值是其中一份,因而通过这个索引就可以命中很多行,这是业务规则,再套用MySQL的特性,主要是以下几条:

1、没加新索引rx_create_time的时候,由于order by后边没有索引,就看where条件中是否有合适的索引,查询选择器选定rx_status这个单列索引,而rx_status=5这个条件下限制的数据行在索引中是连续,即使需要的rx_id不在索引中,再回主键聚簇索引也来得及,由于order by后边没有索引,所以走磁盘级别的排序filesort,高峰积压的时候处方就1万到2万,跑到了100ms,白天低谷的时候几百单也就20ms。

2、新加索引之后,就分两种情况:

2.1、加索引是在晚上,当前命中的行数比较少,由于当天晚上的时候待审核的处方确实很少,也就是rx_status=5的确实很少,查询优化器感觉反正没多少行,排序不重要,因而就还是选择rx_status索引。

2.2、第二天白天,待审核的处方数量很多了(rx_status=5的数据量多了),当时可以命中几万数据,如果当前命中的行数比较多,查询优化器就开始算成本,感觉排序的成本会更高,那就优先保排序吧,所以就选择rx_create_time这个字段,但是这个索引树上没有别的索引字段的信息,没办法,几乎每条数据都要回表,进而引发了灾难。

8、 推荐用书

这本书以一种诙谐幽默的风格写了MySQL的一些运行机制,非常适合阅读,理解成本大幅降低。

https://item.jd.com/13009316.html

https://item.jd.com/10066181997303.html

9、一些感悟

关于数据库的性能优化其实是一个很复杂的大课题,很难通过一篇帖子讲的很全面和深刻,这也就是为什么我的标题是‘浅析’,程序员的成长一定是要付出代价和成本,因为只有真的在一线切身体会到当时的紧张和压力,对于一件事情才能印象深刻,但反之也不能太过于强调代价,如果可以通过一些别人的分享就可以规避一些自己业务的问题和错误的代价也是好的。

MySQL性能优化浅析及线上案例的更多相关文章

  1. MySQL性能优化之max_connections配置参数浅析

    这篇文章主要介绍了MySQL性能优化之max_connections配置参数浅析,本文着重讲解了3种配置max_connections参数的方法,需要的朋友可以参考下 MySQL的max_connec ...

  2. redmine在linux上的mysql性能优化方法与问题排查方案

    iredmine的linux服务器mysql性能优化方法与问题排查方案     问题定位:   客户端工具: 1. 浏览器inspect-tool的network timing工具分析   2. 浏览 ...

  3. MYSQL之性能优化 ----MySQL性能优化必备25条

    今天,数据库的操作越来越成为整个应用的性能瓶颈了,这点对于Web应用尤其明显.关于数据库的性能,这并不只是DBA才需要担心的事,而这更是我 们程序员需要去关注的事情.当我们去设计数据库表结构,对操作数 ...

  4. Mysql 性能优化教程

    Mysql 性能优化教程 目录 目录 1 背景及目标 2 Mysql 执行优化 2 认识数据索引 2 为什么使用数据索引能提高效率 2 如何理解数据索引的结构 2 优化实战范例 3 认识影响结果集 4 ...

  5. 转 Mysql性能优化教程

    Mysql性能优化教程 背景及目标 厦门游家公司(4399.com)用于员工培训和分享. 针对用户群为已经使用过mysql环境,并有一定开发经验的工程师 针对高并发,海量数据的互联网环境. 本文语言为 ...

  6. MySQL性能优化必备25条

    1. 为查询缓存优化你的查询 大多数的MySQL服务器都开启了查询缓存.这是提高性最有效的方法之一,而且这是被MySQL的数据库引擎处理的.当有很多相同的查询被执行了多次的时候,这些查询结果会被放到一 ...

  7. Mysql性能优化三(分表、增量备份、还原)

    接上篇Mysql性能优化二 对表进行水平划分 如果一个表的记录数太多了,比如上千万条,而且需要经常检索,那么我们就有必要化整为零了.如果我拆成100个表,那么每个表只有10万条记录.当然这需要数据在逻 ...

  8. mysql性能优化-慢查询分析、优化索引和配置 (慢查询日志,explain,profile)

    mysql性能优化-慢查询分析.优化索引和配置 (慢查询日志,explain,profile) 一.优化概述 二.查询与索引优化分析 1性能瓶颈定位 Show命令 慢查询日志 explain分析查询 ...

  9. MySQL性能优化 - 别再只会说加索引了

    MySQL性能优化 MySQL性能优化我们可以从以下四个维度考虑:硬件升级.系统配置.表结构设计.SQL语句和索引. 从成本上来说:硬件升级>系统配置>表结构设计>SQL语句及索引, ...

  10. Mysql - 性能优化之子查询

    记得在做项目的时候, 听到过一句话, 尽量不要使用子查询, 那么这一篇就来看一下, 这句话是否是正确的. 那在这之前, 需要介绍一些概念性东西和mysql对语句的大致处理. 当Mysql Server ...

随机推荐

  1. 齐博x1 小程序与公众号长期永久订阅消息的申请方法

    要给用户发送消息提醒的话,需要申请订阅消息.订阅消息分一次性订阅与长期永久性订阅.一次性订阅没有实际意义,用户订阅一次就只能发送一次.这里主要是指导大家如何申请永久长期订阅功能.对于公众号而言,大家先 ...

  2. AT24C02

    AT24C02是一款拥有256bytes(32Page)的EEPROM. 一 :特点(部分) 1:双线接口: 2:双向数据传输协议: 3:400KHz波特率: 4:硬件写保护: 5:最大5ms写入同步 ...

  3. AI之强化学习、无监督学习、半监督学习和对抗学习

    1.强化学习 @ 目录 1.强化学习 1.1 强化学习原理 1.2 强化学习与监督学习 2.无监督学习 3.半监督学习 4.对抗学习 强化学习(英语:Reinforcement Learning,简称 ...

  4. 21.drf视图系统组成及继承关系

    APIView REST framework提供了一个 APIView 类,它是Django的 View 类的子类. APIView 类和Django原生的类视图的 View 类有以下不同: 传入的请 ...

  5. Linux进程间通信(二)

    信号 信号的概念 信号是Linux进程间通信的最古老的一种方式.信号是软件中断,是一种异步通信的方式.信号可以导致一个正在运行的进程被另一个正在运行的异步进程中断,转而处理某个突发事件. 一旦产生信号 ...

  6. 六、模型层(ORM)

    六.模型层(ORM) Django中内嵌了ORM框架,不需要直接编写SQL语句进行数据库操作,而是通过定义模型类,操作模型类来完成对数据库中表的增删改查和创建等操作. O是object,也就类对象的意 ...

  7. Workflow,要不要了解一下

    摘要:Workflow本质是开发者基于实际业务场景开发用于部署模型或应用的流水线工具. Workflow(也称工作流,下文中均可使用工作流进行描述)本质是开发者基于实际业务场景开发用于部署模型或应用的 ...

  8. 【翻译】Thymeleaf – Spring Security集成模块

    原文链接:Thymeleaf - Spring Security integration modules 来源:thymeleaf/thymeleaf-extras-springsecurity自述文 ...

  9. Python中Print方法

    1 number1 = int(input("请输入第一个数:")) 2 number2 = int(input("请输入第二个数:")) 3 4 # 方法一: ...

  10. Centos 7.6 安装部署 openGauss 3.1.0 企业版一主两备集群

    一.安装环境设置 1.1 硬件环境 名称 最低配置 建议配置 测试配置 服务器数量 3 略 略 硬盘 * 至少1GB用于安装openGauss的应用程序.* 每个主机需大约300MB用于元数据存储.* ...