MySQL45讲学习笔记
redo log
InnoDB特有的,在引擎层
掌柜、粉板、账本、赊账
checkpoint、write pos
binlog
归档日志
在Server层
- redo log是InnoDB引擎特有的;binlog是MySQL的Server层实现的,所有引擎都可以使用。
- redo log是物理日志,记录的是“在某个数据页上做了什么修改”;binlog是逻辑日志,记录的是这个语句的原始逻辑,比如“给ID=2这一行的c字段加1 ”。
- redo log是循环写的,空间固定会用完;binlog是可以追加写入的。“追加写”是指binlog文件写到一定大小后会切换到下一个,并不会覆盖以前的日志。
update语句的执行流程图,浅色框表示是在InnoDB内部执行的,深色框表示是在执行器中执行的
prepare和commit,即”两阶段提交”
事务
一致性视图(可重复读级别)
锁
全局锁、表级锁、行锁
全局锁
MySQL提供一个加全局读锁的方法,命令是 Flush tables with read lock (FTWRL)
全局锁的典型使用场景是,做全库逻辑备份
缺点是备份期间所有东西都动不了,包括binlog
而一致性视图(read-view)可以解决:
官方自带的逻辑备份工具是mysqldump。当mysqldump使用参数–single-transaction的时候,导数据之前就会启动一个事务,来确保拿到一致性视图。而由于MVCC的支持,这个过程中数据是可以正常更新的
但是single-transaction方法只适用于所有的表使用事务引擎的库
既然要全库只读,为什么不使用set global readonly=true的方式呢
- readonly的值会被用来做其他逻辑,比如用来判断一个库是主库还是备库。修改global变量的方式影响面更大
- 在异常处理机制上有差异。如果执行FTWRL命令之后由于客户端发生异常断开,那么MySQL会自动释放这个全局锁,整个库回到可以正常更新的状态。而将整个库设置为readonly之后,如果客户端发生异常,则数据库就会一直保持readonly状态,这样会导致整个库长时间处于不可写状态,风险较高
表级锁
MySQL里面表级别的锁有两种:一种是表锁,一种是元数据锁(meta data lock,MDL)
InnoDB这种支持行锁的引擎,一般不使用lock tables命令来控制并发,毕竟锁住整个表的影响面还是太大
MDL不需要显式使用,在访问一个表的时候会被自动加上
坑:给一个小表加个字段,导致整个库挂了
如何安全地给小表加字段?
- 解决长事务,事务不提交,就会一直占着MDL锁
- 在alter table语句里面设定等待时间,如果在这个指定的等待时间里面能够拿到MDL写锁最好,拿不到也不要阻塞后面的业务语句,先放弃。之后开发人员或者DBA再通过重试命令重复这个过程。
1 | ALTER TABLE tbl_name NOWAIT add column ... |
行锁
行锁就是针对数据表中行记录的锁。这很好理解,比如事务A更新了一行,而这时候事务B也要更新同一行,则必须等事务A的操作完成后才能进行更新。
在InnoDB事务中,行锁是在需要的时候才加上的,但并不是不需要了就立刻释放,而是要等到事务结束时才释放。这个就是两阶段锁协议
即:update结束后,commit了才算释放行锁
所以:
如果你的事务中需要锁多个行,要把最可能造成锁冲突、最可能影响并发度的锁尽量往后放
死锁及检测
策略:
- 直接进入等待,直到超时(InnoDB默认50s)
- 发起死锁检测,发现死锁后,主动回滚死锁链条中的某一个事务,让其他事务得以继续执行。将参数innodb_deadlock_detect设置为on,表示开启这个逻辑。
- 意味着每当一个事务被锁的时候,就要看看它所依赖的线程有没有被别人锁住,如此循环,最后判断是否出现了循环等待,也就是死锁。
- 如果所有事务都要更新同一行的场景呢?那么每个新来的被堵住的线程,都要判断会不会由于自己的加入导致了死锁,这是一个时间复杂度是O(n)的操作
- 解决:
- 如果你能确保这个业务一定不会出现死锁,可以临时把死锁检测关掉
- 控制并发度
可见性
关于一致性视图
关键词:MVCC、快照、数组…
InnoDB为每个事务构造了一个数组,用来保存这个事务启动瞬间,当前正在“活跃”的所有事务ID。“活跃”指的就是,启动了但还没提交。
数组里面事务ID的最小值记为低水位,当前系统里面已经创建过的事务ID的最大值加1记为高水位。
这个视图数组和高水位,就组成了当前事务的一致性视图(read-view)。
而数据版本的可见性规则,就是基于数据的row trx_id和这个一致性视图的对比结果得到的
注意:更新操作!
更新数据都是先读后写的,而这个读,只能读当前的值,称为“当前读”(current read)
可重复读的核心就是一致性读(consistent read);而事务更新数据的时候,只能用当前读。如果当前的记录的行锁被其他事务占用的话,就需要进入锁等待。
读提交的逻辑和可重复读的逻辑类似,它们最主要的区别是:
- 在可重复读隔离级别下,只需要在事务开始的时候创建一致性视图,之后事务里的其他查询都共用这个一致性视图;
- 在读提交隔离级别下,每一个语句执行前都会重新算出一个新的视图。
InnoDB的行数据有多个版本,每个数据版本有自己的row trx_id,每个事务或者语句有自己的一致性视图。普通查询语句是一致性读,一致性读会根据row trx_id和一致性视图确定数据版本的可见性。
- 对于可重复读,查询只承认在事务启动前就已经提交完成的数据;
- 对于读提交,查询只承认在语句启动前就已经提交完成的数据;
而当前读,总是读取已经提交完成的最新版本。
唯一索引和普通索引的选择
关键词:change buffer
这两类索引在查询能力上是没差别的,主要考虑的是对更新性能的影响
如果要在这张表中插入一个新记录(4,400)的话,InnoDB的处理流程是怎样的。
第一种情况是,这个记录要更新的目标页在内存中。这时,InnoDB的处理流程如下:
- 对于唯一索引来说,找到3和5之间的位置,判断到没有冲突,插入这个值,语句执行结束;
- 对于普通索引来说,找到3和5之间的位置,插入这个值,语句执行结束。
这样看来,普通索引和唯一索引对更新语句性能影响的差别,只是一个判断,只会耗费微小的CPU时间。
但,这不是我们关注的重点。
第二种情况是,这个记录要更新的目标页不在内存中。这时,InnoDB的处理流程如下:
- 对于唯一索引来说,需要将数据页读入内存,判断到没有冲突,插入这个值,语句执行结束;
- 对于普通索引来说,则是将更新记录在change buffer,语句执行就结束了。
将数据从磁盘读入内存涉及随机IO的访问,是数据库里面成本最高的操作之一。change buffer因为减少了随机磁盘访问,所以对更新性能的提升是会很明显的。
merge的时候是真正进行数据更新的时刻
在一个数据页做merge之前,change buffer记录的变更越多(也就是这个页面上要更新的次数越多),收益就越大。
假设一个业务的更新模式是写入之后马上会做查询,那么即使满足了条件,将更新先记录在change buffer,但之后由于马上要访问这个数据页,会立即触发merge过程。这样随机访问IO的次数不会减少,反而增加了change buffer的维护代价