MySQL 并发控制
🔒

MySQL 并发控制

Created
Mar 20, 2024 08:07 AM
Tags
MySQL在两个级别的并发控制:服务器级别存储引擎级别

读写锁

并发控制这一经典问题的解决方案相当简单。
处理并发读/写访问的系统通常实现一个由两种锁类型组成的锁系统。这两种锁通常被称为共享锁(shared lock)和排他锁(exclusive lock),也叫读锁(read lock)和写锁(write lock)。
先不考虑具体的锁定机制,锁的概念可以如下描述:资源上的读锁是共享的,或者说是相互不阻塞的。多个客户端可以同时读取同一个资源而互不干扰。写锁则是排他的,也就是说,一个写锁既会阻塞读锁也会阻塞其他的写锁,这是出于安全策略的考虑,只有这样才能确保在特定的时间点只有一个客户端能执行写入,并防止其他客户端读取正在写入的资源。

锁的粒度

问题是加锁也需要消耗资源。锁的获取、检查是否空闲、释放都会增加系统的开销。锁的范围也影响数据库的并发性能。
锁定策略是锁开销和数据安全性之间的平衡,这种平衡会影响性能。而MySQL则提供了多种选择。每种MySQL存储引擎都可以实现自己的锁策略和锁粒度。在设计存储引擎时,锁的管理是一个非常重要的决定。将锁粒度固定在某个级别,可以提高某些应用场景下的性能,但同时会使其不适合另外一些应用场景。好在MySQL提供了多种存储引擎,而不是单一的通用解决方案。下面让我们来看两种最重要的锁策略。

表锁

表锁(table lock)是MySQL中最基本也是开销最小的锁策略。表锁非常类似于前文描述的电子表格的锁机制:它会锁定整张表。当客户端想对表进行写操作(插入、删除、更新等)时,需要先获得一个写锁,这会阻塞其他客户端对该表的所有读写操作。只有没有人执行写操作时,其他读取的客户端才能获得读锁,读锁之间不会相互阻塞。
表锁有一些变体,可以在特定情况下提高性能。例如,READ LOCAL表锁支持某些类型的并发写操作。写锁队列和读锁队列是分开的,但写锁队列的优先级绝对高于读队列。

行级锁

使用行级锁(row lock)可以最大程度地支持并发处理(也带来了最大的锁开销)。这种策略允许多人同时编辑不同的行,而不会阻塞彼此。这使得服务器可以执行更多的并发写操作,带来的代价则是需要承担更多开销,以跟踪谁拥有这些行级锁、已经锁定了多长时间、行级锁的类型,以及何时该清理不再需要的行级锁。行级锁是在存储引擎而不是服务器中实现的。服务器通常不清楚存储引擎中锁的实现方式。
 
//官网 排他锁、共享锁、意图锁和记录锁
 

死锁

死锁是指两个或多个事务相互持有和请求相同资源上的锁,产生了循环依赖。当多个事务试图以不同的顺序锁定资源时会导致死锁。当多个事务锁定相同的资源时,也可能会发生死锁。例如,设想运行下面两个针对主键为(stock_id,date)的StockPrice表的事务:
事务1:
START TRANSACTION; UPDATE StockPrice SET close = 45.50 WHERE stock_id = 4 and date = '2020-05-01'; UPDATE StockPrice SET close = 19.80 WHERE stock_id = 3 and date = '2020-05-02'; COMMIT;
事务2:
START TRANSACTION; UPDATE StockPrice SET high = 20.12 WHERE stock_id = 3 and date = '2020-05-02'; UPDATE StockPrice SET high = 47.20 WHERE stock_id = 4 and date = '2020-05-01'; COMMIT;
每个事务都开始执行第一个查询,在处理过程中会更新一行数据,同时在主键索引和其他唯一索引中将该行锁定。然后,每个事务将在第二个查询中尝试更新第二行数据,却发现该行已经被锁定。这两个事务将永远等待对方完成,除非有其他因素介入解除死锁。
一旦发生死锁,如果不回滚其中一个事务(部分或全部),就无法打破死锁。对于事务型的系统,这是无法避免的,所以应用程序在设计时必须考虑如何处理死锁。大多数情况下只需要重新从头开始执行被回滚的事务即可,除非又遇到另一个死锁。InnoDB目前处理死锁的方式是将持有最少行级排他锁的事务回滚(这是一种最容易回滚的近似算法)。

隐式锁定和显式锁定

InnoDB使用两阶段锁定协议(two-phase locking protocol)。在事务执行期间,随时都可以获取锁,但锁只有在提交或回滚后才会释放,并且所有的锁会同时释放。前面描述的锁定机制都是隐式的。InnoDB会根据隔离级别自动处理锁。
另外,InnoDB还支持通过特定的语句进行显式锁定,这些语句不属于SQL规范:
SELECT ... for UPDATE SELECT ... for SHARE