复习时自己产生的问题
首先,InnnoDB 的数据都是放在磁盘上的,InnoDB 操作数据有一个最小的逻辑单位,叫做页(索引页和数据页)。我们对于数据的操作,不是每次都直接操作磁盘,因为磁盘的速度太慢了。InnoDB 使用了一种缓冲池的技术,也就是把磁盘读到的页放到一 块内存区域里面。这个内存区域就叫 Buffer Pool,下一次读取相同的页,先判断是不是在缓冲池里面,如果是,就直接读取,不用再 次访问磁盘。 修改数据的时候,先修改缓冲池里面的页。内存的数据页和磁盘数据不一致的时候, 我们把它叫做脏页。InnoDB 里面有专门的后台线程把 Buffer Pool 的数据写入到磁盘, 每隔一段时间就一次性地把多个修改写入磁盘,这个动作就叫做刷脏。 Buffer Pool 是 InnoDB 里面非常重要的一个结构,它的内部又分成几块区域。这里 我们趁机到官网来认识一下 InnoDB 的内存结构和磁盘结构。
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一样。
如果这个数据页不是唯一索引,不存在数据重复的情况,也就不需要从磁盘加载索 引页判断数据是不是重复(唯一性检查)。这种情况下可以先把修改记录在内存的缓冲 池中,从而提升更新语句(Insert、Delete、Update)的执行速度。把 Change Buffer 记录到数据页的操作叫做 merge。在访问这个数据页的时候,或者通过后台线程、或者数据库 shut down、 redo log 写满时触发merge。
如果数据库大部分索引都是非唯一索引,并且业务是写多读少,不会在写数据后立 刻读取,就可以使用 Change Buffer(写缓冲)。写多读少的业务,调大这个值。 Change Buffer 占 Buffer Pool 的比例,默认 25%
索引应该是放在磁盘的。InnoDB本身不支持哈希索引,所有索引检索都走B树,Adaptive Hash index可以认为是“索引的索引”。哈希索引就是采用一定的哈希算法,把键值换算成新的哈希值,检索时不需要类似B+树那样从根节点到叶子节点逐级查找,只需一次哈希算法即可立刻定位到相应的位置,速度非常快。
如果 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 以事件的形式记录了所有的 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、更新完成。
InnoDB在叶子上存储数据。一个节点就是一页。一个页可以存储多行数据,按照主键的顺序存储。当数据是顺序插入的时候,一页写满了,就申请一个新的页。如果是随机插入,在指定位置的页已经写满了(或者到达了分裂阈值)的时候,就会发生页结构的调整(即B+Tree的节点的分裂)
页可以空或者填充满(100%),行记录会按照主键顺序来排列。这里有个重要的属性:MERGE_THRESHOLD
。该参数的默认值是50%页的大小,它在InnoDB的合并操作中扮演了很重要的角色
当你插入数据时,如果数据(大小)能够放的进页中的话,那他们是按顺序将页填满的。根据B树的特性,它可以自顶向下遍历,但也可以在各叶子节点水平遍历。因为每个叶子节点都有着一个指向包含下一条(顺序)记录的页的指针。那么如果我突然执行删除,页会出现什么情况?
当你删了一行记录时,实际上记录并没有被物理删除,记录被标记(flaged)为删除并且它的空间变得允许被其他记录声明使用。当页中删除的记录达到MERGE_THRESHOLD
(默认页体积的50%),InnoDB会开始寻找最靠近的页(前或后)看看是否可以将两个页合并以优化空间使用。
当前页有空间但是容纳不下我要插入的数据时。下一页又是满的无法插入数据时。
1. 创建新页
2. 判断当前页(页#10)可以从哪里进行分裂(记录行层面)
3. 移动记录行
4. 重新定义页之间的关系
页分裂会发生在插入或更新,并且造成页的错位(dislocation,落入不同的区)论id设计的重要性
感谢这篇文章
Using filresort代表:不能使用索引来排序,用到了额外的排序。
例如:查询用到了a字段上的idx_a,但是后面有order by b。
一些优化方式:
1.建立联合索引idx(a,b)
2.不使用MySQL的排序,改成在代码中排序
设置页的大小与磁盘的预读取特性有关系。局部性原理认为:当一个数据被用到时,其附近的数据也通常会马上被使用。所以顺序读取附近的数据,可以提升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被其他的事务修改,可以用共享锁,阻塞其他的事务修改
快照读(普通的select)用MVCC保证读一致性。
加锁的读和增删改,用lock保证读一致性
原子性(Atomicity,或称不可分割性) 通过undo log 实现
一致性(Consistency)通过AID特性与应用保证一致性
隔离性(Isolation)MVCC
持久性(Durability)通过redo log实现
我们不妨想一下,为啥 Nginx 内部仅仅使用了 4 个线程,其性能就大大超越了 100 个进程的 Apache HTTPD 呢?
在一核 CPU 的机器上,顺序执行A和B永远比通过时间分片切换“同时”执行A和B要快,其中原因,学过操作系统这门课程的童鞋应该很清楚。一旦线程的数量超过了 CPU 核心的数量,再增加线程数系统就只会更慢,而不是更快,因为这里涉及到上下文切换耗费的额外的性能。
在加上现在的磁盘又基本是SSD,无需寻址和没有旋回耗时的确意味着更少的阻塞所以更少的线程(更接近于CPU核心数)会发挥出更高的性能,只有当阻塞密集时,更多的线程数才能发挥出更好的性能。
连接数 = ((核心数 * 2) + 有效磁盘数)
当然,这取决于线上环境,开发还是设置大一点,因为大家都在联调测试。
MyISAM之所以可以把表中的总行数记录下来供COUNT(*)查询使用,那是因为MyISAM数据库是表级锁,不会有并发的数据库行数修改,所以查询得到的行数是准确的。
但是,对于InnoDB来说,就不能做这种缓存操作了,因为InnoDB支持事务,其中大部分操作都是行级锁,所以可能表的行数可能会被并发修改,那么缓存记录下来的总行数就不准确了。
我们知道,InnoDB中索引分为聚簇索引(主键索引)和非聚簇索引(非主键索引),聚簇索引的叶子节点中保存的是整行记录,而非聚簇索引的叶子节点中保存的是该行记录的主键的值。
所以,相比之下,非聚簇索引要比聚簇索引小很多,所以MySQL会优先选择最小的非聚簇索引来扫表。所以,当我们建表的时候,除了主键索引以外,创建一个非主键索引还是有必要的。
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(*)
慢。
假设我们的树深度为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。