MySQL学习笔记

MySQL的锁机制和加锁原理

2021-09-24  本文已影响0人  想象之中丶意料之外

首先对mysql锁进行划分:


行锁

共享锁用法(S锁 / 读锁):

select ... lock in share mode;

排它锁用法(X锁 / 写锁):

select ... for update

表锁

注意:表锁,讲的是锁的颗粒度,共享锁、排他锁讲的是使用方式。

共享锁用法:

LOCK TABLE table_name [ AS alias_name ] READ

排它锁用法:

LOCK TABLE table_name [AS alias_name][ LOW_PRIORITY ] WRITE

解锁用法:

unlock tables;

页锁


乐观锁和悲观锁(思想设计上的锁)

悲观锁

在关系数据库管理系统里,悲观并发控制(又名“悲观锁”,Pessimistic Concurrency Control,缩写“PCC”)是一种并发控制的方法。它可以阻止一个事务以影响其他用户的方式来修改数据。如果一个事务执行的操作对某行数据应用了锁,那只有当这个事务把锁释放,其他事务才能够执行与该锁冲突的操作。悲观并发控制主要用于数据争用激烈的环境,以及发生并发冲突时使用锁保护数据的成本要低于回滚事务的成本的环境中。

悲观锁的具体流程:
在mysql的InnoDB引擎中使用悲观锁
-- 窗口1:
mysql> start transaction;
Query OK, 0 rows affected

-- 对7369这行,加排他锁
mysql> select * from emp where empno  = 7369 for update;
+-------+-------+-------+------+------------+-----+------+--------+
| EMPNO | ENAME | JOB   | MGR  | HIREDATE   | SAL | COMM | DEPTNO |
+-------+-------+-------+------+------------+-----+------+--------+
|  7369 | SMITH | CLERK | 7902 | 1980-12-17 | 800 | NULL |     20 |
+-------+-------+-------+------+------------+-----+------+--------+
1 row in set
mysql> 

窗口2:
mysql> start transaction;
Query OK, 0 rows affected
-- 执行对该行进行修改时,一直处于等待状态
mysql> update emp set ename =  'aa'  where empno =  7369;
-- 当窗口1,提交事务(commit)后,窗口2开始执行。
悲观锁的优点和不足

乐观锁

在关系数据库管理系统里,乐观并发控制(又名“乐观锁”,Optimistic Concurrency Control,缩写“OCC”)是一种并发控制的方法。它假设多用户并发的事务在处理时不会彼此互相影响,各事务能够在不产生锁的情况下处理各自影响的那部分数据。在提交数据更新之前,每个事务会先检查在该事务读取数据后,有没有其他事务又修改了该数据。如果其他事务有更新的话,正在提交的事务会进行回滚。

数据版本,为数据增加的一个版本标识。当读取数据时,将版本标识的值一同读出,数据每更新一次,同时对版本标识进行更新。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的版本标识进行比对,如果数据库表当前版本号与第一次取出来的版本标识值相等,则予以更新,否则认为是过期数据。

乐观锁的优点和不足:


InnoDB锁的特性

1. 通过非索引项检索数据,加表锁!
--  price属性并没有加索引,因此这时候添加的锁为表级锁!
窗口1:
mysql> select * from product where price=88 for update;
+----+------+-------+-----+
| id | name | price | num |
+----+------+-------+-----+
|  2 | 蒙牛 |    88 |   1 |
+----+------+-------+-----+

窗口2:
mysql> update product set price=price-100 where id=6;
这里会等待,直到窗口1 commit后显示下面结果!
Query OK, 1 row affected
Rows matched: 1  Changed: 1  Warnings: 0
2. 使用相同索引值但是不同行引发的冲突
-- 这里的num属性 加上了普通索引,price属性并没有索引
窗口1:
mysql> set autocommit=0;
Query OK, 0 rows affected

mysql> select * from product where num=1 and price=68 for update;
+----+------+-------+-----+
| id | name | price | num |
+----+------+-------+-----+
|  1 | 伊利 |    68 |   1 |
+----+------+-------+-----+

窗口2:
mysql> update product set price=price+100 where num=1 and price=88;
这里会发生等待,直到窗口1 commit 显示下面结果
Query OK, 1 row affected
Rows matched: 1  Changed: 1  Warnings: 0
mysql> select * from product;
+----+----------+-------+-----+
| id | name     | price | num |
+----+----------+-------+-----+
|  1 | 伊利     |    68 |   1 |
|  2 | 蒙牛     |   188 |   1 |
+----+----------+-------+-----+
3. 当使用索引检索数据时不同事务可以操作不同行数据
锁一行数据,DML操作其他行并没有影响
窗口1:
mysql> select * from user where id=1 for update;
+----+-------+
| id | price |
+----+-------+
|  1 |   400 |
+----+-------+

窗口2:
mysql> update user set price=price+100 where id=2;
无需等待窗口1 commit
Database changed
Rows matched: 1  Changed: 1  Warnings: 0

Record Lock、Gap Lock、Next-key Lock锁

Record Lock

Gap Lock 间隙锁

1、什么叫间隙锁?
mysql> select * from product_copy;
+----+--------+-------+-----+
| id | name   | price | num |
+----+--------+-------+-----+
|  1 | 伊利   |    68 |   1 |
|  2 | 蒙牛   |    88 |   1 |
|  6 | tom    |  2788 |   3 |
| 10 | 优衣库 |   488 |   4 |
+----+--------+-------+-----+
-- 其中id为主键 num为普通索引
窗口A:
mysql> select * from product_copy where num=3 for update;
+----+------+-------+-----+
| id | name | price | num |
+----+------+-------+-----+
|  6 | tom  |  2788 |   3 |
+----+------+-------+-----+
1 row in set

窗口B:
mysql> insert into product_copy values(5,'kris',1888,2);
-- 这里会等待  直到窗口A commit才会显示下面结果
Query OK, 1 row affected

-- 但是下面是不需要等待的
mysql> update product_copy set price=price+100 where num=1;
Query OK, 2 rows affected
Rows matched: 2  Changed: 2  Warnings: 0
mysql> insert into product_copy values(5,'kris',1888,5);
Query OK, 1 row affected
2、为什么说 Gap锁 是RR(repeatable-read/可重复读)隔离级别下防止幻读的主要原因?
  1. 解决幻读的方式很简单,就是需要当事务进行当前读的时候,保证其他事务不可以在满足当前读条件的范围内进行数据操作
  2. 根据索引的有序性,我们可以从上面的例子推断出满足where条件的数据,只能插入在num=(1,3]U[3,4)两个区间里面,只要我们将这两个区间锁住,那么就不会发生幻读。
3、主键索引/唯一索引 + 当前读 会加上Gap锁吗?
窗口A:
mysql> select * from product_copy where id=6 for update;
+----+------+-------+-----+
| id | name | price | num |
+----+------+-------+-----+
|  6 | tom  |  2788 |   3 |
+----+------+-------+-----+

窗口B:并不会发生等待
mysql> insert into product_copy values(5,'kris',1888,3);
Query OK, 1 row affected
4、通过范围查询是否会加上Gap锁?
窗口A:
mysql> select * from product_copy where num>3 for update;
+----+--------+-------+-----+
| id | name   | price | num |
+----+--------+-------+-----+
| 10 | 优衣库 |   488 |   4 |
+----+--------+-------+-----+

窗口B:会等待
mysql> insert into product_copy values(11,'kris',1888,5);
Query OK, 1 row affected
不会等待
mysql> insert into product_copy values(3,'kris',1888,2);
Query OK, 1 row affected
5、检索条件并不存在的当前读会加上Gap吗?
  1. 等值查询
窗口A:
mysql> select * from product_copy where num=5 for update;
Empty set

窗口B:6 和 4都会等待
mysql> insert into product_copy values(11,'kris',1888,6);
Query OK, 1 row affected

mysql> insert into product_copy values(11,'kris',1888,4);
Query OK, 1 row affected
  1. 范围查询
窗口A:
mysql> select * from product_copy where num>6 for update;
Empty set
窗口B:8 和 4 都会锁住
mysql> insert into product_copy values(11,'kris',1888,4);
Query OK, 1 row affected

mysql> insert into product_copy values(11,'kris',1888,8);
Query OK, 1 row affected

Next-Key Lock


死锁的原理及分析

MVCC

2PL:Two-Phase Locking

image.png

为什么会发生死锁?

通过两个SQL死锁的例子来说明

1. 两个session的两条语句
image.png
2.两个session的一条语句
image.png

避免死锁,这里只介绍常见的三种

  1. 如果不同程序会并发存取多个表,尽量约定以相同的顺序访问表,可以大大降低死锁机会。
  2. 在同一个事务中,尽可能做到一次锁定所需要的所有资源,减少死锁产生概率;
  3. 对于非常容易产生死锁的业务部分,可以尝试使用升级锁定颗粒度,通过表级锁定来减少死锁产生的概率;

转:MySQL的锁机制和加锁原理

上一篇 下一篇

猜你喜欢

热点阅读