lock和latch
锁分为两种类型 lock 和 latch ,它们之间的差异如下表所示:
- | lock | latch |
---|---|---|
对象 | 事务 | 线程 |
保护 | 数据库内容 | 内存数据结构 |
持续时间 | 整个事务过程 | 临界资源 |
模式 | 行锁、表锁、意向锁 | 读写锁、互斥量 |
死锁 | 通过waits-for graph、time out等机制进行死锁检测和处理 | 无死锁检测与处理机制。仅通过应用进程加锁的顺序保证无死锁情况发生 |
存在于 | Lock Manager 的哈希表中 | 每个数据结构的对象中 |
下面讲到的锁都是针对lock来讲。
锁类型
- 共享锁(S) :允许事务读一行数据。
- 排他锁(x) :允许事务删除或更新一行数据。
以下情况会加锁:
锁类型 | 加锁情况 |
---|---|
行S | 读取行记录 |
行X | 增删改行记录 |
表S | 全表扫描 |
表X | 修改表结构 |
如果事务T1已经获得行r的共享锁,那么事务T2可以立即获得行r的共享锁,这种就叫做锁兼容。如果事务T3想获得行r的排他锁,就必须等待T1、T2释放它们的共享锁,这种就叫做锁不兼容。
共享锁与排他锁的兼容性如下表所示:
- | X | S |
---|---|---|
X | 不兼容 | 不兼容 |
S | 不兼容 | 兼容 |
innodb支持多粒度锁定,即可以同时存在行锁和表锁。并支持另外一种锁方式,称为意向锁。因为数据本身也分为以下几层
数据库 > 表 > 页 > 行
所以,如果如果对某一层加锁,就需要对其所有上层加意向锁,意向锁也分为意向共享锁(IS),意向排他锁(IX)。
对行加共享锁前,会对表加意向共享锁;对行加排他锁前,会对表加意向排他锁。
表级意向锁和表级锁的兼容性如下表所示:
- | IS | IX | S | X |
---|---|---|---|---|
IS | 兼容 | 兼容 | 兼容 | 不兼容 |
IX | 兼容 | 兼容 | 不兼容 | 不兼容 |
S | 兼容 | 不兼容 | 兼容 | 不兼容 |
X | 不兼容 | 不兼容 | 不兼容 | 不兼容 |
例如对记录r加X锁之前,必须对表1加上IX锁,如果已经有事务对表1进行了S表锁,由于不兼容,需要等待表锁操作完成。
查看锁
innodb1.0版本之前,用户只能通过以下命令查看当前数据库锁的请求。
SHOW FULL PROCESSLIST;
SHOW ENGINE INNODB STATUS;
从innodb1.0版本开始,在INFORMATION_SCHEMA架构中添加了表INNODB_TRX、INNODB_LOCKS、INNODB_LOCK_WAITS。通过这3张表,用户可以更简单的监控当前事务并分析可能存在的锁问题。
INNODB_TRX表由以下字段组成:
| 字段 | 说明 | | :- | :- | | trx_id | InnoDB存储引擎内部唯一的事务ID | trx_state | 当前事务的状态。 | trx_started | 事务的开始时间。 | trx_requested_lock_id | 等待事务的锁ID。如trx_state的状态为LOCK WAIT,那么该值代表当前的等待之前事务占用锁资源的ID.若trx_state不是LOCK WAIT,则该值为NULL。 | trx_wait_started | 事务等待开始的时间。 | trx_weight | 事务的权重,反映了一个事务修改和锁住的行数。在InnoDB存储引擎中,当发生死锁需要回滚时,InnoDB存储会选 择该值最小的进行回滚。 | trx_mysql_thread_id | Mysql中的线程ID,SHOW PROCESSLIST显示的结果。 | trx_query | 事务运行的sql语句。
实际例子:
通过state可以观察到trx_id为730FEE的事务当前正在运行,而trx_id为731F4的事务处于LOCK WAIT状态,且运行的SQL语句是select * from parent lock in shar mode。该表只是显示了当前运行的innoDB事务,并不能准确的判断锁的一些情况。如果需要查看锁,还需要访问 INNODB_LOCKS。
INNODB_LOCKS表由如下字段组成:
字段 | 说明 |
---|---|
lock_id | 锁的ID。 |
lock_trx_id | 事务ID。 |
lock_mode | 锁的模式。 |
lock_type | 锁的类型,表锁还是行锁。 |
lock_table | 要加锁的表。 |
lock_index | 锁的索引。 |
lock_space | InnoDB存储引擎表空间的ID号。 |
lock_page | 被锁住的页的数量。若是表锁,则该值为NULL。 |
lock_rec | 被锁住的行的数量。若是表锁,则该值为NULL。 |
lock_data | 被锁住的行的主键值。当是表锁,该值为NULL。 |
按照上面的例子,继续查看表INNODB_LOCKS。
用户可以清楚的看到当前锁的信息,trx_id为730FEE的事务想表parent加了一个X的行锁。ID为7311F4的事务想表parent申请了一个S的行锁。lock_data都是1,申请相同的资源,因此会有等待。这样可以解析INNODB_TRX为什么一个事务的trx_state是RUNNING另一个是LOCK WAITLE
另外需要注意的是,发现lock_data并不是可信的值。例如当用户运行一个范围查找时,lock_data可能只返回第一行的主键值。与此同时,如果当前资源被锁住了。若锁住的页因为InnoDB存储引擎缓冲池的容量,导致页从缓冲池中被刷出,则查看INNODB_LOCKS表时,该值同样显示为NULL。即InnoDB存储引擎不会从磁盘进行再一次的查找
在通过INNODB_LOCKS馋看了每张表上锁的情况后,用户可以判断由此引发的等待情况。当事务较小时,用户就可以人为地、直观地进行判断了。但是当事务量非常大,其中锁和等待也时常发生。这个时候就不容易判断。但是可以通过INNODB_LOCK_WAITS可以很直观的反应出当前事务的等待。
INNODB_LOCK_WAIT表由以下字段组成:
字段 | 说明 |
---|---|
requesting_trx_id | 申请锁资源的事务ID |
requesting_lock_id | 申请的锁的ID。 |
blocking_trx_id | 阻塞的事务的ID。 |
blocking_lock_id | 阻塞的事务的ID。 |
按照上面的例子,运行如下查询:
通过上述的SQL语句,用户可以清楚的看到哪个事务阻塞了另一个事务。当然这只是给出了事务和锁ID,如果需要,用户可以根据表INNODB_TRX、INNODB_LOCKS、INNODB_LOCK_WAITS得到更为直观的详细信息。例如,用户可以执行如下联合查询
一致性非锁定读
一致性的非锁定读是指innodb存储引擎通过行多版本控制的方式来读取当前执行时间数据库中的行数据。
就是说如果读取的行已被加了X锁,这时不需要等待X锁的释放,而是读取行记录的快照数据(即该行的之前版本的数据),该实现是通过undo段来实现,而undo本身是用来在事务中回滚数据,因此快照数据本身是没有额外的消耗的。读取快照是不需要上锁的,因为没有事务需要对历史数据进行修改操作。
由于读取的行数据必须不能被其他事务修改,所以对使用场景有要求,在事务隔离级别READ COMMIT(提交读)和REPEATABLE READ(可重复读,默认)下,innodb使用一致性非锁定读。
在READ COMMIT隔离级别下,对于快照数据,一致性非锁定读总是读取被锁定行的最新一份快照。
在REPEATABLE READ隔离级别下,对于快照数据,一致性非锁定读总是读取事务开始时的行数据版本。
自增长与锁
当对含有自增长的计数器的表进行插入操作时,这个计数器会被初始化,执行如下的语句来得到计数器的值:
select max(auto_inc_col) from test for update;
插入操作会根据这个自增长的计数器值加1赋予自增长列。这个实现方式称作为AUTO-INC Locking。这种锁采用一种特殊的表锁机制,为了提高插入的性能,锁不是在一个事务完成后才释放,而是在完成对自增长值插入的SQL语句后立即释放。
虽然AUTO-INC Locking从一定程度上提高了并发插入的效率,但还是存在一些性能问题。首先,对于有自增长值的列的并发插入性能较差,事务必须等待前一个插入的完成。其次,对于INSERT—-SELECT的大数据量的插入会影响插入的性能,因为另一个事务中的插入会被阻塞。
从MySQL5.1.22版本开始,InnoDB存储引擎引擎中提供了一种轻量级互斥量的自增长实现机制,这种机制大大提高了自增长值插入的性能。并且从该版本开始,InnoDB存储引擎提供了一个参数innodb_autoinc_lock_mode来控制自增长的模式,该参数的默认值为1. 在继续讨论新的自增长方式实现方式之前,需要对自增长的插入进行分类,如下:
插入类型 | 说明 |
---|---|
insert-like | 指所有的插入语句,如insert,replace,insert—select,replace—select,load data等 |
simple inserts | 指能在插入之前就确定插入行数的语句。这些语句包含insert、replace等,需要注意的是:simple inserts不包含insert—on duplicater key update这类SQL语句 |
bulk inserts | 指在插入之前不能确定得到插入行数的语句,如insert—select,replace–select,load data |
mixed-mode inserts | 指插入中有一部分的值是自增长的,有一部分是确定的,如INSERT INTO t1(c1,c2) VALAUES (1,’a’),(null,’b’),(5,’e’); 或者指 INSERT … ON DUPLICATE KEY UPDATE 这类sql语句。 |
接着来分析参数innodb_autoinc_lock_mode,如下表所示:
innodb_autoinc_lock_mode | 说明 |
---|---|
0 | 这是MySQL5.1.22版本之前自增长的实现方式,即通过表锁的AUTO-INC Locking方式,因为有了新的自增长实现方式,0这个选项不应该是新版用户的首选项 |
1 | 这是该参数的默认值。对于simple inserts,该值会用互斥量去对内存中的计数器进行累加的操作,对于bulk inserts,还是使用传统表锁的AUTO-INC Locking方式。在这种配置下,如果不考虑回滚操作,对于自增长列的增长还是连续的,并且在这种方式下,statement-based方式的replication还是能很好地工作。需要注意的是,如果已经使用AUTO-INC Locking方式去产生自增长的值,而这时需要进行simple inserts的操作时,还是需要等待AUTO-INC Locking的释放 |
2 | 在这个模式下,对于所有的insert-like自增长的产生都是通过互斥量,而不是通过AUTO-INC Locking的方式,显然这时性能最高的方式。然而会带来一定的问题。因为并发插入的存在,在每次插入时,自增长的值可能不是连续的。最重要的是,基于Statment-base replication会出现问题。因此,使用这个模式,任何时候都应该使用row-base replication,这样才能保证最大的并发性能及replication主从数据的一致。 |
外键和锁
对于外键值的插入或更新,首先需要查询父表中的记录,即select父表,但是对父表的select操作,不是使用一致性非锁定锁,因为这样会发生数据不一致的问题,因此这时使用的是select … lock in share mode方式,即主动对父表加一个S锁,如果这时父表上已经加了X锁,子表的操作会被阻塞。
原文链接 大专栏 https://www.dazhuanlan.com/2019/08/17/5d576af77b7b6/