InnoDB存储引擎 (第6章 锁)

人们认为行级锁总会增加开销,实际上,只有当实现本身会增加开销时,行级锁才会增加开销。InnoDB 存储引擎不需要锁升级,因为一个锁和多个锁的开销是一样的。

6.1   什么是锁

数据库系统使用锁是为了支持对共享资源的并发访问,提供数据的完整性和一致性。

InnoDB 存储引擎锁的实现和 Oracle 数据库非常类似,都提供一致性的非锁定读、行级锁支持。行级锁没有相关额外的开销,并可以同时得到并发性和一致性。

   

6.2   lock与latch

  1. latch 一般称为"(shuan)"(轻量级锁)

因为其要求锁定的时间非常短。若持续的时间长,应用的性能会非常差。InnoDB 存储引擎中。latch 又可以分为 mutex(互斥量)和 rwlock(读写锁)。目的是用来保证并发线程操作临界资源的正确性,通常没有死锁检测机制。

  1. lock(本节关注对象)

对象是事务,用来锁定的是数据库中的对象,如表,页,行。且 lock 的对象仅在事务 commit rollback 后进行释放,有死锁机制。

   

查看latch

show engine innodb mutex ;

Type : 总是显示Innodb;

Name : latch的信息以及所在源码的位置(行数) ,

6.3   InnoDB存储引擎中的锁

6.3.1 锁的类型

如下两种标准的行级锁:

  • 共享锁(S Lock),允许事务读一行数据;
  • 排它锁(X Lock),允许事务删除或更新一行数据。

   

S锁仅和S锁兼容, X锁和任何锁都不兼容 ;

S锁和X锁都是行锁;

   

意向锁 Intention lock

InnoDB 支持多粒度锁定,这种锁定允许事务在行级上的锁和表级上的锁同时存在。InnoDB 支持意向锁。意向锁是指将锁定的对象分为多个层次,意向锁意味着事务希望在更细粒度上进行加锁,如下图所示:

如上图,如果想对最细粒度的对象进行上锁,那么需要首先对粗粒度的对象上锁。

意向锁即为表级别的锁设计目的是为了在一个事务中揭示下一行将被请求的锁类型。

  1. 意向共享锁(IS Lock):事务想要获得一张表中某几行的共享锁;
  2. 意向排它锁(IX Lock):事务想要获得一张表中某几行的排它锁;

由于InnoDB 支持的是行级别的锁,所以意向锁其实不会阻塞除全表扫以外的任何请求。

   

查看正在运行线程 ;

show full processlist;

   

INNODB_TRX

SELECT * FROM information_schema.INNODB_TRX ;

   

INNODB_LOCKS

查看锁信息:

mysql8中 INFORMATION_SCHEMA INNODB_LOCKS and INNODB_LOCK_WAITS 被移除,替代的是 Performance Schema data_locks and data_lock_waits 表 ;

SELECT * FROM performance_schema.data_locks ;

   

INNODB_LOCK_WAITS

查看事务等待:

SELECT * FROM performance_schema.data_lock_waits ;

可以看到是哪个事务阻塞了另一个事务;

   

6.3.2 一致性非锁定读

consistent nonlocking read 是指 InnoDB 通过行多版本控制的方式来读取当前执行时间数据库中的数据。如果读取的行正在进行 DELETE UPDATE 操作,这时读取操作不会因此去等待行上锁的释放(非锁定)。相反,会去读取行的一个快照数据

快照数据是指该行的之前版本的数据,该实现是通过 undo 段来完成。undo 用来在事务中回滚数据,因此快照数据本身是没有额外的开销。此外,读取快照数据不需要上锁,因为没有事务需要对历史的数据进行修改操作。

InnoDB 默认读取方式,即读取不会占用和等待表上的锁

一个行记录可能有多个快照数据,一般称这种技术为行多版本技术,由此带来的并发控制,称之为多版本并发控制Multi Version Concurrency Control

在事务隔离级别 READ COMMITED REPEATABLE READ InnoDB 默认隔离级别)下,InnoDB使用非锁定的一致性读

但对于快照数据的定义不同:

  • READ COMMITED 事务隔离级别下,非一致性读总是读取被锁定行的最新一份快照数据;
  • REPEATABLE READ 事务隔离级别下,非一致性读总是读取事务开始时的行数据版本。

   

小结:

一致性非锁定读, 不用等行上锁的释放,而是读取行的一个快照数据(行记录上可能多个快照数据), 不同RR和RC隔离级别下,读取的快照情况不同;

   

6.3.3 一致性锁定读

在某些情况下,用户需要显示地对数据库读取操作进行加锁以保证数据逻辑的一致性。要求数据库支持加锁语句,即使是对 SELECT 的只读操作。

两种一致性锁定读:

  • SELECT…FOR UPDATE
  • SELECT…LOCK IN SHARE MODE

SELECT…FOR UPDATE 对读取的行记录加一个 X

SELECT…LOCK IN SHARE MODE 对读取的行记录加一个 S

当事务提交了,锁也就释放了。

6.3.4 自增长与锁

InnoDB 内存结构中,对每一个含有自增长值的表都有一个自增长计数器。插入操作会依据这个自增长的计数器增加1赋予自增长列,这个实现方式称作 AUTO-INC Locking,这种锁采用特殊的表锁机制,为了提高插入的性能,锁不是在一个事务完成后释放,而是在完成对自增长值插入的SQL语句后立即释放

自增长插入类型:

控制自增长模式

参数 : innodb_autoinc_lock_mode

mysql8默认值是2;

6.3.5 外键和锁

 在InnoDB存储引擎中,对于一个外键列,如果没有显式地对这个列加索引,InnoDB存储引擎自动对其加一个索引,因为这样可以避免表锁。(但Oracle不会自动添加索引,用户必须自己手动添加,这也是导致很多死锁问题产生的原因。)

  对于外键值的插入或者更新,首先需要查询父表中的记录,即select父表。但是对于父表的select操作,不是使用一致性非锁定读的方式,因为这样会发生数据不一致的问题,因此这时使用的是select...lock in share mode方式,主动对父表加一个S,如果这时父表已经加X锁,那么子表上的操作会被阻塞。例如:

   

6.4   锁的算法

6.4.1 行锁的3种算法

InnoDb3中行锁的算法设计:

  • Record Lock  :单个行记录上的锁。
  • Gap Lock       :间隙锁,锁定一个范围,但不包含记录本身
  • Next-key Lock:上面两种锁的合体,锁定一个范围,并且锁定记录本身

Record Lock总是会去锁住索引记录。如果InnoDb存储引擎表建立的时候没有设置任何一个索引,这时InnoDB会使用隐式的主键来进行锁定

Next-key Lock为默认算法,InnoDB会自己选一个最小算法模型。

   

当查询的索引含有唯一属性时,InnoDB 会对 Next-Key Lock 进行优化,降级为 Record Lock,即仅锁住索引本身,而不是范围。

若唯一索引由多列组成,而查询仅是查找多个唯一索引列中的其中一个,那个其实是range类型查询,而不是point类型查询,故Innodb依然使用next-key lock 进行锁定;

   

6.4.2 解决Phantom Problem(幻读)

在默认事务隔离级别下,即 REPEATABLE READ下,InnoDB 采用 Next-Ley Locking 机制来避免 Phantom Problem(幻象问题)。

Phantom Problem 是指:在同一事务下,连续执行两次同样的 SQL 语句可能导致不同的结果,第二次的 SQL 语句可能会返回之前不存在的行。

   

6.5   锁问题

锁只会带来三种问题,如果可以防止这三种情况的发生,那就不会出现并发异常。

6.5.1 脏读 (读未提交)

脏页: 指在缓冲池中已经被修改的页,但是还没有刷新到磁盘中,即数据库实例内存中的页和磁盘中的页的数据是不一致的,当然在刷新到磁盘之前,日志都已经被写入到了redo日志文件中。数据库实例内存与磁盘的异步造成的,不影响数据一致性。脏页的刷新是异步的,带来性能提升。

脏数据: 事务对缓冲池中行记录的修改,并且还没有被提交。如果读到了脏数据,即一个事务可以读到另一个 事务中还未提交的数据,则显然违反了数据库的隔离性

脏读: 可以读到脏数据。

脏读需要事务的隔离级别为 READ UNCOMMITTED,InnoDB 默认 REPEATABLE READ。

   

replication环境中的slave节点,如果不需要特别精确的返回值,则可以设置隔离级别为READ UNCOMMITTED;

   

6.5.2 不可重复读(读已提交 / 幻读)

在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的数据可能是不一样的。这样就发生了一个事务内两次读到的数据是不一样的情况 ,称为不可重复读。

脏读读到的是未提交的数据,不可重复读读到的是已提交的数据,但是违反了事务一致性的要求。

InnoDB 的默认事务隔离级别是 REPEATABLE READ,采用 Next-Key Lock算法,避免了不可重复读。不仅锁住了索引,而且锁住了这些索引的覆盖范围(gap) ;

   

6.5.3 丢失更新

一个事务的更新操作会被另一个事务的更新操作所覆盖,从而导致数据的不一致。

要避免丢失更新,需要让事务在这种情况下的操作变成串行化,而不是并行操作。

   

6.6   阻塞

因为不同锁之间的兼容性关系,所以在有些时刻,一个事务中的锁需要等待另一个事务中的锁释放它所占用的资源。

InnoDB存储引擎的源代码中,用Mutex数据结构来实现锁,在访问资源前需要用mutex_enter函数进行申请,在资源访问或修改完毕后立即执行mutex_exit函数。当一个资源已被一个事务占有时,另一个事务执行mutex_enter函数会发生等待,这就是阻塞。阻塞并不是一件坏事,阻塞是为了保证事务可以并发并且正常运行。

innodb_lock_wait_timeout : 控制等待的时间,默认是50 , 该参数可以动态设置;

innodb_rollback_on_timeout : 是否在等待超时时对进行中的事务进行回滚( 默认是OFF,代表不回滚 ); 该参数是静态的;

6.7   死锁

6.7.1 死锁的概念

如果程序是串行的,那么不可能发生死锁。死锁只发生于并发的情况,数据库就是一个并发进行着的程序,因此可能会发生死锁。

(死锁的一个经典案例:A等待BB在等待A。)

InnoDB存储引擎有一个后台的锁监控线程,该线程负责查看可能的死锁问题,并自动告知用户。 InnoDB存储引擎并不会回滚大部分的错误异常,但是死锁除外。发现死锁后,InnoDB存储引擎会马上回滚一个事务

   

wait-for graph(等待图)

作用: 死锁检测,主动的死锁检测方式 ;

要求数据库保持以下两种信息

  • 锁的信息链表
  • 事务等待链表

根据上述链表可以构造出一张有向图,若在这个图中存在回路,就代表存在死锁

InnoDB 通常选择回滚 undo 量最小的事务

深度优先算法实现。

   

6.7.2 死锁概率

有关因素:

  1. 系统中事务的数量(n),概率正比
  2. 每个事务操作的数量(r),概率正比
  3. 操作数据的集合(r) ,概率反比

   

6.7.3 死锁的示例

Innodb不会回滚大部分的错误异常,但是死锁除外,发现死锁后马上回滚一个事务;

6.8   锁升级

锁升级是指将当前锁的粒度降低。例如,数据库可以把一个表的1000个行锁升级成一个页锁,或者将页锁升级为表锁。如果数据库的设计中认为锁是一种稀有资源,而且想避免锁的开销,那数据库中会频繁出现锁升级现象。

SQL Server数据库可能发生锁升级;

InnoDB存储引擎不存在锁升级的问题。在InnoDB存储引擎中,1个锁的开销与1000000个锁是一样的,都没有开销。

因为其不是根据每个记录来产生行锁的,是根据每个事务访问的每个页对锁进行管理的,采用的是位图的方式。

   

   

6.9   小结

 省略..

  

原文地址:https://www.cnblogs.com/coloz/p/13792678.html