15 innodB的隐式锁

本文深入探讨了InnoDB数据库管理系统中隐式锁的工作原理,包括其在事务ID的作用下,如何通过延迟加锁机制减少加锁数量,以及如何在插入操作中实现隐式锁与共享锁的转换,确保数据的一致性和并发操作的高效性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、知识准备之隐式锁

参考:http://www.uml.org.cn/sjjm/201205302.asp

Innodb 实现了一个延迟加锁的机制,来减少加锁的数量,在代码中称为隐式锁(Implicit Lock)。隐式锁中有个重要的元素,事务ID(trx_id)。隐式锁的逻辑过程如下:

A. InnoDB的每条记录中都一个隐含的trx_id字段,这个字段存在于簇索引的B+Tree中。

B. 在操作一条记录前,首先根据记录中的trx_id检查该事务是否是活动的事务(未提交或回滚)。如果是活动的事务,首先将隐式锁转换为显式锁(就是为该事务添加一个锁)。

C. 检查是否有锁冲突,如果有冲突,创建锁,并设置为waiting状态。如果没有冲突不加锁,跳到E。

D. 等待加锁成功,被唤醒,或者超时。

E. 写数据,并将自己的trx_id写入trx_id字段。Page Lock可以保证操作的正确性。


二、具体代码

转自:《InnoDB SMO & Page Extent & Lock & Latch》 by 何登成

InnoDB 的 insert 操作,对插入的记录不加锁,但是此时如果另一个线程进行当前读,类似与以下的用例,session 2 会锁等待 session 1,那么这是如何实现的呢?

session 1:                			session 2:
set autocommit = ‘ off ’;
insert into c values (11, ’ aaa’);
					        select * from c where c1 = 11 lock in share mode;

下面是 session 2 的源码跟踪流程:

row_search_for_mysql();
sel_set_rec_lock();
…
	//  将记录上的 implicit 锁转换为 explicit 锁
	lock_rec_convert_impl_to_expl();
		//  查询当前记录上是否存在 implicit 锁
		// 1.  必须已经持有了 kernel mutex
		// 2.  获取记录上的 DB_TRX_ID 系统列,获取事务 ID
		// 3.  根据事务 ID,判断当前事务是否为活跃事务
		// 4.  若为活跃事务,则返回此活跃事务对象
		impl_trx = lock_clust_rec_some_has_impl(rec, index, offsets);
			ut_ad(mutex_own(&kernel_mutex));
			trx_id = row_get_rec_trx_id();
			trx_is_active(trx_id);
		//  判断返回事务,是否含有 explicit 锁;若有,直接返回;否则将
		// implicit 锁转化为 explicit 锁;由 session 2 完成 session 1 insert 记录的加锁
		lock_rec_has_expl(impl_trx);
		//  当前 session 1 不存在 explicit 锁,因此直接创建一个锁,锁模式为
		// LOCK_REC | LOCK_X | LOCK_REC_NOT_GAP
		//  由于 insert 记录上不可能有其他锁,因此转化直接成功,X 锁加上
		lock_rec_add_to_queue();
//  完成 session 1  的 insert 操作的 implicit 到 explicit 锁转化之后,此时可以加 session 2
//  的 scan S 锁,但是会等待 session 1 放锁
lock_rec_lock();

备注:Insert 不加锁,或者说是 Implicit Lock 的意义其实十分重大。从前面介绍的 Lock 结构中,我们可以分析出,其实 InnoDB 的一个锁结构的开销是比较大的。 或者说InnoDB 锁一条记录的开销,与锁一个页面中所有记录的开销是一样的。而 Insert 通过 Implicit 方式加锁,极大的减轻了 Insert 时的锁模块开销,对于 InnoDB 支持并发 Insert 操作,是一个极大的提升。


三、隐式锁的重要问题——check Duplicate

转自:《InnoDB SMO & Page Extent & Lock & Latch》 by 何登成

进一步参考: Innodb锁系统 Insert/Delete 锁处理及死锁示例分析 http://fan0321.iteye.com/blog/1984364

这里主要看聚集索引的check duplicate。check duplicate也就是保证在隐式锁情况下,多个事务的insert是如何保证索引的unique的。

1、聚集索引check duplicate

ha_innobase::write_row();
	…
	row_ins_index_entry_low();
		//  做 search path,将 cursor 定位到第一个小于等于插入值的位置
		btr_cur_search_to_nth_level(PAGE_CUR_LE);
		// cursor 是 binary search 之后在叶节点定位的 insert 位置
		//  判断 binary search 的结果,当前记录与待 insert 记录有几个相同的列
		//  若相同列取值的列数量(cursor->low_match),超过当前索引的唯一键值数量,
		//  则可能存在唯一性键值冲突
		row_ins_duplicate_error_in_clust(cursor, entry, thr);
			n_unique = dict_index_get_n_uniques();
			if (cursor->low_match >= n_unique)
				//  对 cursor 对应的已有记录加 S 锁(可能会等待),保证记录上的操作,包括:
				// Insert/Update/Delete 已经提交或者回滚
				// S 锁已经可以保证其他事务的 insert 操作不能进行,因为在真正
				// insert 操作进行时,会尝试对 下一个record加 X 锁,详见下一章节分析
				row_ins_set_shared_rec_loc(LOCK_S);
					lock_clust_rec_read_check_and_lock();
						//  判断 cursor 对应的记录上是否存在 implicit 锁(有活跃事务)
						//  若存在,则将 implicit 锁转化为 explicit 锁
						lock_rec_convert_impl_to_expl();
						lock_rec_lock();  //如果上面的隐式锁转化成功,此处加S锁将会等待,直到活跃事务释放锁。
				// S 锁加锁完成之后,可以再次做判断,最终决定是否存在 unique 冲突
				// 1.  判断 insert 记录与 cursor 对应的记录取值是否相同
				// 2.  二级唯一键值锁引,可以存在多个 NULL 值
				// 3.  最后判断记录的 delete_bit 状态,判断记录是否被删除提交
				row_ins_dupl_err_with_rec();
					cmp_dtuple_rec_with_match();
					return !rec_get_deleted_flag();

注意:S锁加锁成功之时,活跃事务应当提交或回滚并释放锁;但不管是提交还是回滚,cursor指向的record仍然存在,可能会有delete标志(发生回滚)。

2、辅助索引check duplicate

a、聚簇索引 primary key 是唯一的;非聚簇唯一索引,其索引也是唯一的。
b、若 insert 记录与聚簇索引项完全相同,并且聚簇索引项为删除项,则直接将其删除标记设置为 0,并在删除项上做 update。
c、若 insert 记录与非聚簇唯一索引项键值完全相同, 并且非聚簇索引项为删除项, 此时并不一定修改项状态,还需要判断两者对应的 primary key 是否相同,若 primary key 也相同,则重用项;否则,插入新项。
d、聚簇索引中,相同 primary key 取值的项,最多只有一项,不可能存在多项。
e、非聚簇唯一索引,索引键值相同的项可能有多项,但是这些项,其 primary  key 是不同的;而且,这些项,只有一个是有效项,其他项都为已删除的提交项。
f、聚簇索引的唯一性检查,只需要检测 insert 对应的记录即可,因为只有一项;非聚簇唯一索引的唯一性检测,需要向后检查多条键值相同记录

<think>好的,我现在需要回答用户关于MySQL中显的区别及用法的问题。首先,我得回忆一下自己之前学过的相关知识,确保信息的准确性。 首先,显的定义。显是由用户或SQL语句明确请求的,比如使用SELECT ... FOR UPDATE或者LOCK TABLES这样的语句。而是数据库引擎自动加上的,比如在插入操作时,为了保证事务的隔离性,InnoDB会自动加,不需要用户干预。这一点我记得引用里的内容提到过,由MySQL内部处理,不需要手动管理[^1]。 接下来,区别方面。用户需要知道两者的控制方、使用场景、类型以及可见性。显是用户主动控制的,比如事务中需要更新数据前先定,防止其他事务修改。而是自动的,比如插入数据时,引擎自动加防止并发问题。类型的话,显可能包括行、表、间隙等,而通常是行级的。可见性方面,显可以通过performance_schema查看,而在转换前可能不可见,但引用里提到在某些情况下会转为显,这时就能查到了[^2]。 然后使用场景。显适合需要明确控制并发的情况,比如复杂的业务逻辑或避免死则用于常规操作,如普通的INSERT、UPDATE,由系统自动处理。这里要注意用户提到的快照读和当前读的区别,显通常用于当前读,比如SELECT ... FOR UPDATE,而快照读可能依赖MVCC,不需要加[^3]。 还需要考虑事务类型。显通常在显事务中使用,比如用START TRANSACTION明确开始的事务。可能在事务中,比如自动提交模下的单条语句。这点引用里提到显通常用LOCK TABLES或者SELECT ... FOR UPDATE,而自动处理[^3]。 另外,要提到两者的转换,比如当遇到冲突时,可能会升级为显,这样用户就能在performance_schema中看到了。这部分需要结合引用中的内容[^2]。 最后,总结区别的对比表格,帮助用户一目了然。同时生成相关问题,比如如何监控,显的常见命令,如何避免死等,这些都需要根据之前的内容来设计。 检查是否有遗漏的点,比如的兼容性、不同隔离级别下的表现,但用户的问题主要区别和使用场景,可能不需要深入细节。不过要确保提到的点准确,比如显的使用命令和的自动管理,以及引用对应的来源。 现在把这些内容组织成结构清晰的回答,分点说明,并确保LaTeX格正确,比如行内公用$...$,独立公用$$...$$,但当前问题可能不需要数学公,所以重点在格和引用正确。</think>### MySQL显的区别及用法 #### 一、核心区别 1. **控制方** - **显**:由用户通过SQL语句(如`SELECT ... FOR UPDATE`、`LOCK TABLES`)或事务命令主动声明。 - ****:由MySQL引擎自动管理,例如插入数据时自动对索引加,无需用户干预[^2]。 2. **使用场景** - **显**:适用于需要强制控制并发访问的场景,如避免脏读、幻读或实现悲观机制。 - ****:用于常规数据操作(如`INSERT`、普通`UPDATE`),由InnoDB保证事务隔离性。 3. **类型与可见性** - **显**:可通过`performance_schema.data_locks`直接查询,支持表、行、间隙等。 - ****:默认不可见,仅在发生冲突时可能转换为显(如二级索引插入时触发升级)。 4. **事务关联性** - **显**:需在显事务(`START TRANSACTION`)中配合使用,例如`SELECT ... FOR UPDATE`定读取行[^3]。 - ****:自动绑定到事务(如自动提交模下的单条语句)。 #### 二、典型用法对比 | **特性** | 显 | | |------------------|---------------------------------|---------------------------------| | **控制权** | 用户主动声明 | 引擎自动管理 | | **可见性** | 可直接监控 | 冲突时转为显才可见 | | **粒度** | 支持表级、行级、间隙 | 主要为行级 | | **典型语句** | `LOCK TABLES`, `FOR UPDATE` | `INSERT`, 普通`UPDATE` | | **事务要求** | 需显事务支持 | /显事务均可 | #### 三、应用示例 1. **显** ```sql START TRANSACTION; SELECT * FROM orders WHERE id = 100 FOR UPDATE; -- 显定行 UPDATE orders SET amount = 200 WHERE id = 100; COMMIT; ``` *用途*:确保其他事务无法修改`id=100`的行,直到当前事务提交。 2. **** ```sql INSERT INTO products (id, name) VALUES (101, 'Phone'); -- InnoDB自动对id=101加 ``` *用途*:防止其他事务同时插入相同主键,保证唯一性。 #### 四、注意事项 1. **的转换** 当与显冲突时(如另一个事务尝试修改被定的行),InnoDB会将其转换为显,此时可通过`performance_schema`查看[^2]。 2. **死风险** 显需谨慎设计加顺序,因自动管理通常更安全,但批量操作仍可能引发竞争。 3. **隔离级别影响** 在`READ COMMITTED`级别下,显可能仅定现有行;在`REPEATABLE READ`级别下,可能附加间隙防止幻读[^3]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值