mysql innodb行级锁的理解,究竟是什么解决了幻读

行锁

  行锁是为了最大并发化所提供的一种锁,封锁某一行数据。我知道的mysql行锁有三种,就间隙锁使用场景,我分成了唯一索引非唯一索引两种情况。记住所有的for update都是当前读并且加上行锁,跟快照读不一样,你需要明白这个问题。

  • Record Lock: 记录锁,就是字面意思锁定某一行数据,值得注意的是,只有通过索引进行检索的时候才会使用行级锁,如果不是通过索引进行检索就会升级成表锁。
  • gap Lock: 间隙锁,开区间范围性加锁
  • Next-key Lock: gap+record,闭区间范围性加锁

当主键作为索引的时候,主键肯定是唯一索引

为了防止混乱,我用两张不同的表进行了实验

Record Lock

查看表索引:

mysql> show index from account;
+---------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table   | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+---------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| account |          0 | PRIMARY  |            1 | id          | A         |           4 |     NULL | NULL   |      | BTREE      |         |               |
+---------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+

行锁:

//事务A锁住id为1的
mysql> select * from account where id='1' for update;
+----+------+-------+
| id | name | money |
+----+------+-------+
|  1 | aaa  |  3000 |
+----+------+-------+
1 row in set (0.00 sec)

//事务B获得行级写锁id为1的时候失败
mysql> select * from account where id='1' for update;
^C^C -- query aborted

//获得行级写锁ID为2成功,说明了并没有发生间隙锁
mysql> select * from account where id='2' for update;
+----+------+-------+
| id | name | money |
+----+------+-------+
|  2 | bbb  |  5000 |
+----+------+-------+
1 row in set (0.00 sec)

行锁变表锁:

// 事务A 
mysql> select * from account where name='aaa' for update;
+----+------+-------+
| id | name | money |
+----+------+-------+
|  1 | aaa  |  3000 |
+----+------+-------+
1 row in set (0.00 sec)
//事务B,阻塞
mysql> select * from account where id=1 for update;
^C^C -- query aborted
mysql> select * from account where id=2 for update;
^C^C -- query aborted
mysql> select * from account for update;
^C^C -- query aborted

分析:
  为了让实验更明显,这里是手动进行select for update进行写加锁,事务A通过非索引name进行搜索,此时它搜索的是整个表,所以此时不会只锁那一行,而是将整个表锁住,此时事务B进行获取写锁的时候就会被阻塞,无法获得想要的写锁。

Gap Lock间隙锁

//事务A,指明了区间1,+无穷
mysql> select * from account where id>1 for update;
+----+------+-------+
| id | name | money |
+----+------+-------+
|  2 | bbb  |  5000 |
|  3 | ccc  |  5000 |
|  5 | ddd  |  5000 |
|  6 | eee  |  5000 |
+----+------+-------+
4 rows in set (0.00 sec)

//事务B,阻塞
mysql> select * from account where id='3' for update;
^C^C -- query aborted

next-key lock

和gap一样,只是区间不同

总结

  我们可以看到,如果索引是在唯一索引上,那么它的这个间隙是不存在的,这不是真正的间隙,除了手动指明区间的时候它才会对那个区间进行封锁,比如>10是(10,+无穷),>=10是[10,+无穷),下面我们看看当非唯一索引的情况。

非唯一索引

表结构:

mysql> desc test2;
+-------+---------+------+-----+---------+-------+
| Field | Type    | Null | Key | Default | Extra |
+-------+---------+------+-----+---------+-------+
| id    | int(11) | NO   | PRI | NULL    |       |
| value | int(11) | YES  | MUL | NULL    |       |
+-------+---------+------+-----+---------+-------+

表索引:

mysql> show index from test2;
+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| test2 |          0 | PRIMARY  |            1 | id          | A         |           3 |     NULL | NULL   |      | BTREE      |         |               |
| test2 |          1 | test     |            1 | value       | A         |           3 |     NULL | NULL   | YES  | BTREE      |         |               |
+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+

主键是唯一索引,新添加一个普通索引在value上。
实验数据:

mysql> select * from test2;
+----+-------+
| id | value |
+----+-------+
|  1 |     1 |
|  3 |     3 |
|  5 |     5 |
+----+-------+

next-key Lock

一般情况下

  有了上面唯一索引的经验,以value=3为例,手动为value=3的那一行进行加锁,那么锁的范围就是(1,3]+[3,5),也就是(1,3)和(3,5)加上3本身。也就是这个范围内的值不能再作为value被使用.

|  1 |     1 |
xxxxxxxxxxxxxx间隙
|  3 |     3 |
xxxxxxxxxxxxxx间隙
|  5 |     5 |
//事务A进行手动加锁
select * from test where value=3 for update;
//事务B
//分别尝试对间隙进行插入
mysql> insert into test2 values(2,2);
^C^C -- query aborted
mysql> insert into test2 values(4,4);
^C^C -- query aborted
//验证开区间
mysql> update test2 set value=0 where value =1;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0
//验证行级锁
mysql> update test2 set value=0 where value=3;
^C^C -- query aborted
当那一行不存在的时候

情况A:表内区间

//事务A
mysql> update test2 set value=4 where value=4;

//事务B,尝试对不存在的那一行进行插入,阻塞,其实它的封锁范围是(3,5),也就是说4不能再被使用
mysql> insert into test2 values(4,4);
^C^C -- query aborted

情况B:非表内区间

//事务A
mysql> update test2 set value=0 where value=10000;

//事务B,全部阻塞,由此看出封锁区间是(5,+无穷)
mysql> insert into test2 values(1111111,1111111);
^C^C -- query aborted
mysql> insert into test2 values(200,200);
^C^C -- query aborted
mysql> insert into test2 values(6,6);
^C^C -- query aborted

到此,间隙锁就基本上分析清楚了

总结

  • 间隙锁一般都是针对非唯一索引而言的,它会对相邻的区间进行封锁,被锁上的区间内的值是无法再被使用的。当那个条件不存在的时候会找表中存在的相邻数据,然后再进行加锁。
  • 当主键做为条件的时候,不存在间隙锁,除非手动指定范围。

解决幻读

幻读的种类分为两种

  • 当前读,这种手动带锁的读就是当前读,每次读依据非唯一索引作为条件,进行范围读取,某一个where范围内被锁住,这个范围内不允许insert,所以就不会有幻读,避免了这一个问题,即使是唯一索引的情况,手动指明范围也是锁住了的。
  • 快照读,对于普通的select语句,是不需要加锁的,用的是MVCC解决的幻读,请参考上篇博客https://blog.csdn.net/qq_41376740/article/details/82314915

如有错误,还望指正,可能理解比较片面

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: innodb行级MySQL数据库中一种非常常用的机制,它可以实现对表中某一行数据的并发写操作。但是在一些特定的情况下,例如在使用大量的情况下,innodb行级可能会升级为表。在这种情况下,对整个表的访问就会受到限制,可能会影响数据库的性能和并发能力。如果您需要进一步了解关于innodb行级和表的信息,建议您查看MySQL的官方文档或者咨询相关的数据库专业人士。 ### 回答2: InnoDBMySQL数据库中一种常见的存储引擎,它支持行级,这意味着在事务中只定需要访问的行,而不是整个表。但有时,行级可能会升级为表。 当一条SQL语句需要定大部分或全部的表时,InnoDB会将行级升级为表。这种情况下,InnoDB会自动将当前升级为表级别的,以提高性能和减少开销。表可以更快地定整个表,而不需要为每一行加行级升级为表可能发生在以下情况下: 1. 使用不可重复隔离级别的事务:当一个事务正在进行操作时,如果另一个事务已经对该表进行了修改操作,那么为了保证数据的一致性,InnoDB会将行级升级为表级。 2. 查询:当一个查询使用了FOR UPDATE或LOCK IN SHARE MODE时,InnoDB会将行级升级为表级,以确保查询的结果不会被其他事务修改。 3. 大事务:当一个事务涉及到大量的行,或需要定多个表时,为了避免冲突和死InnoDB可能会将行级升级为表级。 总的来说,InnoDB行级升级为表级是为了保证事务的一致性和数据的完整性。虽然表级可能会降低并发性能,但可以减少冲突和死的概率。在设计数据库应用程序时,应该根据实际需求和性能需求来选择适当的策略。 ### 回答3: InnoDBMySQL的一种存储引擎,它支持行级和表级行级是指对于同一张表的不同行,可以同时进行取和修改操作,而表级则是指对整张表进行操作时会对整张表加,其他事务无法同时进行取和修改操作。 在某些情况下,InnoDB行级可能会升级为表级。这种情况主要包括两种情况: 1. 冲突:当多个事务同时访问同一张表的不同行,并且存在冲突时,InnoDB会将行级升级为表级。这是为了避免冲突导致的死情况发生,通过使用表级可以减少冲突的概率。 2. 隐式升级:当某个事务执行一个需要全表扫描或大范围扫描操作时,InnoDB会自动将行级升级为表级。这是为了减少InnoDB资源占用和提高性能,因为在全表扫描或大范围扫描时,涉及的行数较多,行级可能需要占用较多的内存资源。 行级升级为表的情况是自动发生的,并且开发者无法手动干预或控制。但是,表级的使用可能会对并发性能产生一定的影响。因此,为了避免行级升级为表级的情况发生,我们可以优化事务的操作,尽量减少冲突的可能性,使用合适的索引来减少全表扫描或大范围扫描操作的发生。 总而言之,InnoDB行级可以在一定程度上提高并发性能,但在特定情况下会升级为表级。了解这些情况并采取优化措施可以更有效地利用资源,提高系统的并发性能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

河海哥yyds

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值