目录
表级锁&行级锁
排他锁&共享锁
InnoDB行级锁
行级锁(record lock):
间隙锁(gap lock):
意向锁
InnoDB表级锁
MVCC(多版本并发控制)
已提交读的MVCC:
可重复读的MVCC:
死锁
锁的优化建议(持续更新
事务要允许并发执行就会导致数据的安全性&一致性和并发效率问题,导致脏读、不可重复读、幻读。
串行化:由锁来实现,会给所有的事务排序,导致并发效率低但是数据的安全性高
未提交读:没有做任何并发处理 ,并发效率高,数据的安全性最低
常用:已提交读(常见oracle)、可重复读(MySQL)
结合了数据的安全性&一致性 + 并发的效率 MVCC多版本控制机制实现的可重复读
表级锁&行级锁
表级锁:对整张表加锁。开销小,加锁快,不会出现死锁;锁粒度大,发生锁冲突的概率高,并发度 低。
行级锁:对某行记录加锁。开销大,加锁慢,会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并 发度高。
排他锁&共享锁
排它锁(Exclusive),X 锁,写锁。
共享锁(Shared),S 锁,读锁。
X和S锁之间有以下的关系: SS可以兼容的,XS、SX、XX之间是互斥的
一个事务对数据对象 O 加了 S 锁,可以对 O 进行读取操作但不能进行更新操作。加锁期间其它事 务能对O 加 S 锁但不能加 X 锁。
一个事务对数据对象 O 加了 X 锁,就可以对 O 进行读取和更新。加锁期间其它事务不能对 O 加任 何锁。
显示加锁:select ... lock in share mode强制获取共享锁,select ... for update获取排它锁
InnoDB行级锁
行级锁(record lock):
InnoDB存储引擎支持事务处理,表支持行级锁定,并发能力更好;
1. InnoDB的行锁是加在索引项上面的,是给索引在加索,并不是给表的行记录加锁;只有通过索引条件检索数据,InnoDB使用的才是行级锁,否则使用的是表锁;
2、由于InnoDB的行锁实现是针对索引字段添加的锁,不是针对行记录加的锁,因此虽然访问的是 InnoDB引擎下表的不同行,但是如果使用相同的索引字段作为过滤条件,依然会发生锁冲突,只能串行进行,不能并发进行。
3、即使SQL中使用了索引,但是经过MySQL的优化器后,如果认为全表扫描比使用索引效率更高,此 时会放弃使用索引,因此也不会使用行锁,而是使用表锁,比如对一些很小的表,MySQL就不会去使用 索引。
间隙锁(gap lock):
InnoDB串行化隔离级别 如何解决幻读? 间隙锁
1. 当使用范围查询条件,并请求共享锁或者排他锁时,InnoDB会给符合条件的已有数据记录的索引项加索;对于键值在条件范围内但并不存在的记录,叫做“间隙(GAP)” ,InnoDB 也会对 这个“间隙”加锁,这种锁机制就是所谓的间隙锁。
举例来说, 假如 user 表中只有 101 条记录, 其 userid 的值分别是 1,2,...,100,101, 下面的 SQL:
select * from user where userid > 100 for update;
这是一个范围条件的检索,InnoDB不仅会对符合条件的userid值为101的记录加锁,也会对userid大于101的“间隙”加锁,防止其他事务在表的末尾增加数据。
InnoDB使用间隙锁的目的,为了防止幻读,以满足串行化隔离级别的要求,对于上面的例子,要是不 使用间隙锁,如果其他事务插入了 userid 大于 100 的任何记录,那么本事务如果再次执行上述语句, 就会发生幻读。
2. 当使用等值查询时,再次插入age=18时会被阻塞住,会在等值18的两边加上间隙锁
select * from user where age = 18;
(如果:
1. age是辅助索引(值可重复) 会加 record lock + gap lock
2. age是主键索引或者唯一索引(值是不允许重复的),只有record lock)
(辅助索引值相等的情况下(辅助索引是可以重复的),主键按升序排列)比如说插入一个age=15的,会阻塞,因为他的主键id会升序排列24,会在已有的15和18之间,由于有间隙锁,所以无法加入。
意向锁
意向锁的存在是为了协调行锁和表锁的关系,支持多粒度(表锁与行锁)的锁并存。
(其他事务在获取一个表锁的时候,可以拿来跟意向锁进行快速的对比来判断,我能不能获取当前表的s或者x锁。)
1)意向共享锁(IS锁):事务在给一行记录加共享锁前,必须先取得该表的 IS 锁。
2)意向排他锁(IX锁):事务在给一行记录加排他锁前,必须先取得该表的 IX 锁。
意向锁和共享锁、排他锁的兼容关系:
1、意向锁是由InnoDB存储引擎获取行锁之前自己获取的;
2、意向锁之间都是兼容的,不会产生冲突;
3、意向锁存在的意义是为了更高效的获取表锁(表格中的X和S指的是表锁,不是行锁!!!)
4、意向锁是表级锁,协调表锁和行锁的共存关系。主要目的是显示事务正在锁定某行或者试图锁定某 行。
意向锁如何解决表锁涉及的效率问题?
需要在大量数据中检验没有被其他事务获取过X锁,有了意向锁之后,事务在给行加锁之前,都活获取表的IS或者IX锁,所以当事务要获取表的X锁时,不需要在检查表中那些行锁被(X或者S)占用,只需要快速检查IX和IS锁即可。效率提升 了
InnoDB表级锁
在绝大部分情况下应该使用行锁,因为事务和行锁往往是选择InnoDB的理由,但个别情况下也使用表级锁;
1)事务需要更新大部分或者全部数据,表又比较大,如果使用默认的行锁,不仅这个事务执行效率低,而且可能会造成其他事务长时间等待和锁冲突;
2)事务涉及多个表,比较复杂,很可能引起死锁,造成大量事务回滚
LOCK TABLE user READ;读锁锁表
LOCK TABLE user WRITE; 写锁锁表
事务执行...
COMMIT/ROLLBACK; 事务提交或者回滚
UNLOCK TABLES; 本身自带提交事务,释放线程占用的所有表锁
使用表锁的时候涉及到一个效率的问题:
要获取一张表的共享锁S或者排他锁X,最起码得确定,这张表没有被其他事务获取过X锁!比如说一张表(一千万个数据)没有被其他事务获取过行锁X锁!
举例:事务一:表S锁 事务二就不可以获取表X锁、行X锁
事务一:表X锁 事务二就不可以获取表X锁 、 行X锁
效率太低
MVCC(多版本并发控制)
MVCC是多版本并发控制(Multi-Version Concurrency Control,简称MVCC),是MySQL中基于乐观锁理论实现隔离级别的方式,用于实现已提交读和可重复读隔离级别的实现,也经常称为多版本数据库。
MVCC机制会生成一个数据请求时间点的一致性数据快照 (Snapshot), 并用这个快照来提供一定 级别 (语句级或事务级) 的一致性读取。从用户的角度来看,好象是数据库可以提供同一数据的多个 版本(系统版本号和事务版本号)。
1. 快照读(snapshot read)
读的是记录的可见版本,不用加锁。如select
2. 当前读(current read)
读取的是记录的最新版本,并且当前读返回的记录,insert,delete,update,select...lock in share mode/for update
已经提交读和可重复读 =》底层实现原理是 MVCC(多版本并发控制)=> 并发的读取方式:快照读
InnoDB提供了两个读取操作:锁定读(SX)和非锁定读(MVCC提供的快照读)=》 依赖底层的一个技术 =》 undo log 回滚日志
回顾:
事务日志:undo log (回滚日志) redo log(重做日志)
事务的ACD特性是由事务日志完成,I由锁+MVCC完成
undo log回滚日志的主要作用:
1. 事务发生错误时回滚rollback
2. 提供了MVCC的非锁定读(快照读)
MVCC:每一行记录实际上有多个版本,每个版本的记录除了数据本身之外,增加了其他字段
undo log 提供在表中新增两列:
DB_TRX_ID:事务ID :记录是那个事务修改的 当前事务ID
DB_ROLL_PTR:回滚指针 之前旧数据的地址 指向undo log日志上数据的指针
各个旧数据之间由链表联系起来,新增数据的旧数据是空的NULL
已提交读的MVCC:
(每次执行语句的时候都重新生成一次快照(Read View),每次select查询时。)
1. 已提交读底层如何解决脏读问题?
已提交读使用的是一种非锁定读,也就是MVCC多版本并发控制提供的一种快照读来解决的,事务每一次select都会把符合当前查询条件的数据拍个照片,相当于生成一次当前数据快照的一个数据版本,生成这个数据版本的时候有一个前提就是数据的状态必须是commit提交状态,当我们对一个数据进行修改但未提交,数据处于prepare状态,所以在生成数据快照的时候用的还是旧数据来生成的,也就是在undo log里面的旧数据,所以不会出现脏读。
2. 但为什么无法解决 不可重复读?
因为每一次select都会重新产生一次数据快照,其他事务更新后而且已提交数据,符合快照读的条件,可以实时反馈到当前事务的select结果中。
3. 为什么无法解决幻读?
因为每一次select都会重新产生一次数据快照,其它事务增加了和当前事务查询条件相同的新的数据并且成功commit提交,导致当前事务再次以同样的条件查询时,数据多了。
可重复读的MVCC:
(同一个事务开始的时候生成一个当前事务全局性的快照(Read View),第一次select查询时)
1. 解决了“脏读问题”
都是生成数据快照,生成数据快照的前提就是数据状态必须是commit
2. 解决了 不可重复读问题
第一次select产生数据快照,而且只产生一次!!!!!
第一次select产生数据快照,其它事务随谈修改了最新的数据,但是当前事务select时,依然查看的是最初的快照数据。
3. 部分解决了幻读
对于select操解决了幻读,原理同上,只在第一次select产生快照;但是对于updata等使用当前读方法的操作,并没有解决幻读问题。
快照内容读取原则:
1、版本未提交无法读取生成快照
2、版本已提交,但是在快照创建后提交的,无法读取
3、版本已提交,但是在快照创建前提交的,可以读取
4、当前事务内自己的更新,可以读到
死锁
MyISAM 表锁是 deadlock free 的, 这是因为 MyISAM 总是一次获得所需的全部锁,要么全部满足, 要么等待,因此不会出现死锁。
但在 InnoDB 中,除单个 SQL 组成的事务外,锁是逐步获得的,即锁的粒度比较小,这就决定了在 InnoDB 中发生死锁是可能的。
死锁问题一般都是我们自己的应用造成的,和多线程的死锁情况相似,大部分都是由于我们多个线程在获取多个锁资源的时候,获取的顺序不同导致的死锁问题。因此我们应用子啊对数据库的多个表坐更新的时候,不同的代码段应对这些表按相同的顺序进行更新操作,以防止锁冲突导致死锁问题。
事务被阻塞了,或者死锁了,mysqld是有事务阻塞的超时时间,题是事务处理失败
mysqld只能能够检测到死锁问题的发生,并进行事务的回滚
锁的优化建议(持续更新)
1.尽量使用较低的隔离级别
2.设计合理的索引并尽量使用索引访问数据,使加锁更加准确,减少锁冲突的机会提高并发能力
3.选择合理的事务大小,小事务发生锁冲突的概率小
4.不同的程序访问一组表时,应尽量约定以相同的顺序访问各表,对一个表而言,尽可能以固定的顺序 存取表中的行。这样可以大大减少死锁的机会
5.尽量用相等条件访问数据,这样可以避免间隙锁对并发插入的影响
6.不要申请超过实际需要的锁级别
7.除非必须,查询时不要显示加锁