锁的粒度
一种提高共享资源并发性的方式就是让锁定对象更有选择性。尽量只锁定包含需要修改的部分数据,而不是所有的资源。更理想的方式是,只对需要修改的数据片段进行精确的锁定。任何时候,让锁定的数据量最小化,理论上就能保证在给定资源上同时进行更改操作,只要被修改的数据彼此不冲突即可。
问题是加锁也需要消耗资源。锁的各种操作,包括获取锁、检查锁是否空闲、释放锁等,都会增加系统的开销。如果系统花费大量的时间来管理锁,而不是存取数据,那么系统的性能可能会受影响。
锁定策略是锁开销和数据安全性之间的平衡,这种平衡会影响性能。大多数商业数据库系统没有提供太多的选择,一般都是在表中施加行级锁(row level lock),为了在锁比较多的情况下尽可能地提供更好的性能,锁的实现方式非常复杂。锁是数据库实现一致性保证的方法。数据库操作专家必须深入源代码,才能确定合适的配置,以优化速度与数据安全之间的平衡。
而MySQL则提供了多种选择。每种MySQL存储引擎都可以实现自己的锁策略和锁粒度。在设计存储引擎时,锁的管理是一个非常重要的决定。将锁粒度固定在某个级别,可以提高某些应用场景下的性能,但同时会使其不适合另外一些应用场景。好在MySQL提供了多种存储引擎,而不是单一的通用解决方案。下面让我们来看两种最重要的锁策略。
表锁
表锁(table lock)是MySQL中最基本也是开销最小的锁策略。表锁非常类似于前文描述的电子表格的锁机制:它会锁定整张表。当客户端想对表进行写操作(插入、删除、更新等)时,需要先获得一个写锁,这会阻塞其他客户端对该表的所有读写操作。只有没有人执行写操作时,其他读取的客户端才能获得读锁,读锁之间不会相互阻塞。
表锁有一些变体,可以在特定情况下提高性能。例如,READ LOCAL表锁支持某些类型的并发写操作。写锁队列和读锁队列是分开的,但写锁队列的优先级绝对高于读队列。[3]
行级锁
使用行级锁(row lock)可以最大程度地支持并发处理(也带来了最大的锁开销)。回到电子表格的类比,行级锁等同于锁定电子表格中的某一行。这种策略允许多人同时编辑不同的行,而不会阻塞彼此。这使得服务器可以执行更多的并发写操作,带来的代价则是需要承担更多开销,以跟踪谁拥有这些行级锁、已经锁定了多长时间、行级锁的类型,以及何时该清理不再需要的行级锁。
行级锁是在存储引擎而不是服务器中实现的。服务器通常[4]不清楚存储引擎中锁的实现方式。在本章的后续内容甚至整本书中都可以看到,每种存储引擎都以自己的方式来实现锁。