MySQL锁介绍
数据库锁分类
| 锁模式分类 | 乐观锁、悲观锁 |
|---|---|
| 范围锁 | 行锁、表锁、页锁 |
| 算法锁 | 临间锁、间隙锁、记录锁 |
| 属性锁 | 共享锁(读锁)、排他锁(写锁) |
| 状态锁 | 意向共享锁、意向排他锁 |
一、乐观锁与悲观锁
-
悲观并发控制(Pessimistic Concurrency Control)
悲观锁(Pessimistic Locking)它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度(悲观),因此,在整个数据处理过程中,将数据处于锁定状态。 悲观锁的实现,往往依靠数据库提供的锁机制 (也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)
-
悲观锁流程
在对任意记录进行修改前,先尝试为该记录加上排他锁(exclusive locking)
如果加锁失败,说明该记录正在被修改,那么当前查询可能要等待或者抛出异常。 具体响应方式由开发者根据实际需要决定。
如果成功加锁,那么就可以对记录做修改,事务完成后就会解锁了。
其间如果有其他对该记录做修改或加排他锁的操作,都会等待我们解锁或直接抛出异常。 -
悲观锁使用
首先我们得关闭MySQL中的autocommit属性或执行数据操作前开始事务,因为mysql默认使用自动提交模式,也就是说当我们进行数据操作的时候,MySQL会将这个操作当做一个事务并且自动提交这个操作。
-- 查看 MySQL 客户端的事务提交方式命令: select @@autocommit; -- 修改 MySQL 客户端的事务提交方式为手动提交命令: set autocommit = 0;举例说明:
1.手动增加排它锁,但是并没有关闭autocommit。
select * from account where id = 1 for update; /*查询id为1记录*/ +----+--------+---------+ | id | user | balance | +----+--------+---------+ | 1 | 张三 | 1000 | +----+--------+---------+ update account set balance=balance-100 where id=1; select * from account where id = 1 for update; +----+--------+---------+ | id | user | balance | +----+--------+---------+ | 1 | 张三 | 900 | +----+--------+---------+2.正常流程
-- 窗口1 set autocommit=0; -- 这里锁的是表 select * from account for update; +----+--------+---------+ | id | user | balance | +----+--------+---------+ | 1 | 张三 | 1000 | | 2 | 李四 | 1000 | +----+--------+---------+ -- 窗口2 update account set balance = balance - 100 where id = 1; /*执行时会显示等待状态,如果窗口1一直不提交事务,则会出现超时现象。窗口1执行commit提交事务后此操作才执行成功*/ -- 窗口1 commit; select * from account; +----+--------+---------+ | id | user | balance | +----+--------+---------+ | 1 | 张三 | 900 | | 2 | 李四 | 1000 | +----+--------+---------+上面的例子展示了排它锁的原理:一个锁在某一时刻只能被一个线程占有,其它线程必须等待锁被释放之后才可能获取到锁或者进行数据的操作。
3.悲观锁的优点和不足
优点:悲观锁实际上是采取了“先取锁在访问”的策略,为数据的处理安全提供了保证。
缺点:在效率方面,由于额外的加锁机制产生了额外的开销,并且增加了死锁的机会。并且降低了并发性;当一个事务处理一行数据的时候,其他事务必须等待该事务提交之后,才能操作这行数据。
-
-
乐观并发控制(Optimistic Concurrency Control)
乐观锁( Optimistic Locking ) 相对悲观锁而言,乐观锁假设认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让返回用户错误的信息,让用户决定如何去做。乐观锁的实现方式主要有两种:CAS机制和版本号机制,下面详细介绍。
-
CAS(Compare And Swap)操作包括了3个操作数。
-
需要读写的内存位置(V)
-
进行比较的预期值(A)
-
拟写入的新值(B)
CAS操作逻辑如下:如果内存位置V的值等于预期的A值,则将该位置更新为新值B,否则不进行任何操作。许多CAS的操作是自旋的:如果操作不成功,会一直重试,直到操作成功为止。
这里引出一个新的问题,既然CAS包含了Compare(比较)和Swap(交换)两个操作,它又如何保证原子性呢?答案是:CAS是由CPU支持的原子操作,其原子性是在硬件层面进行保证的。
举例说明CAS实现方法:
-- 查询出张三余额信息 select balance from account where id = 1; +---------+ | balance | +---------+ | 900 | +---------+ -- 增加张三余额100 update account set balance = balance+100 where id = 1 and balance = 900; -- 查看余额 +---------+ | balance | +---------+ | 1000 | +---------+当多个线程尝试使用CAS同时更新张三余额时,只有其中一个线程能更新成功,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。
CAS机制引发的经典ABA问题
- 现在有一个变量
count=10,现在有三个线程,分别为A、B、C。 - 线程A和线程C同时读到count变量,所以线程A和线程C的内存值和预期值都为10
- 此时线程A使用CAS将count变量修改为100
- 修改完后,就在这时,线程B进来了,读取得到count的值为100(内存值和预期值都是100),将count值修改成10
- 线程C拿到执行权,发现内存值是10,预期值也是10,将count值修改成11
此时可以看到,我们重点放在线程C上面,虽然我们C成功的修改了值。但是内存值和预期值和我们原来的相同,C却不知道之前这个变量已经被A、B两个线程操作过了。所以就会有一定的风险。我们针对这个思考,如果变量的值只能朝着一个方向转换,比如A到B,B再到C,不构成环形,就不会存在问题。通过版本号(时间戳)机制解决了该问题!
-
版本号机制。
除了CAS,版本号机制也可以用来实现乐观锁。版本号机制的基本思路是在数据中增加一个字段version,表示该数据的版本号,每当数据被修改,版本号加1。当某个线程查询数据时,将该数据的版本号一起查出来;当该线程更新数据时,判断当前版本号与之前读取的版本号是否一致,如果一致才进行操作。
需要注意的是,这里使用了版本号作为判断数据变化的标记,实际上可以根据实际情况选用其他能够标记数据版本的字段,如时间戳等。
举例说明版本号机制实现方法:
-- 添加版本号字段 alter table account add version int(255) default 0; -- 重复CAS执行case。 select version from account where id = 1; +---------+ | version | +---------+ | 0 | +---------+ -- 增加张三余额100 update account set balance=balance+100,version=version+1 where id=1 and version=0; -- 查看张三数据 select * from account where id = 1; +----+--------+---------+---------+ | id | user | balance | version | +----+--------+---------+---------+ | 1 | 张三 | 1100 | 1 | +----+--------+---------+---------+乐观锁每次在执行数据的修改操作时,都会带上一个版本号,一旦版本号和数据的版本号一致就可以执行修改操作并对版本号执行+1操作,否则就执行失败。因为每次操作的版本号都会随之增加,所以不会出现ABA问题,因为版本号只会增加不会减少。除了version以外,还可以使用时间戳,因为时间戳天然具有顺序递增性。
-
-
乐观锁适用的场景受到了更多的限制,无论是CAS还是版本号机制。
例如,CAS只能保证单个变量操作的原子性,当涉及到多个变量时,CAS是无能为力的,而悲观锁则可以通过对行或表加锁来处理。再比如版本号机制,如果(SELECT)查询数据的时候是针对表A,而更新数据(UODATE)的时候是针对表2,也很难通过简单的版本号来实现乐观锁。
-
如果悲观锁和乐观锁都可以使用,那么选择就要考虑竞争的激烈程度:
-
当竞争不激烈 (出现并发冲突的概率小)时,乐观锁更有优势,因为悲观锁会锁住数据,其他事务无法同时访问,影响并发,而且加锁和释放锁都需要消耗额外的资源。
-
当竞争激烈(出现并发冲突的概率大)时,悲观锁更有优势,因为乐观锁在执行更新时频繁失败,需要不断重试,浪费CPU资源。
-
二、范围锁(行锁、表锁)及其属性锁
-
行锁介绍
-
锁一行或者多行记录,MySQL(InnoDB)的行锁是基于索引加载的,所以行锁是要加在索引响应的行上,即命中索引。
-
开销大,加锁慢;会出现死锁;锁定粒度小,发生锁冲突的概率低,并发度高。
-
使用方法
-
共享锁用法(S锁 读锁):
若事务T对数据对象A加上S锁,则事务T可以读A但不能修改A,其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S锁。这保证了其他事务可以读A,但在T释放A上的S锁之前不能对A做任何修改。
select ... lock in share mode;举例:
-- 窗口A start transaction; select * from account where id = 1 lock in share mode; -- 窗口B加X锁 select * from account where id = 1 for update; /*会一直处于等待状态*/ -- 窗口A提交事务后将锁释放。 commit; -- 窗口B可以加X锁 +----+--------+---------+---------+ | id | user | balance | version | +----+--------+---------+---------+ | 1 | 张三 | 1100 | 1 | +----+--------+---------+---------+ -
排它锁用法(X 锁 写锁):
若事务T对数据对象A加上X锁,事务T可以读A也可以修改A,其他事务不能再对A加任何锁,直到T释放A上的锁。这保证了其他事务在T释放A上的锁之前不能再读取和修改A。 排它锁,也称作独占锁,一个锁在某一时刻只能被一个线程占有,其它线程必须等待锁被释放之后才可能获取到锁。
select ... for update;举例:在上面的悲观锁中已经说明。
-
-
获取InnoDB行锁争用情况
可以通过检查InnoDB_row_lock状态变量来分析系统上的行锁的争夺情况
show status like 'innodb_row_lock%'; +-------------------------------+--------+ | Variable_name | Value | +-------------------------------+--------+ | Innodb_row_lock_current_waits | 0 | | Innodb_row_lock_time | 190608 | | Innodb_row_lock_time_avg | 23826 | | Innodb_row_lock_time_max | 51636 | | Innodb_row_lock_waits | 8 | +-------------------------------+--------+
-
-
表锁介绍
-
表锁会对整张表进行加锁。
-
资源开销比行锁少,不会出现死锁的情况,但是发生锁冲突的概率很大。
-
被大部分的MySQL引擎支持,MyISAM和InnoDB都支持表级锁,但是InnoDB默认的是行级锁。
-
使用方法
-
共享锁用法(S锁 读锁):
LOCK TABLE table_name [AS alias_name] READ; -
排它锁用法(X 锁 写锁):
LOCK TABLE table_name [AS alias_name][ LOW_PRIORITY ] WRITE -
解锁
UNLOCK tables;
-
-
查询表级锁争用情况
可以通过检查table_locks_waited和table_locks_immediate状态变量来分析系统上的表锁定争夺;
show status like 'table%'; +-----------------------+-------+ | Variable_name | Value | +-----------------------+-------+ | Table_locks_immediate | 43448 | | Table_locks_waited | 0 | +-----------------------+-------+ -
并发插入(Concurrent Inserts)
MyISAM表也支持查询和插入操作的并发进行。MyISAM存储引擎有一个系统变量concurrent_insert,专门用以控制其并发插入的行为,其值分别可以为0、1或2。该特性使得执行
INSERT操作的时候允许同时SELECT,并减少锁争抢行为。- 当concurrent_insert设置为0时,不允许并发插入。
- 当concurrent_insert设置为1时,如果MyISAM表中没有空洞(即表的中间没有被删除的行),MyISAM允许在一个进程读表的同时,另一个进程从表尾插入记录。这也是MySQL的默认设置。
- 当concurrent_insert设置为2时,无论MyISAM表中有没有空洞,都允许在表尾并发插入记录。
举例:
session_1 session_2 lock table account read; insert into account values(‘王五’, 1000, 0);
ERROR 1099 (HY000): Table ‘account’ was locked with a READ lock and can’t be updated其他session可以进行插入操作,但是更新会等待:
insert into account values(‘王五’, 1000, 0);
Query OK, 1 row affected (0.00 sec)
update account set balance = balance+100 where id = 3;
等待当前session不能访问其他session插入的记录:
select * from account where id = 3;
Empty set (0.00 sec)释放锁:
unlock tables;
Query OK, 0 rows affected (0.00 sec)等待 当前session解锁后可以获得其他session插入的记录: Session2获得锁,更新操作完成:
update account set balance = balance+100 where id = 3;
Query OK, 1 row affected (1 min 17.75 sec)Rows matched: 1 Changed: 1 Warnings: 0可以利用MyISAM存储引擎的并发插入特性,来解决应用中对同一表查询和插入的锁争用。例如,将concurrent_insert系统变量设为2,总是允许并发插入;同时,通过定期在系统空闲时段执行 OPTIMIZE TABLE语句来整理空间碎片,收回因删除记录而产生的中间空洞。
-
-
页锁介绍
-
页级锁是 MySQL 中比较独特的一种锁定级别,在其他数据库管理软件中并不常见。
-
开销和加锁速度介于表锁和行锁之间;会出现死锁;锁定粒度介于表锁和行锁之间,并发度一般
-
-
锁比较
行锁 表锁 页锁 MyISAM √ BDB(已被InnoDB取代) √ √ InnoDB √ √
三、记录锁、间隙锁、临键锁
-
记录锁(Record Locks)
记录锁很好理解,一般要通过主键或唯一索引加锁,就可以较好的实现。
select * from account where id=1 for update; -
间隙锁(Gap Locks)
间隙锁锁定的时一个开区间,而不是某个键,它是基于非唯一索引。需要注意的是用非唯一索引时,要explain下,确保sql走了索引(mysql查询优化器认为全表扫描比用索引更快时会锁表)。那么什么情况下出现间隙所呢?
产生间隙锁的条件(InnoDB默认事务隔离级别RR下):
- 使用普通索引锁定;
- 使用多列唯一索引;
- 使用唯一索引锁定多行记录。
-
记录锁与间隙锁的产生,举例如下:
-
唯一索引的间隙锁
CREATE TABLE `test` ( `id` int(1) NOT NULL AUTO_INCREMENT, `name` varchar(8) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; INSERT INTO `test` VALUES ('1', '小罗'); INSERT INTO `test` VALUES ('5', '小黄'); INSERT INTO `test` VALUES ('7', '小明'); INSERT INTO `test` VALUES ('11','小红');产生记录锁
/* 开启事务1 */ BEGIN; /* 查询 id = 5 的数据并加记录锁 */ SELECT * FROM `test` WHERE `id` = 5 FOR UPDATE; # 注意:以下的语句不是放在一个事务中执行,而是分开多次执行,每次事务中只有一条添加语句 /* 事务2插入一条 name = '小张' 的数据 */ INSERT INTO `test` (`id`, `name`) VALUES (4, '小张'); # 正常执行 /* 事务3插入一条 name = '小张' 的数据 */ INSERT INTO `test` (`id`, `name`) VALUES (8, '小东'); # 正常执行 /* 提交事务1,释放事务1的锁 */ COMMIT;以上由于主键是唯一索引,而且是只使用一个索引查询,并且只锁定一条记录,所以以上的例子,只会对 id = 5 的数据加上记录锁,而不会产生间隙锁。
产生间隙锁
/* 开启事务1 */ BEGIN; /* 查询 id 在 5 - 7 范围的数据并加记录锁 */ SELECT * FROM `test` WHERE `id` BETWEEN 5 AND 7 FOR UPDATE; # 注意:以下的语句不是放在一个事务中执行,而是分开多次执行,每次事务中只有一条添加语句 /* 事务2插入一条 id = 3,name = '小张1' 的数据 */ INSERT INTO `test` (`id`, `name`) VALUES (3, '小张1'); # 正常执行 /* 事务3插入一条 id = 4,name = '小白' 的数据 */ INSERT INTO `test` (`id`, `name`) VALUES (4, '小白'); # 正常执行 /* 事务4插入一条 id = 6,name = '小东' 的数据 */ INSERT INTO `test` (`id`, `name`) VALUES (6, '小东'); # 阻塞 /* 事务5插入一条 id = 8, name = '大罗' 的数据 */ INSERT INTO `test` (`id`, `name`) VALUES (8, '大罗'); # 阻塞 /* 事务6插入一条 id = 9, name = '大东' 的数据 */ INSERT INTO `test` (`id`, `name`) VALUES (9, '大东'); # 阻塞 /* 事务7插入一条 id = 10, name = '李西' 的数据 */ INSERT INTO `test` (`id`, `name`) VALUES (10, '李西'); # 阻塞 /* 事务8插入一条 id = 12, name = '张三' 的数据 */ INSERT INTO `test` (`id`, `name`) VALUES (12, '张三'); # 正常执行 /* 提交事务1,释放事务1的锁 */ COMMIT;从上面我们可以看到,(5, 7)、(7, 11) 这两个区间,都不可插入数据,其它区间,都可以正常插入数据。所以我们可以得出结论:当我们给 (5, 7) 这个区间加锁的时候,会锁住 (5, 7)、(7, 11) 这两个区间。
如果我们锁住不存在的数据时,会怎样:
/* 开启事务1 */ BEGIN; /* 查询 id = 3 这一条不存在的数据并加记录锁 */ SELECT * FROM `test` WHERE `id` = 3 FOR UPDATE; # 注意:以下的语句不是放在一个事务中执行,而是分开多次执行,每次事务中只有一条添加语句 /* 事务2插入一条 id = 3,name = '小张1' 的数据 */ INSERT INTO `test` (`id`, `name`) VALUES (2, '小张1'); # 阻塞 /* 事务3插入一条 id = 4,name = '小白' 的数据 */ INSERT INTO `test` (`id`, `name`) VALUES (4, '小白'); # 阻塞 /* 事务4插入一条 id = 6,name = '小东' 的数据 */ INSERT INTO `test` (`id`, `name`) VALUES (6, '小东'); # 正常执行 /* 事务5插入一条 id = 8, name = '大罗' 的数据 */ INSERT INTO `test` (`id`, `name`) VALUES (8, '大罗'); # 正常执行 /* 提交事务1,释放事务1的锁 */ COMMIT;我们可以看出,指定查询某一条记录时,如果这条记录不存在,会产生间隙锁。
-
普通索引的间隙锁
CREATE TABLE `test1` ( `id` int(1) NOT NULL AUTO_INCREMENT, `number` int(1) NOT NULL COMMENT '数字', PRIMARY KEY (`id`), KEY `number` (`number`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; INSERT INTO `test1` VALUES (1, 1); INSERT INTO `test1` VALUES (5, 3); INSERT INTO `test1` VALUES (7, 8); INSERT INTO `test1` VALUES (11, 12);/* 开启事务1 */ BEGIN; /* 查询 number = 3 的数据并加记录锁 */ SELECT * FROM `test1` WHERE `number` = 3 FOR UPDATE; # 注意:以下的语句不是放在一个事务中执行,而是分开多次执行,每次事务中只有一条添加语句 /* 事务2插入一条 number = 0 的数据 */ INSERT INTO `test1` (`number`) VALUES (0); # 正常执行 /* 事务3插入一条 number = 1 的数据 */ INSERT INTO `test1` (`number`) VALUES (1); # 被阻塞 /* 事务4插入一条 number = 2 的数据 */ INSERT INTO `test1` (`number`) VALUES (2); # 被阻塞 /* 事务5插入一条 number = 4 的数据 */ INSERT INTO `test1` (`number`) VALUES (4); # 被阻塞 /* 事务6插入一条 number = 8 的数据 */ INSERT INTO `test1` (`number`) VALUES (8); # 正常执行 /* 事务7插入一条 number = 9 的数据 */ INSERT INTO `test1` (`number`) VALUES (9); # 正常执行 /* 事务8插入一条 number = 10 的数据 */ INSERT INTO `test1` (`number`) VALUES (10); # 正常执行 /* 提交事务1 */ COMMIT; -- 查看表中数据 +----+--------+ | id | number | +----+--------+ | 14 | 0 | | 1 | 1 | | 5 | 3 | | 7 | 8 | | 18 | 8 | | 19 | 9 | | 20 | 10 | | 11 | 12 | +----+--------+这里可以看到,number (1 - 8) 的间隙中,插入语句都被阻塞了,而不在这个范围内的语句,正常执行,这就是因为有间隙锁的原因。我们再进行以下的测试,方便我们更好的理解间隙锁的区域(我们要将数据还原成原来的那样):
/* 开启事务1 */ BEGIN; /* 查询 number = 3 的数据并加记录锁 */ SELECT * FROM `test1` WHERE `number` = 3 FOR UPDATE; /* 事务1插入一条 id = 2, number = 1 的数据 */ INSERT INTO `test1` (`id`, `number`) VALUES (2, 1); # 阻塞 /* 事务2插入一条 id = 3, number = 2 的数据 */ INSERT INTO `test1` (`id`, `number`) VALUES (3, 2); # 阻塞 /* 事务3插入一条 id = 6, number = 8 的数据 */ INSERT INTO `test1` (`id`, `number`) VALUES (6, 8); # 阻塞 /* 事务4插入一条 id = 8, number = 8 的数据 */ INSERT INTO `test1` (`id`, `number`) VALUES (8, 8); # 正常执行 /* 事务5插入一条 id = 9, number = 9 的数据 */ INSERT INTO `test1` (`id`, `number`) VALUES (9, 9); # 正常执行 /* 事务6插入一条 id = 10, number = 12 的数据 */ INSERT INTO `test1` (`id`, `number`) VALUES (10, 12); # 正常执行 /* 事务7修改 id = 11, number = 12 的数据 */ UPDATE `test1` SET `number` = 5 WHERE `id` = 11 AND `number` = 12; # 阻塞 /* 提交事务1 */ COMMIT;这里有一个奇怪的现象:
- 事务3添加 id = 6,number = 8 的数据,给阻塞了;
- 事务4添加 id = 8,number = 8 的数据,正常执行了。
- 事务7将 id = 11,number = 12 的数据修改为 id = 11, number = 5的操作,给阻塞了;
看下图:
从图中可以看出,当 number 相同时,会根据主键 id 来排序,所以:
- 事务3添加的 id = 6,number = 8,这条数据是在 (3, 8) 的区间里边,所以会被阻塞;
- 事务4添加的 id = 8,number = 8,这条数据则是在(8, 12)区间里边,所以不会被阻塞;
- 事务7的修改语句相当于在 (3, 8) 的区间里边插入一条数据,所以也被阻塞了。
结论
- 在普通索引列上,不管是何种查询,只要加锁,都会产生间隙锁,这跟唯一索引不一样;
- 在普通索引跟唯一索引中,数据间隙的分析,数据行是优先根据普通索引排序,再根据唯一索引排序。
-
-
临键锁(Next-key Locks)
临键锁,是记录锁与间隙锁的组合,它的封锁范围,既包含索引记录,又包含索引区间。
注:临键锁的主要目的,也是为了避免幻读(Phantom Read)。如果把事务的隔离级别降级为RC,临键锁则也会失效。
-
总结
- 记录锁、间隙锁、临键锁,都属于排它锁;
- 记录锁就是锁住一行记录;
- 间隙锁只有在事务隔离级别 RR 中才会产生;
- 唯一索引只有锁住多条记录或者一条不存在的记录的时候,才会产生间隙锁,指定给某条存在的记录加锁的时候,只会加记录锁,不会产生间隙锁;
- 普通索引不管是锁住单条,还是多条记录,都会产生间隙锁;
- 间隙锁会封锁该条记录相邻两个键之间的空白区域,防止其它事务在这个区域内插入、修改、删除数据,这是为了防止出现 幻读 现象;
- 普通索引的间隙,优先以普通索引排序,然后再根据主键索引排序(多普通索引情况还未研究);
- 事务级别是RC(读已提交)级别的话,间隙锁将会失效。
四、状态锁(意向共享锁、意向排他锁)
-
共享锁(S)、排他锁(X)
上文已经说明。
-
意向共享锁(IS)和意向排他锁(IX)
如果事务想要给表中几行数据加上行级共享锁,那么需要先在表级别加上意向共享锁(IS);
如果事务想要给表中几行数据加上行级排他锁,那么需要先在表级别加上意向排他锁(IX)(注意:如果是想要加表级S锁或X锁,不需要先加意向锁。)
-
意向锁的作用:
举个例子:
事务A(S)锁住了表中的一行,让这一行只能读,不能写。
之后,事务B申请整个表的写锁。
如果事务B申请成功,那么理论上它就能修改表中的任意一行,这与A持有的行锁是冲突的。
数据库需要避免这种冲突,就是说要让B的申请被阻塞,直到A释放了行锁。
数据库要怎么判断这个冲突呢?
step1:判断表是否已被其他事务用表锁锁表
step2:判断表中的每一行是否已被行锁锁住。
注意step2,这样的判断方法效率太低,因为需要遍历整个表。
于是就有了意向锁。
在意向锁存在的情况下,事务A必须先申请表的意向共享锁,成功后再申请一行的行锁。
在意向锁存在的情况下,上面的判断可以改成
step1:不变
step2:发现表上有意向共享锁,说明表中有些行被共享行锁锁住了,因此,事务B申请表的写锁会被阻塞。
注意:申请意向锁的动作是数据库完成的,就是说,事务A申请一行的行锁的时候,数据库会自动先开始申请表的意向锁,不需要我们程序员使用代码来申请。
-
以下是S、X锁和意向锁的兼容性

五、死锁
关于死锁
MyISAM表锁是无死锁的,这是因为MyISAM总是一次获得所需的全部锁,要么全部满足,要么等待,因此不会出现死锁。但在InnoDB中,除单个SQL组成的事务外,锁是逐步获得的,这就决定了在InnoDB中发生死锁是可能的。如下所示的就是一个发生死锁的例子。
| session_1 | session_2 |
|---|---|
| set autocommit = 0; select * from table_1 where id=1 for update; | set autocommit = 0; select * from table_2 where id=1 for update; |
| select * from table_2 where id =1 for update; 因session_2已取得排他锁,等待 | 做一些其他处理… |
| select * from table_1 where where id=1 for update; |
六、问题
- 为什么InnoDB可以支持行锁、而MyISAM不支持行锁。
- 为什么唯一索引查询某一条记录只会产生记录锁,但普通索引查询某一条记录会产生间隙锁呢。
- 唯一索引与主键索引的区别?
本文详细阐述了MySQL中的悲观锁(如行锁、表锁、页锁)与乐观锁(如CAS与版本号机制)的工作原理,对比了它们的优缺点,以及不同场景下的选择建议。此外,还介绍了状态锁(意向锁)的概念和死锁的原理。
640

被折叠的 条评论
为什么被折叠?



