1、事务4大特性
事务4大特性: 原子性、一致性、隔离性、持久性
原⼦性: 事务是最⼩的执⾏单位,不允许分割。事务的原⼦性确保动作要么全部完成,要么全不执行
一致性: 执⾏事务前后,数据保持⼀致,多个事务对同⼀个数据读取的结果是相同的;
隔离性: 并发访问数据库时,⼀个⽤户的事务不被其他事务所⼲扰,各并发事务之间数据库是独⽴的;
持久性: ⼀个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发⽣故障也不应该对其有任何影响。
实现保证: MySQL的存储引擎InnoDB使用重做日志保证一致性与持久性,回滚日志保证原子性,使用各种锁来保证隔离性。
2、事务隔离级别
读未提交: 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。
读已提交: 允许读取并发事务已经提交的数据,可以阻⽌脏读,但是幻读或不可重复读仍有可能发⽣。
可重复读: 同⼀字段的多次读取结果都是⼀致的,除⾮数据是被本身事务⾃⼰所修改,可以阻⽌脏读和不可重复读,会有幻读。
串行化: 最⾼的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执⾏,这样事务之间就完全不可能产⽣⼲扰。
| 隔离级别 | 并发问题 |
|---|---|
| 读未提交 | 可能会导致脏读、幻读或不可重复读 |
| 读已提交 | 可能会导致幻读或不可重复读 |
| 可重复读 | 可能会导致幻读 |
| 可串行化 | 不会产⽣⼲扰 |
3、默认隔离级别-RR
默认隔离级别: 可重复读;
同⼀字段的多次读取结果都是⼀致的,除⾮数据是被本身事务⾃⼰所修改;
可重复读是有可能出现幻读的,如果要保证绝对的安全只能把隔离级别设置成SERIALIZABLE;这样所有事务都只能顺序执行,自然不会因为并发有什么影响了,但是性能会下降许多。
第二种方式,使用MVCC解决快照读幻读问题(如简单select),读取的不是最新的数据。维护一个字段作为version,这样可以控制到每次只能有一个人更新一个版本。
select id from table_xx where id = ? and version = V
update id from table_xx where id = ? and version = V+1
第三种方式,如果需要读最新的数据,可以通过GapLock+Next-KeyLock可以解决当前读幻读问题,
select id from table_xx where id > 100 for update;
select id from table_xx where id > 100 lock in share mode;
4、RR和RC使用场景
事务隔离级别RC(read commit)和RR(repeatable read)两种事务隔离级别基于多版本并发控制MVCC(multi-version concurrency control)来实现。
| RC | RR | |
|---|---|---|
| 实现 | 多条查询语句会创建多个不同的ReadView | 仅需要一个版本的ReadView |
| 粒度 | 语句级读一致性 | 事务级读一致性 |
| 准确性 | 每次语句执行时间点的数据 | 第一条语句执行时间点的数据 |
5、行锁,表锁,意向锁
InnoDB⽀持⾏级锁(row-level locking)和表级锁,默认为⾏级锁
InnoDB按照不同的分类的锁:
共享/排它锁(Shared and Exclusive Locks):行级别锁,
意向锁(Intention Locks),表级别锁
间隙锁(Gap Locks),锁定一个区间
记录锁(Record Locks),锁定一个行记录
表级锁:(串行化)
Mysql中锁定 粒度最大的一种锁,对当前操作的整张表加锁,实现简单 ,资源消耗也比较少,加锁快,不会出现死锁 。其锁定粒度最大,触发锁冲突的概率最高,并发度最低,MyISAM和 InnoDB引擎都支持表级锁。
行级锁:(RR、RC)
Mysql中锁定 粒度最小 的一种锁,只针对当前操作的行进行加锁。 行级锁能大大减少数据库操作的冲突。其加锁粒度最小,并发度高,但加锁的开销也最大,加锁慢,会出现死锁。 InnoDB支持的行级锁,包括如下几种:
记录锁(Record Lock): 对索引项加锁,锁定符合条件的行。其他事务不能修改和删除加锁项;
间隙锁(Gap Lock): 对索引项之间的“间隙”加锁,锁定记录的范围,不包含索引项本身,其他事务不能在锁范围内插入数据。
Next-key Lock: 锁定索引项本身和索引范围。即Record Lock和Gap Lock的结合。可解决幻读问题。
InnoDB 支持多粒度锁(multiple granularity locking),它允许行级锁与表级锁共存,而意向锁就是其中的一种表锁。
共享锁( shared lock, S )锁允许持有锁读取行的事务。加锁时将自己和子节点全加S锁,父节点直到表头全加IS锁
排他锁( exclusive lock, X )锁允许持有锁修改行的事务。 加锁时将自己和子节点全加X锁,父节点直到表头全加IX锁
意向共享锁(intention shared lock, IS):事务有意向对表中的某些行加共享锁(S锁)
意向排他锁(intention exclusive lock, IX):事务有意向对表中的某些行加排他锁(X锁)
| 互斥性 | 共享锁(S) | 排它锁(X) | 意向共享锁IS | 意向排他锁IX |
|---|---|---|---|---|
| 共享锁(S) | ✅ | ❌ | ✅ | ❌ |
| 排它锁(X) | ❌ | ❌ | ❌ | ❌ |
| 意向共享锁IS | ✅ | ❌ | ✅ | ✅ |
| 意向排他锁IX | ❌ | ❌ | ✅ | ✅ |
6、MVCC多版本并发控制
MVCC是一种多版本并发控制机制,通过事务的可见性看到自己预期的数据,能降低其系统开销.(RC和RR级别工作)
InnoDB的MVCC,是通过在每行记录后面保存系统版本号(可以理解为事务的ID),每开始一个新的事务,系统版本号就会自动递增,事务开始时刻的系统版本号会作为事务的ID。这样可以确保事务读取的行,要么是在事务开始前已经存在的,要么是事务自身插入或者修改过的,防止幻读的产生。
1.MVCC手段只适用于Msyql隔离级别中的读已提交(Read committed)和可重复读(Repeatable Read).
2.Read uncimmitted由于存在脏读,即能读到未提交事务的数据行,所以不适用MVCC.
3.简单的select快照度不会加锁,删改及select for update等需要当前读的场景会加锁
原因是MVCC的创建版本和删除版本只要在事务提交后才会产生。客观上,mysql使用的是乐观锁的一整实现方式,就是每行都有版本号,保存时根据版本号决定是否成功。Innodb的MVCC使用到的快照存储在Undo日志中,该日志通过回滚指针把一个数据行所有快照连接起来。
版本链
在InnoDB引擎表中,它的聚簇索引记录中有两个必要的隐藏列:
trx_id
这个id用来存储的每次对某条聚簇索引记录进行修改的时候的事务id。
roll_pointer
每次对哪条聚簇索引记录有修改的时候,都会把老版本写入undo日志中。这个roll_pointer就是存了一个指针,它指向这条聚簇索引记录的上一个版本的位置,通过它来获得上一个版本的记录信息。(注意插入操作的undo日志没有这个属性,因为它没有老版本)
每次修改都会在版本链中记录。SELECT可以去版本链中拿记录,这就实现了读-写,写-读的并发执行, 提升了系统的性能。
- [ 萱儿AXW ]