mysql的锁机制学习

1、不同引擎支持不同的锁机制

innodb支持表级也支持行级锁
表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。

行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度最高。

2、查询表级锁争用情况

(root:oratest_0112:Tue Oct 20 13:00:39 2020)[sakila]> show status like 'table%';
+----------------------------+-------+
| Variable_name              | Value |
+----------------------------+-------+
| Table_locks_immediate      | 141   |
| Table_locks_waited         | 0     |
| Table_open_cache_hits      | 1     |
| Table_open_cache_misses    | 1     |
| Table_open_cache_overflows | 0     |
+----------------------------+-------+

如果Table_locks_waited 的值较高,说明存在着较为严重的表级锁争用。

3、并发事务处理带来的问题 

1)更新丢失
多个事务选择同一行更新,由于每个事务都不知道其他事务的存在,就会发生丢失更新问题。比如两个人同时编辑同一份文档。

(2)脏读
一个事务读取到了另外一个事务中未提交的数据

(3)不可重复读
一个事务在读取某些数据后的某个时间,再次读取以前读过的数据时,却发现读出的数据已经发生了改变或某些记录被删除。

(4)幻读
一个事务按相同的查询条件查询数据时,发现其他事务插入了满足其查询条件的新数据。

更新丢失是应用程序的责任,需要应用程序对要更新的数据加必要的锁解决。
脏读、不可重复读、幻读都是数据库读一致性的问题,必须由数据库提供机制解决。
数据库实现事务隔离的方式基本分两种:
1、在读取数据前,对其加锁,阻止其他事务对数据修改;
2、不加锁,通过一定机制生成一个数据请求时间点的一致性数据快照,用这个快照提供一定级别的一致性读取。MVCC。

4、获取innodb行锁争用情况

(root:oratest_0112:Tue Oct 20 13:44:11 2020)[sakila]> show status like 'innodb_row_lock%';
+-------------------------------+-------+
| Variable_name                 | Value |
+-------------------------------+-------+
| Innodb_row_lock_current_waits | 0     |
| Innodb_row_lock_time          | 0     |
| Innodb_row_lock_time_avg      | 0     |
| Innodb_row_lock_time_max      | 0     |
| Innodb_row_lock_waits         | 0     |
+-------------------------------+-------+
5 rows in set (0.00 sec)

5、innodb的行锁模式及加锁方法

innodb实现的行锁类型
(1)共享锁(S):允许一个事务去读取一行,阻止其它事务获得相同数据集的排他锁。
(2)排他锁(X):允许获得排他锁的事务更新数据,阻止其他事务获得相同数据集的共享读锁和排他写锁。
(3)意向共享锁(IS)
(4)意向排他锁(IX)

显式加锁:
共享锁(S):select * from table_name where ... lock in share mode
排他锁(X):select * from table_name where ... for update

6、innodb行锁的实现方式

innodb行锁是通过给索引上的索引项加锁实现的。有3种情形
(1)record lock:对索引项加锁
(2)gap lock:对索引项之间的间隙,第一条记录前的间隙或是最后一条记录后的间隙加锁。
(3)next-key lock:前两种的组合,对记录及其前面的间隙加锁。

注意:innodb行锁的实现特点,如果不通过索引条件检索数据,则innodb将对表中的所有记录加锁,实际效果和表锁一样。

案例:不通过索引条件查询时,innodb会锁定表中所有记录

a.测试表结构如下:
(root:oratest_0112:Wed Oct 21 09:15:47 2020)[kafka_test]> show create table test1\G
*************************** 1. row ***************************
       Table: test1
Create Table: CREATE TABLE `test1` (
  `id` int(11) DEFAULT NULL,
  `utime` timestamp NULL DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1
1 row in set (0.00 sec)


b.插入测试数据
insert into test1 select 1,now() from dual;
insert into test1 select 2,now() from dual;
insert into test1 select 3,now() from dual;
insert into test1 select 4,now() from dual;

(root:oratest_0112:Wed Oct 21 09:17:06 2020)[kafka_test]> select * from test1;
+------+---------------------+
| id   | utime               |
+------+---------------------+
|    1 | 2020-10-21 09:16:56 |
|    2 | 2020-10-21 09:17:01 |
|    3 | 2020-10-21 09:17:03 |
|    4 | 2020-10-21 09:17:06 |
+------+---------------------+
4 rows in set (0.00 sec)


c.会话1查询数据
(root:oratest_0112:Wed Oct 21 09:19:06 2020)[kafka_test]> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)

(root:oratest_0112:Wed Oct 21 09:19:15 2020)[kafka_test]> select * from test1 where id=1 for update;
+------+---------------------+
| id   | utime               |
+------+---------------------+
|    1 | 2020-10-21 09:16:56 |
+------+---------------------+
1 row in set (0.00 sec)


d.会话2查询数据
(root:oratest_0112:Wed Oct 21 09:20:37 2020)[kafka_test]> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)

(root:oratest_0112:Wed Oct 21 09:20:39 2020)[kafka_test]>
(root:oratest_0112:Wed Oct 21 09:20:40 2020)[kafka_test]> select * from test1 where id=2 for update;


等待的状态



e.在会话1提交之后,会话2的查询才会返回结果。

结论:会话2请求的是其他行的排他锁,但是出现了锁等待,原因就是在没有索引的前提下,innodb会对所有记录都加锁。
案例:测试通过索引条件查询,加锁的情形

a.给上述测试表test1增加主键属性
alter table test1 add primary key(id);

(root:oratest_0112:Wed Oct 21 09:24:10 2020)[kafka_test]> show create table test1\G
*************************** 1. row ***************************
       Table: test1
Create Table: CREATE TABLE `test1` (
  `id` int(11) NOT NULL,
  `utime` timestamp NULL DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
1 row in set (0.00 sec)


b.会话1查询数据
(root:oratest_0112:Wed Oct 21 09:21:27 2020)[kafka_test]> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)

(root:oratest_0112:Wed Oct 21 09:24:43 2020)[kafka_test]> select * from test1 where id=1 for update;
+----+---------------------+
| id | utime               |
+----+---------------------+
|  1 | 2020-10-21 09:16:56 |
+----+---------------------+
1 row in set (0.00 sec)


c.会话2查询数据
(root:oratest_0112:Wed Oct 21 09:24:16 2020)[kafka_test]> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)

(root:oratest_0112:Wed Oct 21 09:25:12 2020)[kafka_test]> select * from test1 where id=2 for update;
+----+---------------------+
| id | utime               |
+----+---------------------+
|  2 | 2020-10-21 09:17:01 |
+----+---------------------+
1 row in set (0.00 sec)


d.会话3查询数据
(root:oratest_0112:Wed Oct 21 09:25:17 2020)[kafka_test]> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)

(root:oratest_0112:Wed Oct 21 09:31:21 2020)[kafka_test]> select * from test1 where id=1 for update;
^C^C -- query aborted
ERROR 1317 (70100): Query execution was interrupted
(root:oratest_0112:Wed Oct 21 09:31:29 2020)[kafka_test]> select * from test1 where id=1 for update;


可以看到会话3请求的排他锁和会话1请求的排它锁访问的数据集是一样的,这个时候会话3需要等待。

结论:innodb的行锁是针对索引添加的,不是针对记录行的,所以访问数据使用的是相同的索引键,是会出现索引冲突的。

7.innodb next-key锁机制,以及innnodb使用next-key锁的原因

next-key锁:使用范围条件而不是相等条件检索数据,并请求共享或排他锁时,innodb会给符合条件的已有数据记录的索引项加锁,对于键值在条件范围内但并不存在的记录,叫做间隙,
innodb会对这个间隙加锁,这种锁机制就叫next-key锁。

案例:emp表有101条记录,empid的值为1,2,... 100,101

select * from emp where empid>100 for update;

这个sql是一个范围查询,innodb不仅会对符合条件的empid值为101的记录加锁,也会对empid大于101(这些记录并不存在)的间隙加锁。

加next-key锁可以防止幻读,以满足相关隔离级别的要求,比如上面的sql如果不加间隙锁,如果其他事务插入了empid大于100的任何记录,那么本事务如果再次执行上述的查询sql,就会有幻读。

innodb除了通过范围条件加锁时使用间隙锁外,如果使用相等条件请求一个不存在的记录加锁,innodb也会使用间隙锁。

案例:会话1

(root:oratest_0112:Fri Oct 23 09:30:08 2020)[(none)]> set session transaction isolation  level repeatable read;
Query OK, 0 rows affected (0.00 sec)

(root:oratest_0112:Fri Oct 23 09:31:13 2020)[(none)]> select @@tx_isolation;
1 row in set (0.00 sec)

(root:oratest_0112:Fri Oct 23 09:31:21 2020)[(none)]> nopager
PAGER set to stdout
(root:oratest_0112:Fri Oct 23 09:31:23 2020)[(none)]> select @@tx_isolation;
+-----------------+
| @@tx_isolation  |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set (0.00 sec)
(root:oratest_0112:Fri Oct 23 09:25:33 2020)[kafka_test]> select * from test1;
+----+---------------------+
| id | utime               |
+----+---------------------+
|  1 | 2020-10-21 09:16:56 |
|  2 | 2020-10-21 09:17:01 |
|  3 | 2020-10-21 09:17:03 |
|  4 | 2020-10-21 09:17:06 |
+----+---------------------+
4 rows in set (0.00 sec)

(root:oratest_0112:Fri Oct 23 09:25:36 2020)[kafka_test]> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)

(root:oratest_0112:Fri Oct 23 09:25:53 2020)[kafka_test]> select * from test1 where id=6 for update;   (查询一个不存在的记录)
Empty set (0.00 sec)

会话2

(root:oratest_0112:Fri Oct 23 09:33:09 2020)[kafka_test]> set session transaction isolation  level repeatable read;
Query OK, 0 rows affected (0.00 sec)

(root:oratest_0112:Fri Oct 23 09:33:10 2020)[kafka_test]> select @@tx_isolation;
+-----------------+
| @@tx_isolation  |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set (0.00 sec)

(root:oratest_0112:Fri Oct 23 09:33:14 2020)[kafka_test]> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)

(root:oratest_0112:Fri Oct 23 09:33:19 2020)[kafka_test]> insert into test1 values(8,now());


此时会话2阻塞等待的状态。

会话1

执行rollback

会话2

(root:oratest_0112:Fri Oct 23 09:33:19 2020)[kafka_test]> insert into test1 values(8,now());
Query OK, 1 row affected (29.80 sec)

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

#慧#

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

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

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

打赏作者

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

抵扣说明:

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

余额充值