1 事务特性
1.1 原子性
mysql通过WAL(Write Ahead Log,写前日志)来实现。如果事务提交了,但buffer pool中脏页没有刷盘,则从redo日志恢复。MySQL InnoDB 存储引擎中,即使事务尚未提交,某些情况下缓冲池(Buffer Pool)中的脏页也可能被刷写到磁盘。InnoDB 使用多个后台线程来管理缓冲池中的页面刷新,包括 master thread
和 page cleaner threads
。这些线程会定期检查缓冲池,将脏页刷新到磁盘,以确保缓冲池中有足够的干净页面用于新的读取操作。这个过程并不依赖于事务是否已经提交,这个时候就可以通过undo日志来保证。
1.2 持久性
MySQL InnoDB 存储引擎使用两阶段提交协议来确保 Redo Log 和 Binlog 的一致性。具体来说:
在准备阶段,InnoDB会先将事务记录到 Redo Log 中,并标记为“prepare”状态。
然后,MySQL Server层会将同样的事务记录到 Binlog 中。
在提交阶段,InnoDB会再次更新 Redo Log,标记事务为“commit”。
通过这种方式,即使在提交过程中发生崩溃,也可以根据 Redo Log 和 Binlog 的状态进行正确的恢复操作。
1.3 隔离性
从低到高:读未提交、读提交、可重复读、可串行化。
1.4 一致性
约束一致性和数据一致性,通过原子性、隔离性和持久性来保证一致性。
2 事务隔离
脏读:一个事务访问到了另一个事务未提交的数据。
不可重复读:一个事务读取同一条记录2次,结果不一致。
幻读:一个事务读取2次,得到记录条数据不一致。
mysql默认隔离级别式Repeatable read,会出现幻读,因为当前事务正在读,不允许其他事务写,允许其他事务读,但没有约束其他事务的insert操作。mysql额外使用间隙锁(Gap lock)来防止幻读,但Gap lock会造成缩表。最佳事件是采用行级锁。
3 锁机制
单版本来保证事务之间互相隔离。行锁又分为共享锁(读锁,读不限制,但写会阻塞)和排他锁(写锁,读和写都阻塞)。共享锁和排他锁都是悲观锁,乐观锁是指在数据更新提交时才进行冲突检测,发生冲突则提示错误。区别就是悲观锁时先锁再说,乐观锁时先修改再说。
为了允许表锁和行锁并存,实现多粒度锁机制,Innodb内部使用了意向共享锁(表锁)、意向排他锁(表锁)、自增锁(特殊的表锁)。就是说如果事务想对数据行进行加行锁,就需要对该行对应的表进行加意向锁。
3.1 自增锁
自增锁的实现形式,mysql8之前默认值是1(连续模式),之后是2(交叉模式,所有的insert都不会使用AUTO-inc自增锁,而使用轻量的mutex锁,这样多条insert可以并发执行)
连续模式下,insert确定条数据时,不会使用自增锁,会直接将insert语句锁需要的自增锁预留出来。实际分配ID的过程,innodb会使用轻量级的Mutex锁,ID分配完,Mutex锁自动释放。但insert into select会用到自增锁。我们的系统一般是对自增值连续性并不敏感且binlog一般使用ROW模式,故可以调整为2以提升性能。binlog如果是statement或Mixed,在主从同步会产生数据不一致的情况。
3.2 行锁
Innodb行级锁算法,还是那位博主的视频。Record Lock:单个记录的锁。Gap Lock:间隙锁,锁定一个范围,不包括记录本身(不锁数据,仅仅锁数据前面的Gap)。Next-Key Lock:同时锁数据,并锁住数据前面的Gap(记录锁+范围锁的)。
从下图可以知道,尽量使用主键进行更新数据。
- 主键+RR:主键上加X锁,是排他性锁
- 唯一键+RR:在唯一索引上增加排他性锁(X锁),再在主键记录上加X锁。
- 非唯一索引+RR:第一步:通过非唯一索引条件定位到第一