事务和锁

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了事务和锁相关的知识,希望对你有一定的参考价值。


Atomic,同一个事务里,要么都提交,要么都回滚
Consistency,即在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏
Isolation,并发事务间的数据是彼此隔离的,通过锁的方式来实现。
Durabiliy,事务提交后,所有结果务必被持久化REDO UNDO

INNODB记录逻辑的操作。

INNODB原理:

         事务提供一种机制将一个活动设计的所有的操作纳入一个不可分割的执行单元,

组成事务的所有操作只有在所有操作均能正常执行的情况下提交,只要其中任一操作执行失败,都将导致整个事务回滚,

简单说:事务提供一种机制--“要么什么都不做,要么做全套”的机制

撤销日志,undolog:

undo 工作原理:

         主要是为了满足事务的原子性,在操作任何数据前,将数据备份到一个地方-UNDOLOG ,然后进行数据的增、删、改操作,如果出现

了错误或用户执行ROLLBACK语句,系统可以利用UNDO LOG中的备份数据恢复到事务开始之前

重做日志,redolog

redo工作原理:

         UNDOLOG相反,REDOLOG记录的是新数据的备份,在事务提交前,只要将REDOLOG持久化,不需要将数据持久化,当系统奔溃时,虽然数据

没有持久化,但REDOLOG持久化,系统可根据REDOLOG内容,将所有数据恢复到最新的状态。

事务隔离级别可以解决并发操作中存在的问题:

Dirty read (脏读)-- READ-UNCOMMITTED

事务(A)读取到另一个事务(B)中修改后但尚未提交的数据,并这个数据的基础上操作。这时,如果 B事务回滚,那么 A事务读到的数据是无效的。不符合一致性
unrepeatable read(不可重复读)
事务(A)读取到了另一个事务(B)已经提交的更改数据,不符合隔离性

phantom read(幻读)
事务(A)读取到了另一个事务(B)提交的新增数据,不符合隔离性

INNODB采用NEXT-KEY LOCK机制来避免幻读

为了解决 脏读、幻读、不可重复读,我们引入事务隔离级别:
Read Uncommitted(读未提交)--脏读、,不可重复读
Read Commited(读已提交新的数据)--幻读
Repeatable Read(可重复读)--默认隔离级别,消除了脏读、不可重复读、幻读,保证事务一致性
Serializable(串行)--串行化读,每次读都需要获得表级共享锁,读写间相互都会阻塞

技术分享


RR可能出现幻读主要是参数设置innodb_locks_unsafe_for_binlog=1

日志类型的事务类型可以设置RC

mysql支持的锁:表锁,行锁、页级锁

INNODB可能发生表锁:

1、auto-inc锁
2、全表更新、全索引更新
3、使用SR事务隔离级别

 

锁的类型:表锁基于聚集索引的主键

1、record lock(行/记录锁)

单个记录上的锁,有可能是普通索引,唯一索引,组合索引等。
2
、gap lock(间隙锁)

在索引记录间隙上的锁,或者是第一条索引记录之前、最后一条索引记录之后上的间隙锁


3
、next-key lock (record lock + gap lock)

索引记录锁以及索引之间的间隙锁,2者组合锁

技术分享


SELECT自动加读锁

其他DML、DDL自动加写锁

InnoDB是通过给索引上的索引项加锁来实现行锁

InnoDB有几种锁:

共享锁(S - LOCKING),允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁

排它锁(X - LOCKING),允许获得排他锁的事务更新数据,阻止其他事务取得相同数据集的共享读锁和排他锁

InnoDB还独有的实现了2种锁:
  意向共享锁(IS),事务打算给数据行加行共享锁,事务在给一个数据行加共享锁前必须先取得该表的IS锁
  意向独占锁(IX),事务打算给数据行加行排他锁,事务在给一个数据行加排他锁前必须先取得该表的IX锁

。。。。。。。。

注意:
 (1)在不通过索引条件查询的时候,InnoDB使用的是表锁(默认地,全表所有行加锁,和表级锁相当,例外条件是 RC + innodb_locks_unsafe_for_binlog 组合选项),而不是细粒度行锁。

(2)由于MySQL的行锁是针对索引加的锁,不是针对记录加的锁,所以虽然是访问不同行的记录,但是如果是使用相同的索引键,是会出现锁冲突的。

加共享锁:SELECT * FROM xx WHERE … LOCK IN SHARE MODE
加排他锁:SELECT * FROM xx WHERE … FOR UPDAT

 技术分享

由于MySQL的行锁是针对索引加的锁,不是针对记录加的锁,所以虽然是访问不同行的记录,但是如果是使用相同的索引键,是会出现锁冲突的。应用设计的时候要注意这一点。

S、X、IS、IX,表示的是,本锁和其他锁共存的方式,是互斥还是兼容

RECORD LOCK、GAP LOCK、NEXT-KEY LOCK,表示的是,这些锁要加载的范围,是行记录本身,还是行记录+间隙,甚至更大的范围

1、任何辅助索引上的锁,或者非索引列上的锁,最终都要回溯到主键上,在主键上也要加一把锁
2、任何叶子节点上的S或X锁之前,都会在根节点加一个IS或IX锁,也就是表级别的IS、IX锁
3、主键索引上的锁,都是record lock
4、唯一索引辅助索引上的锁,也都是record lock
5、非唯一索引辅助索引上的锁,则是next-key lock
6、不会有单独的gap lock出现,只会伴随着record lock出现,依附于它

主键索引 = record lock(但外键约束、唯一约束检测仍然使用gap lock)
唯一辅助索引 = record lock(但外键约束、唯一约束检测仍然使用gap lock)
非唯一辅助索引 = next-key lock

上述结论的前提:
| innodb_locks_unsafe_for_binlog | OFF   |
RR级别

如果想降低锁的范围,则可以:
设置RC隔离级别,或者启用innodb_locks_unsafe_for_binlog,可以关闭gap lock(但外键约束、唯一约束检测仍然使用gap lock)

如果辅助索引上的搜索及锁定是排他的,则会回溯到其相应的聚集索引,并且在它上面加锁

对无索引的字段检索更新时,升级成表级锁


SELECT … FROM,一致性非锁定读,除非是SERIALIZABLE隔离级别

SELECT … FROM,一致性非锁定读,除非是SERIALIZABLE隔离级别,在其影响的索引记录上设置一个共享锁

 LOCK IN SHARED MODE,使用共享next-key lock

FOR UPDATE使用排他next-key lock锁,会阻止LOCK IN SHARED MODE请求

UPDATE/DELETE,排他next-key lock

INSERT,排他record lock,而非next-key lock,但在写入新记录之前需要加意向插入gap lock(insertion intention gap lock)

INSERT ... ON DUPLICATE KEY UPDATE,排他next-key lock(即将被UPDATE的记录上)

REPLACE,没冲突/重复时,和INSERT一样,否则(有冲突时是先DELETE后INSERT)加next-key lock

INSERT INTO T SELECT ... FROM S WHERE,T表上排他record lock;事务隔离级别为RC或者启用innodb_locks_unsafe_for_binlog并且隔离级别不是SERIALIZABLE时,S表上采用无锁一致性读。否则,加排他next-key lock(RC不加锁,RR加next-key lock)
CREATE TABLE..SELECT,和INSERT…SELECT一样

 REPLACE INTO t SELECT ... FROM s WHERE或 UPDATE t ... WHERE col IN (SELECT ... FROM s ...),都会在s表上加next-key lock

AUTO_INCREMENT列上写入新数据时,索引末尾设置排他锁。

AUTO_INCREMENT列上写入新数据时,索引末尾设置排他锁。请求自增列计数器时,InnoDB使用一个AUTO-INC表锁,只对请求的那个SQL有影响,不会影响整个事务。该锁被持有时,其他会话不能往InnoDB表中写入新行

LOCK TABLES,设置表锁

几个重点的锁类型,需要关注下:
  如果辅助索引上的搜索及锁定是排他的,则会取回其相应的聚集索引,并且在它上面加锁
  对无索引的字段检索更新时,升级成表级锁
  SELECT … FROM,一致性非锁定读,除非是SERIALIZABLE隔离级别
   INSERT INTO T SELECT ... FROM S WHERE,T表上排他record lock;事务隔离级别为RC或者启用innodb_locks_unsafe_for_binlog并且隔离级别不是SERIALIZABLE时,S表上采用无锁一致性读。否则,加排他next-key lock
  INSERT,排他record lock,非next-key lock,但加意向插入gap lock(还有一种叫做 意向插入(insertion intention) 的gap lock,如果两个不同事务想往同一个gap lock中写入数据,但写入位置不一样时,是无需等待,可以直接写入的,因为没有冲突)
  AUTO_INCREMENT列上写入新数据时,索引末尾设置排他锁。请求自增列计数器时,InnoDB使用一个AUTO-INC表锁,只对请求的那个SQL有影响,不会影响整个事务。该锁被持有时,其他会话不能往InnoDB表中写入新行


innodb_print_all_deadlocks 选项

设置 innodb_print_all_deadlocks = 1,在日志中记录全部死锁信息

自动检测死锁,并优先回滚小事务(影响较小的事务)

加表锁时,不会发生死锁

事务中,如果SELECT调用存储函数/存储过程失败了,对应的SQL会回滚事务。如果再显式执行ROLLBACK,那么整个事务都回滚。

事务回滚时,会释放全部锁。个别情况下,如果个别SQL因为某些错误回滚事务的话,它所持有的行锁可能无法释放,因为InnoDB的行锁信息并没有记录是哪个SQL持有的,这时候,建议执行一次显式的ROLLBACK

事务中涉及多个表,或者涉及多行记录时,每个事务的操作顺序都要保持一致,降低死锁概率,最好用存储过程/存储函数固化

通过索引等方式优化SQL效率,降低死锁发生概率(减小扫描/锁范围,降低概率)

事务尽快提交,小事务越不容易发生死锁
  加FOR UPDATE、LOCK IN SHARE MODE读锁时,最好降低事务隔离级别,例如用RC级别,降低死锁发生概率

事务中涉及多个表,或者涉及多行记录时,每个事务的操作顺序都要保持一致,降低死锁概率,最好用存储过程/存储函数固化

通过索引等方式优化SQL效率,降低死锁发生概率(减小扫描/锁范围,降低概率)


以上是关于事务和锁的主要内容,如果未能解决你的问题,请参考以下文章

MySQL数据库高级——事务和锁

事务和锁

MySQL事务和锁详解

redis事务和锁机制

mysql事务和锁

面试中的老大难-mysql事务和锁,一次性讲清楚!