大家好,我是水滴~~
无论何时,当多个进程或线程并发访问同一资源时,就会产生并发控制的问题。在数据库系统中,数据也是一种多用户共享的资源,为了保证数据的一致性,需要对数据操作进行并发控制,而数据库系统通常使用锁(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 等。服务器层完全不了解存储引擎中的行锁实现。