MySQL性能调优(7)复习补充

复习时自己产生的问题

log

缓冲池 Buffer Pool

首先,InnnoDB 的数据都是放在磁盘上的,InnoDB 操作数据有一个最小的逻辑单位,叫做页(索引页和数据页)。我们对于数据的操作,不是每次都直接操作磁盘,因为磁盘的速度太慢了。InnoDB 使用了一种缓冲池的技术,也就是把磁盘读到的页放到一 块内存区域里面。这个内存区域就叫 Buffer Pool,下一次读取相同的页,先判断是不是在缓冲池里面,如果是,就直接读取,不用再 次访问磁盘。 修改数据的时候,先修改缓冲池里面的页。内存的数据页和磁盘数据不一致的时候, 我们把它叫做脏页。InnoDB 里面有专门的后台线程把 Buffer Pool 的数据写入到磁盘, 每隔一段时间就一次性地把多个修改写入磁盘,这个动作就叫做刷脏。 Buffer Pool 是 InnoDB 里面非常重要的一个结构,它的内部又分成几块区域。这里 我们趁机到官网来认识一下 InnoDB 的内存结构和磁盘结构。

Buffer Pool
SHOW STATUS LIKE '%innodb_buffer_pool%';
官网链接
Buffer Pool 默认大小是 128M(134217728 字节),可以调整。
这个可以作为优化,根据业务把缓冲池调大有利于更多的索引热点数据查询
https://dev.MySQL.com/doc/refman/5.7/en/server-status-variables.html
内存的缓冲池写满了InnoDB 用 LRU算法来管理缓冲池(链表实现,不是传统的 LRU,分成了 young 和 old),经过淘汰的数据就是热点数据。这个策略跟redis一样。
Change Buffer 写缓冲

如果这个数据页不是唯一索引,不存在数据重复的情况,也就不需要从磁盘加载索 引页判断数据是不是重复(唯一性检查)。这种情况下可以先把修改记录在内存的缓冲 池中,从而提升更新语句(Insert、Delete、Update)的执行速度。把 Change Buffer 记录到数据页的操作叫做 merge。在访问这个数据页的时候,或者通过后台线程、或者数据库 shut down、 redo log 写满时触发merge。

如果数据库大部分索引都是非唯一索引,并且业务是写多读少,不会在写数据后立 刻读取,就可以使用 Change Buffer(写缓冲)。写多读少的业务,调大这个值。 Change Buffer 占 Buffer Pool 的比例,默认 25%

Adaptive Hash Index

索引应该是放在磁盘的。InnoDB本身不支持哈希索引,所有索引检索都走B树,Adaptive Hash index可以认为是“索引的索引”。哈希索引就是采用一定的哈希算法,把键值换算成新的哈希值,检索时不需要类似B+树那样从根节点到叶子节点逐级查找,只需一次哈希算法即可立刻定位到相应的位置,速度非常快。

(redo)Log Buffer

如果 Buffer Pool 里面的脏页还没有刷入磁盘时,数据库宕机或者重 启,这些数据丢失。如果写操作写到一半,甚至可能会破坏数据文件导致数据库不可用。为了避免这个问题,InnoDB 把所有对页面的修改操作专门写入一个日志文件,并且 在数据库启动时从这个文件进行恢复操作(实现 crash-safe)——用它来实现事务的持久性。这 种 日 志 和 磁 盘 配 合 的 整 个 过 程 , 其 实 就 是 MySQL 里 的 WAL 技 术 (Write-Ahead Logging),它的关键点就是先写日志,再写磁盘。

同样是写磁盘,为什么不直接写到 db file 里面去?为什么先写日志再写磁盘?
我们先来了解一下随机 I/O 和顺序 I/O 的概念。
磁盘的最小组成单元是扇区,通常是 512 个字节。
操作系统和内存打交道,最小的单位是页 Page。
操作系统和磁盘打交道,读写磁盘,最小的单位是块 Block。
如果我们所需要的数据是随机分散在不同页的不同扇区中,那么找到相应的数据需要等到磁臂旋转到指定的页,然后盘片寻找到对应的扇区,才能找到我们所需要的一块数据,一次进行此过程直到找完所有数据,这个就是随机 IO,读取数据速度较慢。
假设我们已经找到了第一块数据,并且其他所需的数据就在这一块数据后边,那么就不需要重新寻址,可以依次拿到我们所需的数据,这个就叫顺序 IO。
刷盘是随机 I/O,而记录日志是顺序 I/O,顺序 I/O 效率更高。因此先把修改写入日志,可以延迟刷盘时机,进而提升系统吞吐。
当然 redo log 也不是每一次都直接写入磁盘,在 Buffer Pool 里面有一块内存区域(Log Buffer)专门用来保存即将要写入日志文件的数据,默认 16M,它一样可以节省磁盘 IO。
需要注意:redo log 的内容主要是用于崩溃恢复。磁盘的数据文件,数据来自 buffer pool。redo log写入磁盘,不是写入数据文件。那么,Log Buffer 什么时候写入 log file?
在我们写入数据到磁盘的时候,操作系统本身是有缓存的。flush 就是把操作系统缓冲区写入到磁盘
log buffer 写入磁盘的时机,由一个参数控制,默认是 1。
SHOW VARIABLES LIKE 'innodb_flush_log_at_trx_commit';
0(延迟写)
log buffer 将每秒一次地写入 log file 中,并且 log file 的 flush 操作同时进行。
该模式下,在事务提交的时候,不会主动触发写入磁盘的操作。
1(默认,实时写,实时刷)
每次事务提交时 MySQL 都会把 log buffer 的数据写入 log file,并且刷到磁盘
中去。
2(实时写,延迟刷)
每次事务提交时 MySQL 都会把 log buffer 的数据写入 log file。但是 flush 操
作并不会同时进行。该模式下,MySQL 会每秒执行一次 flush 操作

问题:Redo log 和db文件都是在磁盘上的,为什么写redo log 是顺序io 写db文件就是随机IO呢?

buffer pool中有很多数据等待刷脏的时候,写入redo log是顺序写入的。
而这些数据在磁盘中的位置不是连续的,每次都要重新寻址,所以是随机I/O。
数据写入到redo log里面就有了保障,刷盘就不需要那么频繁了,提升了系统的吞吐量。MySQL很多地方都利用的这一点

双写缓冲问题

InnoDB 的页和操作系统的页大小不一致,InnoDB 页大小一般为 16K,操作系统页 大小为 4K,InnoDB 的页写入到磁盘时,一个页需要分 4 次写。如果存储引擎正在写入页的数据到磁盘时发生了宕机,可能出现页只写了一部分的 情况,比如只写了 4K,就宕机了,这种情况叫做部分写失效(partial page write),可能会导致数据丢失,如果这个页本身已经损坏了,用redo log来做崩溃恢复是没有意义的。所以在对于应用 redo log 之前,需要一个页的副本。如果出现了写入失效,就用页的副本来还原这个页,然后再应用 redo log。这个页的副本就是 double write,InnoDB 的双写技术。通过它实现了数据页的可靠性。

跟redo log一样,double write 由两部分组成,一部分是内存的 double write,一个部分是磁盘上的 double write。因为 double write 是顺序写入的,不会带来很大的开销
Binlog

binlog 以事件的形式记录了所有的 DDL 和 DML 语句(因为它记录的是操作而不是 数据值,属于逻辑日志),可以用来做主从复制和数据恢复。 跟 redo log 不一样,它的文件内容是可以追加的,没有固定大小限制。 在开启了 binlog 功能的情况下,我们可以把 binlog 导出成 SQL 语句,把所有的操 作重放一遍,来实现数据的恢复。 binlog 的另一个功能就是用来实现主从复制,它的原理就是从服务器读取主服务器 的 binlog,然后执行一遍。

例如一条语句:update teacher set name='盆鱼宴' where id=1;
1、先查询到这条数据,如果有缓存,也会用到缓存。
2、把 name 改成盆鱼宴,然后调用引擎的 API 接口,写入这一行数据到内存,同时记录 redo log。这时 redo log 进入 prepare 状态,然后告诉执行器,执行完成了,可以随时提交。
3、执行器收到通知后记录 binlog,然后调用存储引擎接口,设置 redo log为 commit状态。
4、更新完成。
  1. 先记录到内存,再写日志文件。
  2. 记录 redo log 分为两个阶段。
  3. 存储引擎和 Server 记录不同的日志。
  4. 先记录 redo,再记录 binlog。

index

页分裂

InnoDB在叶子上存储数据。一个节点就是一页。一个页可以存储多行数据,按照主键的顺序存储。当数据是顺序插入的时候,一页写满了,就申请一个新的页。如果是随机插入,在指定位置的页已经写满了(或者到达了分裂阈值)的时候,就会发生页结构的调整(即B+Tree的节点的分裂)

页的内部原理

页可以空或者填充满(100%),行记录会按照主键顺序来排列。这里有个重要的属性:MERGE_THRESHOLD。该参数的默认值是50%页的大小,它在InnoDB的合并操作中扮演了很重要的角色

当你插入数据时,如果数据(大小)能够放的进页中的话,那他们是按顺序将页填满的。根据B树的特性,它可以自顶向下遍历,但也可以在各叶子节点水平遍历。因为每个叶子节点都有着一个指向包含下一条(顺序)记录的页的指针。那么如果我突然执行删除,页会出现什么情况?

页合并

当你删了一行记录时,实际上记录并没有被物理删除,记录被标记(flaged)为删除并且它的空间变得允许被其他记录声明使用。当页中删除的记录达到MERGE_THRESHOLD(默认页体积的50%),InnoDB会开始寻找最靠近的页(前或后)看看是否可以将两个页合并以优化空间使用。

页分裂

当前页有空间但是容纳不下我要插入的数据时。下一页又是满的无法插入数据时。

1. 创建新页
2. 判断当前页(页#10)可以从哪里进行分裂(记录行层面)
3. 移动记录行
4. 重新定义页之间的关系
页分裂会发生在插入或更新,并且造成页的错位(dislocation,落入不同的区)论id设计的重要性

感谢这篇文章

索引用到文件排序 Using filesort
Using filresort代表:不能使用索引来排序,用到了额外的排序。
例如:查询用到了a字段上的idx_a,但是后面有order by b。
一些优化方式:
1.建立联合索引idx(a,b)
2.不使用MySQL的排序,改成在代码中排序
为什么要固定Page大小,而不是需要多少数据,读取多少数据(按需读取)为什么16k
设置页的大小与磁盘的预读取特性有关系。局部性原理认为:当一个数据被用到时,其附近的数据也通常会马上被使用。所以顺序读取附近的数据,可以提升I/O效率。每次都至少读取一页
默认的16KB大小,在涉及表扫描和批量更新的业务场景中更实用,效率已经很高了
官网解释
https://dev.MySQL.com/doc/refman/5.7/en/innodb-parameters.html#sysvar_innodb_page_size
表没主键索引问题
如果我们定义了主键(PRIMARY KEY),那么 InnoDB 会选择主键作为聚集索引。 
如果没有显式定义主键,则 InnoDB 会选择第一个不包含有 NULL 值的唯一索引作为主键索引、
如果也没有这样的唯一索引,则 InnoDB 会选择内置 6 字节长的 ROWID 作为隐 藏的聚集索引,它会随着行记录的写入而主键递增。
辅助索引什么情况下查询需要回表,什么情况下不需要回表
例如索引是index(a,b,c),select 索引里面的列
(包括select a,select b,select c,select a,b select a,c select b,c,select a,b,c),
都不需要到主键索引的叶子节点获取完整数据,此时不需要回表。

事务与锁

共享锁 有什么意义呢
举个例子,order_info 和 order_detail 有逻辑的主外键关系。
在操作order_detail的时候,不希望order_info被其他的事务修改,可以用共享锁,阻塞其他的事务修改
innodb有了MVCC为什么还需要LBCC
快照读(普通的select)用MVCC保证读一致性。
加锁的读和增删改,用lock保证读一致性
MySQL ACID怎么保证
原子性(Atomicity,或称不可分割性) 通过undo log 实现
一致性(Consistency)通过AID特性与应用保证一致性
隔离性(Isolation)MVCC
持久性(Durability)通过redo log实现

优化

连接池大小应该设置多大
我们不妨想一下,为啥 Nginx 内部仅仅使用了 4 个线程,其性能就大大超越了 100 个进程的 Apache HTTPD 呢?
在一核 CPU 的机器上,顺序执行A和B永远比通过时间分片切换“同时”执行A和B要快,其中原因,学过操作系统这门课程的童鞋应该很清楚。一旦线程的数量超过了 CPU 核心的数量,再增加线程数系统就只会更慢,而不是更快,因为这里涉及到上下文切换耗费的额外的性能。
在加上现在的磁盘又基本是SSD,无需寻址和没有旋回耗时的确意味着更少的阻塞所以更少的线程(更接近于CPU核心数)会发挥出更高的性能,只有当阻塞密集时,更多的线程数才能发挥出更好的性能。

连接数 = ((核心数 * 2) + 有效磁盘数)

当然,这取决于线上环境,开发还是设置大一点,因为大家都在联调测试。

Count问题
MyISAM之所以可以把表中的总行数记录下来供COUNT(*)查询使用,那是因为MyISAM数据库是表级锁,不会有并发的数据库行数修改,所以查询得到的行数是准确的。
但是,对于InnoDB来说,就不能做这种缓存操作了,因为InnoDB支持事务,其中大部分操作都是行级锁,所以可能表的行数可能会被并发修改,那么缓存记录下来的总行数就不准确了。
我们知道,InnoDB中索引分为聚簇索引(主键索引)和非聚簇索引(非主键索引),聚簇索引的叶子节点中保存的是整行记录,而非聚簇索引的叶子节点中保存的是该行记录的主键的值。
所以,相比之下,非聚簇索引要比聚簇索引小很多,所以MySQL会优先选择最小的非聚簇索引来扫表。所以,当我们建表的时候,除了主键索引以外,创建一个非主键索引还是有必要的。
COUNT(*)和COUNT(1)和count(字段)区别

InnoDB handles SELECT COUNT(*) and SELECT COUNT(1) operations in the same way. There is no performance difference. 官方发话

画重点:same way,no performance difference所以,对于COUNT(1)和COUNT(*),MySQL的优化是完全一样的,根本不存在谁比谁快!

建议使用COUNT(*)!因为这个是SQL92定义的标准统计行数的语法。

COUNT(字段)多了一个步骤就是判断所查询的字段是否为NULL所以他的性能要比COUNT(*)慢。

存储引擎

InnoDB和MyISAM区别
  1. InnoDB 支持事务,MyISAM 不支持事务。这是 MySQL 将默认存储引擎从 MyISAM 变成 InnoDB 的重要原因之一
  2. InnoDB 支持外键,而 MyISAM 不支持。对一个包含外键的 InnoDB 表转为 MYISAM 会失败
  3. InnoDB 是聚集索引数据跟索引存放在一起,MyISAM 是非聚集索引数据和索引分开。
  4. InnoDB 最小的锁粒度是行锁,MyISAM 最小的锁粒度是表锁。
  5. 由于索引分开的MyISAM 在内存中存储了row_count 值的 meta 信息可以直接获取到总行数
底层使用B+树不用B树
  1. AVL(平衡二叉树)树解决了索引频繁的修改,和查询效率不高的问题。
  2. B树相对AVL树,每个节点存储的数据更多,路数更多,树的深度减少,减少I/O次数,提升效率。
  3. B+树相对B树,效率更稳定,因为数据存在叶子节点;排序能力更强,因为叶子节点有下一个数据区的指针;读写能力更强,因为根节点和枝节点不用存数据,所以可以保存更多的关键字。扫库扫表能力更强,因为数据都存在叶子节点,而且叶子节点都有下一个数据区的指针,所以遍历起来很方便。
B+树数据存放量问题
假设我们的树深度为2 我们id用bigint数据类型占8字节 我们一条数据1k大小
指针在InnoDB中占6字节 一个页为16k=16384字节 
我们一页就可以存放16384/14=1170个指向记录 注意!这是根节点
每个指向记录下面存(16k/1k)*1170=18720记录 
如果深度为3那么就是1170*1170*16=21902400条记录
所以说我们2000万的数据如果通过id搜索也就3次io。

猜你喜欢

转载自blog.csdn.net/yakax/article/details/104400807