innodb锁和事物

• InnoDB存储引擎支持行级锁,其大类能够细分为共享锁和排它锁两类
• 共享锁(S):容许拥有共享锁的事务读取该行数据。当一个事务拥有一行的共享锁时,另外的事务能够在同一行数据也得到共享锁,但另外的事务没法得到同一行数据上的排他锁
• 排它锁(X):容许拥有排它锁的事务修改或删除该行数据。当一个事务拥有一行的排他锁时,另外的事务在此行数据上没法得到共享锁和排它锁,只能等待第一个事务的锁释放
• 除了共享锁和排他锁以外,InnoDB也支持意图锁。该锁类型是属于表级锁,代表事务在后期会对该表的行施加共享锁或者排它锁mysql

意图锁也有两种类型:
• 共享意图锁(IS):事务将会对表的行施加共享锁
• 排他意图锁(IX):事务将会对表的行施加排它锁sql

例:数据库

select … for share mode语句就是施加了共享意图锁,而select … for update语句就是施加了排他意图锁
• 这四种锁之间的相互共存和排斥关系以下性能

 

InnoDB锁相关系统表spa

Information_schema.innodb_trx记录了InnoDB中每个正在执行的事务,包括该事务得到的锁信息,事务开始时间,事务是否在等待锁等信息设计

Information_schema.innodb_locks记录了InnoDB中事务在申请但目前尚未获取到的每一个锁信息,以及当前事务的锁正在阻止其余事务得到锁3d

Information_schema.innodb_lock_waits记录了InnoDB中事务之间相互等待锁的版本控制

 

• 行级锁
• 行级锁是施加在索引行数据上的锁日志

当一个InnoDB表没有任何索引时,则行级锁会施加在隐含建立的聚簇索引上,因此说当一条sql没有走任何索引时,那么将会在每一条汇集索引后面加X锁orm

• 间隔锁
• 当 咱们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件 的已有数据记录的索引项加锁;对于键值在条件范围内但并不存在的记录,叫作“间隙(GAP)”,InnoDB也会对这个“间隙”加锁
• 间隔锁是施加在索引记录之间的间隔上的锁,锁定一个范围的记录、但不包括记录自己,好比SELECT c1 FROM t WHERE c1 BETWEEN 10 and 20 FOR UPDATE语句,尽管有可能对c1字段来讲当前表里没有=15的值,但仍是会阻止=15的数据的插入操做,是由于间隔锁已经把索引查询范围内的间隔数据也都锁住了

• 间隔锁的使用只在部分事务隔离级别才是生效的
• 间隔锁只会阻止其余事务的插入操做

gap lock的前置条件:
• 1 事务隔离级别为REPEATABLE-READ,innodb_locks_unsafe_for_binlog参数为0,且sql走的索引为非惟一索引(不管是等值检索仍是范围检索)
2 事务隔离级别为REPEATABLE-READ,innodb_locks_unsafe_for_binlog参数为0,且sql是一个范围的当前读操做,这时即便不是非惟一索引也会加gap lock

例:

连接1:

mysql> update temp set name='abc' where id>4;
• 连接2:
• mysql> insert into temp values(4,‘abc’); ##等待

• Next-key锁
• 在默认状况下,mysql的事务隔离级别是可重复读,而且innodb_locks_unsafe_for_binlog参数为0,这时默认采用next-key locks。所谓Next-Key Locks,就是记录锁和间隔锁的结合,即除了锁住记录自己,还要再锁住索引之间的间隙。

• 插入意图锁
插入意图锁是在插入数据时首先得到的一种间隔锁,对这种间隔锁只要不一样的事务插入的数据位置是不同的,虽然都是同一个间隔,也不会产生互斥关系

例:

• 好比有一个索引有4和7两个值,若是两个事务分别插入5和6两个值时,虽然两个事务都会在索引4和7之间施加间隔锁,但因为后续插入的数值不同,因此二者不会互斥

• 好比下例中事务A对索引>100的值施加了排他间隔锁,而事务B在插入数据以前就试图先施加插入意图锁而必须等待

• 自增锁
• 自增锁是针对事务插入表中自增列时施加的一种特殊的表级锁,即当一个事务在插入自增数据时,另外一个事务必须等待前一个事务完成插入,以便得到顺序的自增值
• 参数innodb_autoinc_lock_mode能够控制自增锁的使用方法

•innodb死锁

• 死锁的状况发生在不一样的的事务相互之间拥有对方须要的锁,而致使相互一直无限等待

• 死锁可能发生在不一样的事务都会对多个相同的表和相同的行上施加锁,但事务对表的操做顺序不相同

• 为了减小死锁的发生,要避免使用lock table语句,要尽可能让修改数据的范围尽量的小和快速;当不一样的事务要修改多个表或者大量数据时,尽量的保证修改的顺序在事务之间要一致
• 默认状况下InnoDB下的死锁自动侦测功能是开启的,当InnoDB发现死锁时,会将其中的一个事务做为牺牲品回滚。(InnoDB选择牺牲的事务每每是代价比较小的事务,其代价计算是根据事务insert,update, delete的数据行规模决定

• 能够经过show engine innodb status语句查看最后一次发生死锁的状况(能够经过innodb_print_all_deadlocks参数将死锁信息打印到错误日志中)

减小死锁的发生

• 尽量的保持事务小型化,减小事务执行的时间能够减小发生影响的几率
• 及时执行commit或者rollback,来尽快的释放锁
• 能够选用较低的隔离级别,好比若是要使用select... for update和select...lock in share mode语句时可使用读取提交数据隔离级别
• 当要访问多个表数据或者要访问相同表的不一样行集合时,尽量的保证每次访问的顺序是相同的。好比能够将多个语句封装在存储过程当中,经过调用同一个存储过程的方法能够减小死锁的发生

• 增长合适的索引以便语句执行所扫描的数据范围足够小
• 尽量的少使用锁,好比若是能够承担幻读的状况,则直接使用select语句,而不要使用select...for update语句

• 若是没有其余更好的选择,则能够经过施加表级锁将事务执行串行化,最大限度的限制死锁发生

 

InnoDB事务隔离级别

• InnoDB存储引擎提供了四种事务隔离级别,分别是:
• READ UNCOMMITTED:读取未提交内容
• READ COMMITTED:读取提交内容
• REPEATABLE READ:可重复读,默认值。
• SERIALIZABLE:串行化

更改事物隔离级别语法格式为:

SET [GLOBAL | SESSION] TRANSACTION ISOLATION LEVEL
{
READ UNCOMMITTED
| READ COMMITTED
| REPEATABLE READ
| SERIALIZABLE
}

• REPEATABLE READ:可重复读,默认值。代表对同一个事务来讲第一次读数据时会建立快照,在事务结束前的其余读操做(不加锁)会得到和第一次读相同的结果。当读操做是加锁的读语句(select … for update或者lock in share mode),或者update和delete语句时,加锁的方式依赖于语句是否使用惟一索引访问惟一值或者范围值

• 当访问的是惟一索引的惟一值时,则InnoDB会在索引行施加行锁

• 当访问惟一索引的范围值时,则会在扫描的索引行上增长间隔锁或者next-key锁以防止其余连接对此范围的插入

注:

对可重复读隔离级别来讲,第一个事务的修改会在每行记录上都增长排他锁,而且直到事务结束后锁才会释放

第二个事务会一直等待前面事务的锁被释放后才能执行

• READ COMMITTED:读取提交内容。意味着每次读都会有本身最新的快照。对于加锁读语句(select … for update和lock in share mode),或者update,delete语句会在对应的行索引上增长锁,但不像可重复读同样会增长间隔锁,所以其余的事务执行插入操做时若是是插入非索引行上的数值,则不影响插入。
• 因为该隔离级别是禁用间隔锁的,因此会致使幻读的状况
• 若是是使用此隔离级别,就必须使用行级别的二进制日志

此隔离级别还有另外的特色:
• 对于update和delete语句只会在约束条件对应的行上增长锁
• 对update语句来讲,若是对应的行上已经有锁,则InnoDB会执行半一致读的操做,来肯定update语句对应的行在上次commit以后的数据是否在锁的范围,若是不是,则不影响update操做,若是是,则须要等待对应的锁解开

注:

对读取提交内容事务隔离级别来讲,第一个修改操做会在全部行上都加排他锁,但会在肯定不修改上的行上释放对应的锁

第二个事务经过半一致读的方式判断每行的最后commit的数据是否在修改的范围里,会在未加锁的行上加上排他锁

• READ UNCOMMITTED:读取未提交内容,所读到的数据多是脏数据
• SERIALIZABLE:串行化,此隔离级别更接近于可重复读这个级别,只是当autocommit功能被禁用后,InnoDB引擎会将每一个select语句隐含的转化为select … lock in share mode

一致读

在默认的隔离级别下一致读是指InnoDB在多版本控制中在事务的首次读时产生一个镜像,在首次读时间点以前其余事务提交的修改能够读取到,而首次读时间点以后其余事务提交的修改或者是未提交的修改都读取不到

• 惟一例外的状况是在首次读时间点以前的本事务未提交的修改数据能够读取到
• 在读取提交数据隔离级别下,一致读的每一个读取操做都会有本身的镜像
• 一致读操做不会施加任何的锁,因此就不会阻止其余事务的修改动做

• 一致读在某些DDL语句下不生效:
• 碰到drop table语句时,因为InnoDB不能使用被drop的表,因此没法实现一致读
• 碰到alter table语句时,也没法实现一致读
• 当碰到insert into… select, update … select和create table … select语句时,在默认的事务隔离级别下,语句的执行更相似于在读取提交数据的隔离级别下

. InnoDB 的MVCC

InnoDB 的 MVCC ,是经过在每行记录后面保存两个隐藏的列来实现的。这两个列一把保存了行的建立时间,一个保存行的过时时间(或删除时间),固然存储的并非真正的时间,而是系统版本号每开始一个事务,系统版本号就会自动递增,事务开始时刻的版本号做为当前事务的版本号,用来和查询到的每行记录的版本号就行比较。

如下是 REPEATABLE READ 的隔离级别下具体操做:

  • SELECT InnoDB 会根据如下两个条件检查每行记录: a. InnoDB 只查询版本早于当前事务版本的数据行(也就是,行的系统版本号小于或等于事务的系统版号),这样能够确保事务读取的行,要么是在事务开始前的已经存在的,要么是事务自身插入或者修改过的。 b. 行的删除版本要么未定义,要么大于当前事务版本号。这能够确保事务读取到的行,在事务开始以前未被删除。 只有符合上述两个条件的记录,才能返回做为查询结果
  • INSERT InnoDB 为新插入的每一行保存当前系统版本号做为行版本号
  • DELETE InnoDB 为删除的每一行保存当前系统版本号做为行删除标识
  • UPDATE InnoDB 为插入一行新记录,保存当前系统版本号做为行版本号,同时保存当前系统版本号到原来的行做为行删除标识

保存着两个额外的系统版本号,使大多数读操做均可以不用加锁。这样设计使得读数据操做很简单,性能很好,而且也能保证只会读取到符合标准的行

 

Autocommit/commit/rollback

• 当设置autocommit属性开启时,每一个SQL语句都会隐含成为独立的事务。
• 默认状况下autocommit属性是开启的,也就意味着当每一个SQL语句最后执行结果不返回错误时都会执行commit语句,当返回失败时会执行rollback语句
• 而当autocommit属性开启时,能够经过执行start transaction或者begin语句来显示的开启一个事务,而事务里能够包含多个SQL语句,最终事务的结束是由commit或者rollback终结
• 而当在数据库连接里执行set autocommit=0表明当前数据库连接禁止自动提交,事务的终结由commit或者rollback决定,同时也意味着下一个事务的开始
• 若是一个事务在autocommit=0的状况下数据库连接退出而没有执行commit语句,则这个事务会回滚
• 一些特定的语句会隐含的终结事务,就比如是执行了commit语句
• commit语句表明将此事务的数据修改永久化,并对其余事务可见,而rollback则表明将此事务的数据修改回滚。
• commit和rollback都会把当前事务执行所施加的锁释放

相关文章
相关标签/搜索