MySQL性能优化学习笔记

1.索引是怎么实现的吗?

1.索引是什么,索引是由什么来实现的?

索引是为了加速对表中数据的检索而创建的一种分散存储的数据结构,索引是有存储引擎来实现的。

2.为什么用索引

索引能极大的减少存储引擎需要扫描的数据量;索引可以把随机IO变成顺序IO;索引可以帮助我们在进行分组、排序的时候,避免使用临时表

3.索引为什么使用B+Tree

动态展示数据结构的网站: https://www.cs.usfca.edu/~galles/visualization/Algorithms.html

首先,我们会想到用二叉树来存储数据,但是,二叉树会造成整个树的深度过大,通过根节点查找数据,会多走很多步。然后就会考虑平衡二叉树,但是平衡二叉树每个节点只有两个子节点,所以树的深度过大,而且每个磁盘块保存的数据量太小了。而B树就很好的解决了这两个问题(mysql使用的B+树原理跟B树差不多)。

B+树和B树的区别是:

  • B+树关键字搜索采用闭合区间

  • B+树非叶子节点不保存数据信息,只保存关键字和子节点的引用,数据保存在叶子节点

  • B+树叶子节点是顺序排列的,并且相邻节点具有顺序引用的关系

B+树对比B树有哪些优势:

  • 拥有B树所有的优势

  • 扫库/表能力更强

  • 磁盘读写能力更强

  • 排序能力更强

4.B+树在存储引擎中的体现形式
  • InnoDb:在InnDb中的索引和数据都存在以.idb为后缀名的文件中,所有数据都存在以主键索引为根节点或者枝节点的B+树的叶子节点中(即便建表时没有创建主键,InnoDB也会自动创建隐性主键),其他的索引形成的B+树的叶子节点都存的是主键

  • MyISAM:索引存在.myi文件中,数据存在.myd文件中,各个索引形成的B+树的叶子节点都是数据的物理地址

5.索引知识补充
  • 列的离散性(区分度):每一列的不同数据越多,它的离散性也就越好,如果离散性不好,创建的索引在搜索时,B+树可选择分支太多,优化器可能就会使用全表扫描

  • 最左匹配原则:对索引中关键字进行比对,一定是从左向右且不可跳过

  • 联合索引注意点:1.经常使用的列靠左放,2.区分度高的列靠左放 3.宽度小的列靠左放

  • 覆盖索引:如果查询的列可以通过索引中的关键字直接返回(不需要回表),那么就是覆盖索引

  • select * from user where name like 'abc%',如果那么name字段有索引,这条语句也不一定会用到索引,因为name字段有可能因为区分度不高,优化器通过全表扫描

  • >,<可以用到索引,not in,!=用不到,order by可以用到

  • 联合索引中,精确匹配最左列范围匹配另一列可以用到索引 ,如:[age+name], 条件是:age=28 and name like 'abcd%'

  • 联合索引中,范围匹配某个列,其右边的所以的列都用不到索引,如[age+name],条件是: age > 20 and name = 'abcde'

2.

1.mysql的体系结及查询流程

客户端->连接池(Connection Poll) ->sql InterFace -> 解析器 -> 优化器 ->执行器 ->存储引擎

2.mysql的存储引擎(插拔式)
  • csv存储引擎:应用于数据的快速导入导出,表直接转成csv文件

  • archive存储引擎:应用于日志系统,大量设备数据采集

  • memory存储引擎:数据存在内存中,数据表默认只有16M,大多数应用于临时表

  • MyIsam存储引擎:select count 无需进行数据的扫描,表级锁,不支持事务

  • InnoDB:支持事务,行级锁,聚集索引(主键索引)的方式来存储数据,支持外键

3.mysql查询优化详解
  • mysql客户端/服务端通信 :半双工的通信方式,其常用的连接状态(通过 show processlist查看)有:sleep(线程正在等待客户端发送数据),Query(连接线程正在执行查询),Locked(线程正在等待表锁释放),Sorting result(线程正在对数据进行排序),Sending data(正在向请求端返回数据)

  • 查询缓存 : 完全相同的两条sql才会命中缓存,如果涉及的表有任何改变,缓存失效

  • 查询优化处理

    1.解析sql:通过词法分析,语法分析将sql语句解析成解析树

    2.预处理阶段:通过mysql的语法规则进一步检查解析树的合法性,如:检查表和列是否存在,解析名字和别民的设置,进行权限验证

    3.查询优化器:找到最优的执行计划,有:使用等价变换规则,将可转化的外连接查询换成内连接查询,优化count/min/max等函数,覆盖索引扫描,子查询优化,提前终止查询(limit),in的优化 等。

  • 查询执行引擎

  • 返回客户端:增量的返回结果,开始生成第一条结果时,mysql就开始往请求方逐步返回

4.如何定位慢sql
  • 业务驱动

  • 测试驱动

  • 慢查询日志: show variables like 'slow_quer%';来找到慢查询日志的位置,日志文件中的time:日志的记录时间,User@Host:执行的用户及主机,Query_time:查询的耗时,Lock_time:锁表时间,Rows_send:发送给请求方的条数,Rows_examined:语句扫描的条数,SET timetamp:语句执行的时间戳, select ....执行的具体语句。还可以通过mysqldumpslow来分析慢查询日志文件

3. InnoDB事务/锁

1.事务
  • 原子性:最小的工作单元,要么一起调教成功,要么一起回滚

  • 一致性:事务中操作的数据及状态是一致的,即写入资料的结果必须完全符合预定的规则,不会因为出现系统意外等外部原因等造成状态不一致

  • 隔离性:一个事务所操作的数据在提交之前,对其他事务的可见性设置(一般设为不可见)

  • 持久性:事务所作的修改就会永久保存,不会因为系统意外而导致数据丢失

2.事务并发造成哪些问题
  • 脏读:一个事务中对一条记录进行修改,另一个并发的事务查到了这个修改后的数据,然后第一个事务回滚

  • 不可重复读:一个事务中第一次查询的结果,被并发的另一个事务所修改,第一个事务中第二次查询到修改后的值

  • 幻读:一个事务中查询了一个数据范围,并发的另一个事务插入了一条符合这个范围的数据,第一个事务再次查询范围造成的问题

3.针对这些问题定义了四种隔离级别
  • 读未提交(未解决并发问题):事务未提交对其他事务是可见的,造成的问题:脏读

  • 读提交(解决脏读问题):一个事务开始之后,只能看到自己提交的事务所作的修改,造成的问题:不可重复读

  • 可重复读(解决不可重复读):在同一个事务中不管什么时候读取同样的数据结果是一样的,造成的问题:幻读

  • 串行话(解决所有问题):最高的隔离级别,通过强制事务的串行执行

对于以上几种隔离级别,并发能力越来越低 对于InnoDB引擎,在可重复读的隔离级别下,解决了幻读的问题

4.隔离级别是通过什么实现的

通过锁和MVCC来实现

5.锁

锁是用户管理不同事务对共享资源的并发访问

表锁和行锁的区别:

  • 锁定粒度:表锁>行锁

  • 加锁效率:表锁>行锁

  • 冲突概率:表锁>行锁

  • 并发性能:表锁<行锁

InnoDB支持行锁和表锁(另类的行锁)

InnoDB的行锁是通过给索引的索引项加锁来实现的,只有通过索引条件进行数据检索,InnoDB才会使用行锁,否则 InnoDB将使用表锁(锁住索引的所有记录),如:非主键索引被锁住,除了这个索引被锁,其对应的主键也会被锁

锁的类型:

  • 共享锁(行锁):又称读锁(s锁),多个事务对于同一个数据共享一把锁,只能读不能修改,加锁语句是:在普通搜索后面加上LOCK IN SHARE MODE

  • 排他锁(行锁):又称写锁(x锁),排他锁不能与其他锁并存,如果一个事务获取了一个数据的排他锁,其他事务不能在获取到该行数据的锁(排他锁,共享锁),只有获取排他锁的事务能对该行数据进行读取和修改(其他事务读取的数据来自快照)

  • 意向锁共享锁(表锁):又称 IS锁,表示事务准备给数据行加入共享锁,即一个数据行加共享锁前必须先取得该表的IS锁,意向共享锁之间是可以互相兼容的

  • 意向锁排他锁(表锁):又称 IX锁,表示事务准备给数据行加入排他锁,即一个数据行加排他锁前必须先取得该表的IX锁,意向排他锁之间是可以互相兼容的

意向锁是InnoDB数据操作前自动加上去的,不需要用户干预,其意义:当事务想去锁表的时候,可以先判断意向锁是否存在,存在则可快速返回该表不能缩表,如果,一个事务中一个数据行加了排他锁,另一个事务中的更新条件没有索引(这时候就会锁表),这时候这个事务就会别锁住

  • 自增锁:针对自增列自增长的一个特殊的表级别锁,默认值是1,代表连续,事务未提交ID永久丢失,比如:一个事务中插入一条数据,但是回滚,那么这个别分配到的ID就会丢失,不会被其他事务用到(除非强制设置ID)

  • 记录锁(行锁的算法):Record Lock 锁住具体的索引项,当sql执行按照唯一性(主键,唯一索引)索引进行数据的检索时,查询条件等值匹配且查询的数据存在时,这时候sql语句加上的锁即为Record Lock,即从临键锁退化为记录锁,如:select * from t where id = 4 for update ,数据库中的数据是 1,4,7,10,那么锁住的是4这条记录

    注意点:如果id不是主键或者唯一索引,那么锁住的是(1,4](4,7]

  • 间隙锁(行锁的算法):Gap Lock 锁住数据不存在的区间(左开右开),,当sql执行按照索引进行数据的检索时,查询条件不存在,这时候sql语句加上的锁即为Gap Lock,即从临键锁退化为间隙锁,间隙锁只存在于可重复读的隔离级别下如:select * from t where id > 4 and id <7 for update ,数据库中的数据是 1,4,7,10,那么锁住的是(4,7)

  • 临键锁(行锁的算法):Next-key Lock ,锁住记录+区间(左开右闭),即锁住这条记录的区间以及下一个区间,当sql执行按照索引进行数据的检索时,查询条件为范围查找(between,and,<,>等)并有数据命中,则此时sql语句加上的锁为Next-key Lock,临键锁 = 间隙锁+记录锁,该算法是为了防止幻读, 如:select * from t where id >5 and id <10 for update ,数据库中的数据是 1,4,7,10,那么锁住的是(4,7],(7,10]

6.脏读/不可重复读/幻读的问题怎么解决
  • 脏读:加上排他锁

  • 不可重复读:加上共享锁

  • 幻读:加上临键锁

7.死锁的介绍及避免

一个事务中持有A的锁,等待B的锁,另一个事务中持有B的锁,等待A的锁,就造成死锁

如何避免:

  • 类似的业务以固定的顺序访问表和行 ,如:一个事务中先扣用户的钱,在给商家加钱,在另一个事务中,也要类似的操作

  • 大事务拆成小事务

  • 在同一个事务中,尽可能一次锁定所有需要的的资源

  • 业务允许的情况下,降低隔离级别

  • 为表加上合理的索引

4.MVCC(多版本并发控制)

1.MVCC是什么

并发访问(读或者写)数据库时,对正在事务内处理的数据做多版本的管理,以达到永用来避免写操作的堵塞,从而引发读操作的并发问题

2.建表或作插入、修改、查找操作时,做了什么
  • 建表:在创建一个表的时候,数据库会默认创建两个隐藏的列 DT_TRX_ID(数据行的版本号),DT_ROLL_PT(删除版本号),

  • 插入:当进行数据插入时,不管是手动还是默认开启的事务都会分配到一个自增的事务ID,在commit的时候,把事务ID插入到DT_TRX_ID中

  • 修改:会把当前修改的事务ID,添加到数据的DT_ROLL_PT字段,同时生成一个新的版本,DT_TRX_ID也是这个事务的ID

  • 查找:

    1.查找数据行版本小于或等于当前事务版本的数据行,这样就能保证事务读取的行要么是在事务开始前已经存在,要么是当前事务插入或修改过的

    2.查找删除版本要么是null,要么大于当前事务版本号的记录,确保查出的行记录在事务开启之前没有被删掉

    两个条件同时满足

3.undo log是什么

是指事务开始之前,在操作任何数据之前,首先将需要操作的数据备份到一个地方(undo Log).

  • undo Log是为了实现事务原子性而出现的产物

  • undo Log在mysql InnoDB引擎中用来实现多版本并发控制

4.undo log 怎么实现事务的原子性

事务执行过程中如果出现了错误或者用户执行了rollback,mysql可以通过undo Log的备份将数据恢复到事务开始之前的状态

5.undo Log 怎么实现多版本并发控制

事务未提交之前,Undo Log保存了未提交之前的版本数据,Undo 中的数据可以作为数据旧版本快照,供其他并发事务进行快照读

6.当前读、快照读
  • 快照读:sql读取的数据来自快照就是快照读,普通的select 就是快照读,InnoDB快照读,数据的读取由cache(原本数据)+undo(事务修改过的数据)两部分组成

  • 当前都:sql读取的版本来自最新版本,通过锁机制来保证读取的数据无法通过其他事务来修改,update,insert,delete,select..lock in share mode,select ..for update 都是当前读

7.redo log

redo log指事务中操作的任何数据,将最新的数据备份到一个地方(Redo Log)

redo log 的持久:不是随着事务的提交而写入的,在事务执行过程中,便开始写入,

redo log是为了实现事务的持久性而出现的产物:防止在发生故障的时间点,尚有脏读页未写进磁盘,在重启mysql的时候根据redo log进行重做,从而达到事务中未入磁盘数据进入持久化这一特性

一旦事务提交并且持久化落盘之后,此时redo log中对应的事务数据记录就失去了意义,所以redo log 是循环写入的,可以理解成一个环形,头部(写入的的位置)快要追上尾部(持久化到磁盘的位置)时,就全力来持久化磁盘,给写入redo log 留下空间

8.redo buffer 通过什么策略持久化到redo log

一般有三种策略,Innodb_flush_log_at_trx_commit设置为0、1、2

  • 设置为0,每秒从buffer->OS cache(操作系统缓存区)-> dist(到磁盘) ,造成的问题是可能丢失一秒内的事务

  • 设置为1(默认),每次提交事务都从buffer->OS cache(操作系统缓存区)-> dist(到磁盘) ,最安全,但是性能最差

  • 设置为2,从buffer->OS cache 是每次事务提交, 从 OS cache-> dist是每秒,这是最推荐的,因为如果mysql异常,只可能丢失一个事务,如果所在服务器崩溃,丢失一秒内的事务

5.配置优化

1.MySQL服务器参数类型

基于作用域,可以分为

  • 全局参数 如:set global autocommit = on/off

  • 会话参数(会话参数不单独设置会采用全局参数),如:set session autocommit = on/off

注意:

  • 全局参数的设置对于已存在的会话无法生效

  • 会话参数的设置会随着会话的销毁而失效

  • 全局类的统一配置建议配置在默认配置文件中,否则重启服务会导致配置失效

2.如何找到默认配置文件

可以通过 mysql --help | grep -A 1 'Default options are read from the following files in the given order'来找到

有可能找多多个配置文件,后面加载的配置文件会把前面加载的配置文件覆盖掉

3.全局配置文件哪些需要注意
  • 最大连接数配置 : max_connections,但是有时真实的最大连接数会比设置的要少,这是因为,系统句柄数(打开文件的个数)设置的小,可以通过 ulimit -a 来查看,通过 /etc/security/limits.conf来设置,或者是因为,mysql的句柄数配置的小 可以通过 /usr/lib/systemd/system/mysqld.service来设置

  • lower_case_table_names = 0 表名区分大小写

  • 内存参数配置:

    1.每一个connection内存参数配置:sort_buffer_size 排序缓冲区大小,建议 256k-2M之间,join_buffer_size 关联查询缓冲区大小 建议 256k-1M之间,如配置400个连接,4000*(0.256M+0.256M)= 2G

    2.Innodb_buffer_pool_size (非常关键),默认是 128M, Innodb_buffer_pool_size 中有数据缓存、索引缓存、缓冲数据、内部结构,大的缓冲池可以减少多次磁盘I/O访问相同表数据以提高性能,参考计算公式:Innodb_buffer_pool_size = (总物理内存-系统运行所用-connection所用) 90%*

  • 其他参数配置:见 https://www.cnblogs.com/wyy123/p/6092976.html

6.数据库设计

1.三大范式
  • 所有字段值都是不可分解的原子值 (每一个列只有一个单一的值,不可再拆分)

  • 在一个数据库表中,一个表中只能保存一种数据,不可以把多种数据保存在同一张数据库表中 (每一个表都不包含其他表已包含的非主键信息)

  • 每一列数据都和主键直接相关,而不能间接相关 (每一行都有主键进行区分)

但是不一定非要完全三大范式来做,因为,如果完全满足第一范式,那么造成表有太多的列,完全满足第三范式,就造成过多的表关联

你可能感兴趣的:(MySQL性能优化学习笔记)