锁机制

乐观锁

概念

先进行业务操作,不到最后一步不加锁。

适合场景

适合读多写少,如果写过多会导致大量冲突,查询量增加,从而影响读性能。
不能解决脏读问题。

使用形式

  1. version
update table 
set x=x+1, version=version+1 
where id=#{id} and version=#{version};

也是用cas的方式自旋,直到更新成功
1. cas
更新时判断传入值与当前内存值是否相同:
——相同则可以修改为新值;
——失败一般重试,进直到更新成功。

悲观锁

概念

在业务处理过程中,加锁保证外部不会修改数据。

适合场景

适合写多读少,如果读过多会导致大量加锁,从而影响写性能。

使用形式

使用数据库提供的方式,保护临界资源不被并发修改
1. 比较
image-20211015135148985
表锁:开销小,加锁快;不会出现死锁;锁定力度大,发生锁冲突概率高,并发度最低
页锁:开销和加锁速度介于表锁和行锁之间;会出现死锁;锁定粒度介于表锁和行锁之间,并发度一般
行锁:开销大,加锁慢;会出现死锁;锁定粒度小,发生锁冲突的概率低,并发度高

  1. 表锁
    分为表共享读锁(Table Read Lock)和表独占写锁(Table Write Lock)。
    image-20211015135823033

MYISAM

#表独占写锁
#MyISAM在执行更新操作(UPDATE、DELETE、INSERT等)前,会自动给涉及的表加写锁,无需手动加锁
#锁调度时默认会优先获取写锁,因为重要性写>读
可通过下面方式降低优先级
- 指定INSERT、UPDATE、DELETE语句的LOW_PRIORITY属性,降低该语句的优先级
- 执行命令SET LOW_PRIORITY_UPDATES=1,使该连接发出的更新请求优先级降低。
- 指定启动参数low-priority-updates,使MyISAM引擎默认给予读请求以优先的权利。

获得表film_text的WRITE锁定
mysql> lock table film_text write;
Query OK, 0 rows affected (0.00 sec)

释放锁:
mysql> unlock tables;
Query OK, 0 rows affected (0.00 sec)
#表共享读锁
#获得表film_text的READ锁定
#MyISAM在执行查询语句(SELECT)前,会自动给涉及的所有表加读锁,所以不会出现死锁,而Innodb可能会出现
#加完锁只支持读操作,不会升级,而且只能select锁住的表,否则可能出现
#ERROR 1100 (HY000): Table 'film' was not locked with LOCK TABLES

mysql> lock table film_text read;
Query OK, 0 rows affected (0.00 sec)

#允许在表尾并发插入,但更新会等待
#lock table film_text read local;

#释放锁
mysql> unlock tables;
Query OK, 0 rows affected (0.00 sec)

Innodb
意向锁是InnoDB自动加的,不需用户干预。
意向共享锁(IS):事务打算给数据行加行共享锁,事务在给一个数据行加共享锁前必须先取得该表的IS锁。
意向排他锁(IX):事务打算给数据行加行排他锁,事务在给一个数据行加排他锁前必须先取得该表的IX锁。

  1. 行锁
    Innodb
    检查
#分析系统上的行锁的争夺情况:
mysql> 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     |  
+-------------------------------+-------+  

行锁模式
共享锁(S):允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。
SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE。
可用来防止其它事务对该条数据的更新操作;

排他锁(X):允许获得排他锁的事务更新数据,阻止其他事务取得相同数据集的共享读锁和排他写锁。另外,为了允许行锁和表锁共存,实现多粒度锁机制
SELECT * FROM table_name WHERE ... FOR UPDATE。

InnoDB还有两种内部使用的意向锁(Intention Locks),这两种意向锁都是表锁。

锁升级
对于普通SELECT语句,InnoDB不会加任何锁;

UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及数据集加排他锁(X);

行锁是针对索引加的,如果更新语句用不到索引(主键、唯一、普通索引皆可,会互相锁住),那么会加表锁;值得注意的是,如果执行计划觉得扫表效率更高,不走索引,也会导致锁表。

如果使用范围条件检索,并请求共享或排他锁时,会对已有数据的索引项加锁,键值在范围内但不存在记录的数据,叫做间隙Gap,Innodb也会加锁,称之为“间隙锁”,相等的条件索引也会产生。——总结:范围搜索 or 精确搜索但不存在的数据
其目的一是为了实现隔离,防止幻读(基于非唯一索引,主键上没有,只会阻塞insert操作),二是为了恢复和复制的需要。如果把隔离级别降为读已提交,则会禁用间隙锁。

临键锁(Next-Key Locks),特殊的间隙锁,当某个事务持有该数据行的临键锁时,会锁住一段左开右闭区间的数据。

Innodb的默认隔离级别RR,该级别下默认使用Next key lock;
Innodb对非唯一索引加锁过程,锁住条件对应的唯一索引,然后对该非唯一索引进行Next-key lock 和 Gap Key lock;
如果是对唯一索引加锁,锁住的只是相应的Record Key。

详见

假设有如下表:
MySqlInnoDBRepeatable-Read:table(id PK, age KEY, name)

id age name
1 10 Lee
3 24 Soraka
5 32 Zed
7 45 Talon
该表中 age 列潜在的临键锁有:
(-∞, 10],
(10, 24],
(24, 32],
(32, 45],
(45, +∞],

事务 A 中执行如下命令:

-- 根据非唯一索引列 UPDATE 某条记录
UPDATE table SET name = Vladimir WHERE age = 24;
-- 或根据非唯一索引列 锁住某条记录
SELECT * FROM table WHERE age = 24 FOR UPDATE;

不管执行了上述 SQL 中的哪一句,之后如果在事务 B 中执行以下命令,则该命令会被阻塞:

INSERT INTO table VALUES(100, 26, 'Ezreal');

很明显,事务 A 在对 age 为 24 的列进行 UPDATE 操作的同时,也获取了 (24, 32) 这个区间内的临键锁。
对主键加了id=3的Record lock;
对索引age加了 (10,24]的Next key lock,和下一个区间(24,32)的Gap key lock。

不仅如此,在执行以下 SQL 时,也会陷入阻塞等待:

INSERT INTO table VALUES(100, 30, 'Ezreal');

那最终我们就可以得知,在根据非唯一索引 对记录行进行 UPDATE \ FOR UPDATE \ LOCK IN SHARE MODE 操作时,InnoDB 会获取该记录行唯一索引的记录锁,以及非唯一索引的 临键锁 ,并同时获取该记录行下一个区间的间隙锁

事务 A在执行了上述的 SQL 后,最终被锁住的记录区间为 (10, 32)

主动使用表锁

SET AUTOCOMMIT=0;#必须加,否则MySQL不会给表加锁  
LOCK TABLES t1 WRITE, t2 READ, ...;  
[do something with tables t1 and t2 here];  
COMMIT;  #提交并不会释放lock的锁
UNLOCK TABLES; #会隐含提交事务

第一种情况是:事务需要更新大部分或全部数据,表又比较大,如果使用默认的行锁,不仅这个事务执行效率低,而且可能造成其他事务长时间锁等待和锁冲突,这种情况下可以考虑使用表锁来提高该事务的执行速度。
第二种情况是:事务涉及多个表,比较复杂,很可能引起死锁,造成大量事务回滚。这种情况也可以考虑一次性锁定事务涉及的表,从而避免死锁、减少数据库因事务回滚带来的开销。
这两种事务太多就应该考虑使用MyISAM表了。

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注