提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
本文主要记录mysql的查询、索引、sql优化、事务、主从同步、分库分表等问题的分析与面试回答示例。
提示:以下是本篇文章正文内容,下面案例可供参考
表象:页面加载过慢、接口压测响应时间过长(超过1s)
方案一:开源工具
调试工具:Arthas
运维工具:Prometheus 、Skywalking
方案二:Mysql自带慢日志
慢查询日志记录了所有执行时间超过指定参数(long_query_time,单位:秒,默认10秒)的所有SQL语句的日志如果要开启慢查询日志,需要在MySQL的配置文件(/etc/my.cnf)中配置如下信息:
配置完毕之后,通过以下指令重新启动MySQL服务器进行测试,查看慢日志文件中记录的信息/var/lib/mysql/localhost-slow.log
一个SQL语句执行很慢,如何分析?
可以采用EXPLAIN 或者DESC命令获取MySQL如何执行SELECT语句的信息
展示SQL执行的情况,部分字段说明如下:
可以采用MySQL自带的分析工具EXPLAIN
索引(index) 是帮助MySQL高效获取数据的数据结构(有序)。在数据之外,数据库系统还维护着满足特定查找算法的数据结构(B+树),这些数据结构以某种方式引用 (指向)数据,这样就可以在这些数据结构上实现高级查找算法,这种数据结构就是索引。
维护树的数据结构,提高查找效率,减少IO的操作
B+树、二叉树、红黑树、B树
MySQL默认使用的索引底层数据结构是B+树。再聊B+树之前,我们先聊聊二叉树和B树
B-Tree,B树是一种多叉路衡查找树,相对于二叉树,B树每个节点可以有多个分支,即多叉。以一颗最大度数(max-degree)为5(5阶)的b-tree为例,那这个B树每个节点最多存储4个key
B+Tree是在BTree基础上的一种优化,使其更适合实现外存储索引结构,InnoDB存储引擎就是用B+Tree实现其索引结构
B树与B+树对比:
聚集索引选取规则:
1.如果存在主键,主键索引就是聚集索引。
2.如果不存在主键,将使用第一个唯一(UNIQUE)索引作为聚集索引。
3.如果表没有主键,或没有合适的唯一索引,则InnoDB会自动生成一个rowid作为隐藏的聚集索引。
回表查询:先通过二级索引找到对应的主键值,在用主键值在聚集索引中去找整行的数据
覆盖索引是指查询使用了索引,并且需要返回的列,在该索引中已经全部能够找到。
判断下面的SQL哪些是覆盖索引,为什么?
select * from tb_user where id = 1
是,因为根据id查询的,id默认是主键索引,就是聚簇索引,聚簇索引中对应的是整行的记录
select id,name from tb_user where name = 'Arm'
是,因为通过name这个二级索引也可以找到id,是一次性可以查询出来id和name的
select id,name,gender from tb_user where name = 'Arm'
不是,需要通过回表查询才能获取到gender
覆盖索引是指查询使用了索引,并且需要返回的列,在该索引中已经全部能够找到。
在数据量比较大时,如果进行limit分页查询,在查询时,越往后,分页查询效率越低。
我们一起来看看执行limit分页查询耗时对比:
因为,当在进行分页查询时,如果执行limit 9000000,10,此时需要MySQL排序前9000010记录,仅仅返回9000000 - 9000010 的记录,其他记录丢弃,查询排序的代价非常大。
优化思路:
一般分页查询时,通过创建覆盖索引能够比较好地提高性能,可以通过覆盖索引加子查询形式进行优化
先陈述自己在实际的工作中是怎么用的
主键索引
唯一索引
根据业务创建的索引(复合索引)
针对于数据量较大,且查询比较频繁的表建立索引。单表超过10万数据 (增加用户体验)
针对于常作为查询条件(where)、排序(order by)、分组(group by) 操作的字段建立索引。
尽量选择区分度高的列作为索引,尽量建立唯一索引,区分度越高,使用索引的效率越高(比如一个地区字段,大部分都是北京市,就不适合创建索引)。
如果是字符串类型的字段,字段的长度较长,可以针对于字段的特点,建立前缀索引(比如简介内容想要创建索引,就采用前缀索引)。
尽量使用联合索引,减少单列索引,查询时,联合索引很多时候可以覆盖索引,节省存储空间,避免回表,提高查询效率。
要控制索引的数量,索引并不是多多益善,索引越多,维护索引结构的代价也就越大,会影响增删改的效率。
如果索引列不能存储NULL值,请在创建表时使用NOT NULL约束它。当优化器知道每列是否包含NULL值时,它可以更好地确定哪个索引最有效地用于查询。
索引失效的情况有很多,可以说一些自己遇到过的。
给tb_seller创建联合索引,字段顺序: name,status,address
那快读判断索引是否失效了呢?执行计划explain
违反最左前缀法则
如果索引了多列,要遵守最左前缀法则。指的是查询从索引的最左前列开始,并且不跳过索引中的列。匹配最左前缀法则,走索引。
有效的情况如下图所示:
失效的情况如下图所示(违反最左前缀法则):
范围查询右边的列,不能使用索引。
根据前面的两个字段name , status查询是走索引的,但是最后一个条件address没有用到索引。
不要在索引列上进行运算操作,索引将失效。
字符串不加单引号,造成索引失效。
由于,在查询是,没有对字符串加单引号,MySQL的查询优化器,会自动的进行类型转换,造成索引失效。
以%开头的Like模糊查询,索引失效。如果仅仅是尾部模糊匹配,索引不会失效。如果是头部模糊匹配,索引失效。
表的设计优化
索引优化(参考优化创建原则和索引失效)
SQL语句优化
主从复制、读写分离
分库分表
事务是一组操作的集合,它是一个不可分割的工作单位,事务会把所有的操作作为一个整体一起向系统提交或撤销操作请求,即这些操作要么同时成功,要么同时失败。
● 并发事务问题:脏读、不可重复读、幻读
● 隔离级别:读未提交、读已提交、可重复读、串行化
脏读:一个事务读到另外一个事务还没有提交的数据。
不可重复读:一个事务先后读取同一条记录,但两次读取的数据不同,称之为不可重复读。
幻读:一个事务按照条件查询数据时,没有对应的数据行,但是在插入数据时,又发现这行数据已经存在,好像出现了“幻影”。
对并发事务进行隔离,有以下隔离级别,如下图所示:
注意:事务隔离级别越高,数据越安全,但是性能越低。
undo log和redo log都是mysql的日志文件,但是功能不一样。
redo log 重做日志,记录的是事务提交时数据页的物理修改,是用来实现事务的持久性。
该日志文件由两部分组成:重做日志缓冲(redo log buffer) 以及重做日志文件(redo log file) ,前者是在内存中,后者在磁盘中。当事务提交之后会把所有修改信息都存到该日志文件中,用于在刷新脏页到磁盘,发生错误时,进行数据恢复使用。(每个一段时间会进行清理)
undo log 回滚日志,用于记录数据被修改前的信息,作用包含两个:提供回滚和MVCC(多版本并发控制)。undo log和redo log记录物理日志不一样,它是逻辑日志。
锁:排他锁(如一 个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁)
mvcc:多版本并发控制
MVCC全称Multi-Version Concurrency Control,多版本并发控制。指维护一个数据的多个版本,使得读写操作没有冲突
MVCC的具体实现,主要依赖于数据库记录中的隐式字段、undo log日志、readView。
例如:下面有4个事务,事务2、3、4对该记录都进行了操作,那么在事务5中的两条查询语句是查询的哪个事务版本的记录呢?
MVCC的实现原理:
记录中的隐藏字段
undo log 回滚日志
a. 回滚日志,在insert、 update、 delete的时候产生的便于数据回滚的日志。
b. 当insert的时候,产生的undo log日志只在回滚时需要,在事务提交后,可被立即删除。
c. 而update、delete的时候, 产生的undo log日志不仅在回滚时需要,mvcc版本访问也需要,不会立即被删除。
undo log 版本链
不同事务或相同事务对同一条记录进行修改,会导致该记录的undo log生成一条记录版本链表,链表的头部是最新的旧记录,链表尾部是最早的旧记录。
readview
ReadView (读视图)是 快照读 SQL执行时MVCC提取数据的依据,记录并维护系统当前活跃的事务(未提交的) id。
a. 当前读:读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。对于我们日常的操作,如:
select … lock in share mode(共享锁),select … for update、update、 insert、 delete(排他锁)都是一 种当前读。
b. 快照读:简单的select (不加锁)就是快照读,快照读,读取的是记录数据的可见版本,有可能是历史数据,不加锁,是非阻塞读。
Read Committed:每次select, 都生成一个快照读。
Repeatable Read:开启事务后第一个select语句才是快照读的地方。
举例说明:
当前读:不受隔离级别影响,事务A的两次select都是读到的最新的数据
快照读:不同的隔离级别不一样,RC读出来不一样,RR读出来都是相同的,获得的结果就是快照读
ReadView包含的四个核心字段:
不同的隔离级别,生成ReadView的时机不同:
➢ READ COMMITTED :在事务中每一 次执行快照读时生成ReadView。
➢ REPEATABLE READ:仅在事务中第一 次执行快照读时生成ReagView,后续复用该ReadView。
主从架构:主库用于写数据,从库用于读数据
主库与从库如何进行同步的?
MySQL主从复制的核心就是二进制日志
二进制日志(bin log) 记录了所有的 DDL (数据定义语言,增删改查表结构) 语句和 DML (数据操纵语言,增删改查表中的数据) 语句,但不包括数据查询 (SELECT、 SHOW) 语句。
复制分成三步:
主从架构,读写分离,分担了访问压力
分库分表的时机:
分库分表主要解决存储的压力
垂直拆分与水平拆分
垂直拆分之垂直分库:以表为依据,根据业务将不同表拆分到不同库中。
特点:
1.按业务对数据分级管理、维护、监控、扩展
2.在高并发下,提高磁盘IO和数据量连接数
垂直拆分之垂直分表:以字段为依据,根据字段属性将不同字段拆分到不同表中。
特点:
1.冷热数据分离
2.减少IO过渡争抢,两表互不影响
拆分规则:
● 把不常用的字段单独放在一张表
● 把text、blob等大字段拆分出来放在附表中
水平拆分之水平分库:将一个库中的数据拆分到多个库中。
特点:
1.解决了单库大数量,高并发的性能瓶颈问题
2.提高了系统的稳定性和可用性
路由规则
● 根据id节点取模
● 按id也就是范围路由, 节点1(1-100万),节点2(100万-200万)
水平拆分之水平分表:将一个表的数据拆分到多个表中(可以在同一个库内)。
特点:
1.优化单一表数据量过大而产生的性能问题;
2.避免IO争抢并减少锁表的几率;
分库分表的策略有哪些?
常用垂直分库和垂直分表
https://www.bilibili.com/video/BV1yT411H7YK?p=20&vd_source=98092b0aee05ae7c890b09fe07f13df4