zl程序教程

您现在的位置是:首页 >  数据库

当前栏目

高性能 MySQL(二):并发控制(锁)

2023-09-11 14:22:52 时间

❤️个人主页:水滴V2
🚀支持水滴:点赞👍、收藏⭐、留言💬

大家好,我是水滴~~

无论何时,当多个进程或线程并发访问同一资源时,就会产生并发控制的问题。在数据库系统中,数据也是一种多用户共享的资源,为了保证数据的一致性,需要对数据操作进行并发控制,而数据库系统通常使用(Lock)来控制并发问题。

1 读写锁

当我们从表中读取一条记录时,即使同一时刻有多个用户并发读取,也不会有什么问题,因为读取并不会修改数据,所以不会出错。但当一个用户读取,而别一个用户试图删掉数据,这就会造成读取到不一致的数据。

所以无论是读(查)还是写(增、删、改)都应该有相应的锁机制。这两种类型的锁通常被称为共享锁(Shared Lock)和排他锁(Exclusive Lock),也叫做读锁(Read Lock)和写锁(Write Lock)。

1.1 共享锁/读锁

读锁是共享的,或者说是相互不阻塞的。多个用户在同一时刻可以同时读取同一资源,而互不干扰。

MySQL 对读取的记录加共享锁,在 SQL 语句后面加lock in share mode,例如:

select ... lock in share mode;

1.2 排他锁/写锁

写锁是排他的,也就是说一个写锁会阻塞其他的写锁和读锁。并且读锁在未释放前,写锁也会被阻塞,直到读锁被释放。

MySQL 对读取的记录加排他锁,在 SQL 语句后面加for update,例如:

select ... for update;

2 锁的使用

我们使用一些 SQL 来操作共享锁和排他锁。下图是一个user表,表中有10条数据,就使用这张表来演示下锁的使用。

在这里插入图片描述

2.1 读—>读

A窗口先开启事务,再使用读锁

B窗口再使用读锁,我们发现B窗口的查询并没有被阻塞,也就是说读锁与读锁之前互不影响。

在这里插入图片描述

2.2 读—>写

A窗口先开启事务,再使用读锁

B窗口再使用写锁,我们发现B窗口被阻塞住了;

A窗口提交事务后(释放读锁),B窗口立马输出结果。

在这里插入图片描述

2.3 写—>读

A窗口先开启事务,再使用写锁

B窗口再使用读锁,然而B窗口同样被阻塞住了;

A窗口提交事务后(释放写锁),B窗口立马输出结果。

在这里插入图片描述

2.4 写—>写

A窗口先开启事务,再使用写锁

B窗口再使用写锁,然而B窗口也是被阻塞住了;

A窗口提交事务后(释放写锁),B窗口立马输出结果。

在这里插入图片描述

通过上面的使用,我们也能看出结论。读锁与读锁之前互不影响;而写锁与其他任意组合(使用顺序),都会有影响。

3 锁粒度

为了尽可能提高数据库的并发性,每次锁定的数据范围越小,理论上并发性就越高。

问题时加锁本身也是需要消耗资源的。锁的各种操作,包括获得锁、检查锁是否已释放、释放锁等,都会增加系统的开锁。如果系统花费大量的时间来管理锁,而不是存取数据,那么系统的性能也会受到影响。

所以 MySQL 提供了多种存储引擎,每种存储引擎都有自己的锁策略和锁粒度,我们可以根据自己的业务需求来选择。

下面介绍两种最重要的锁策略。

3.1 表锁

表锁(Table Lock)是 MySQL 中最基本的锁策略,并且是开锁最小的策略(粒度比较大)。

表锁是 MySQL 服务层实现的,它不依赖于存储引擎,不管你用的是哪种存储引擎,表锁的策略都是一样的。

由于表锁一次会将整个表锁住,所以可以很好的避免死锁问题。当然,锁的粒度大所带来最大的负面影响是并发率比较低。

3.2 行锁

行锁(Row Lock)可以最大程度地支持并发处理,但同时也带来了最大的锁开锁。

在 MySQL 中,行级锁只在存储引擎层实现,比如 InnoDB 和 XtraDB 等。服务器层完全不了解存储引擎中的行锁实现。

在这里插入图片描述