作者:Kuba Łopuszański 编译:徐轶韬
在InnoDB Data Locking –第1部分“简介”中,我们通过同时编辑电子表格的比喻描述了锁能够解决的难题。虽然通过比喻可以获得直观的感觉,但是我们需要将解决方案与现实进行匹配。在这篇文章中,将讨论我们之前看到的语句如何映射到InnoDB的表,行,锁,锁队列等实际情况,例如“ Alice请求对文件A的读取访问,但必须等待Basil首先释放其写权限”。
比喻
之前的帖子中使用的比喻应该是这样的:
共享驱动器上的文件→数据库
文件内的电子表格→表
电子表格中的行→行
电子表格中的一列→列
人→客户端
人的行动计划→事务
访问权限请求→锁
访问权限→锁模式
请求访问权限→获取锁
为了使我的故事与现实生活办公室中发生的事情相似,它涉及“Alice计划阅读文件A”(译为“客户端A执行一个事务,执行该事务需要从取得数据库A的共享锁开始”),这在InnoDB中实际上不会发生,因为事务不能锁定整个数据库,这样太糟糕了。为了实现更高的并行度,在InnoDB中,事务通常需要在单个行的级别上进行更细粒度的访问。为了方便进行说明,在这里我们暂且假设锁定的是数据库。
什么是数据库“锁”?
当我理解数据库术语时,我发现非常困惑的一件事,“锁”一词在数据库中的含义与在编程中的含义不同。在编程中,如果您具有“锁”,则它是存储在内存中某个地址下的单个对象,然后有多个线程尝试“锁定”它并成功或等待成功。因此,每个资源有一个锁,“锁”的动作是线程执行的操作,您可以使用调试器来捕获它发生的瞬间,但是没有内存对象(除了调用堆栈)显式记录给定线程尝试或成功获得锁的事实。在InnoDB中,以上概念称为“闩(shuan)锁”,用于将“锁”一词重新用于其他用途。在InnoDB的锁系统中,“锁”实际上更像是“通过特定事务请求对特定资源的特定种类的访问权的请求”。因此,对于特定资源,如果有许多事务请求访问它,则可能存在数百个“锁”,如果单个事务需要使用不同的锁模式来访问它,那么对于单个事务也可能有多个锁。“锁”可以等待,也可以被授予,并记录对给定资源的给定事务的访问权限。您可以将其视为纸质表格,必须提交文件才能获得许可,该文件在某些官员的抽屉中等待批准印章并最终被授予,并充当证明您的权利的证书。
例如:一个锁系统可以同时包含以下与单个资源(表report的row#2)有关的锁
<transaction#3305, row#2 of table `report`, shared, granted ><transaction#3305, row#2 of table `report`, exclusive, granted ><transaction#3306, row#2 of table `report`, exclusive, waiting >
显示建模的好处之一是知道谁请求内存中的对象,您可以通过查看那些对象来检查情况。可以通过performance_schema.data_locks表查看InnoDB引擎中活动事务创建的所有锁:
> SELECT ENGINE_TRANSACTION_ID as trx_id, LOCK_DATA as row, OBJECT_NAME as `table`, LOCK_MODE, LOCK_STATUS FROM performance_schema.data_locks WHERE LOCK_TYPE='RECORD'; +--------+-----+--------+-----------+-------------+ | trx_id | row | table | LOCK_MODE | LOCK_STATUS | +--------+-----+--------+-----------+-------------+ | 3305 | 2 | report | S | GRANTED | | 3305 | 2 | report | X | GRANTED | | 3306 | 2 | report | X | WAITING | +--------+-----+--------+-----------+-------------+
我会在后面的“记录锁”部分解释LOCK_MODE列各种值的意思。现在S和X对应于共享和排他就足够了。
(如果您开始怀疑在另一个表中使用锁来保护对表的访问,那么让我来安慰您:这不是一个真正的InnoDB表。有一些魔术使它看起来像一个表,但它实际上是扫描服务器内存中的实际底层数据结构,并将它们呈现为整齐的行)
实际上,这些只是显式锁-出于性能原因,InnoDB避免显式表示访问权限,该访问权限可以从行本身的状态隐式推导出。您会看到,每当事务修改一行时,它都会在行的标题中添加自己的ID,以标识它是最后一个修改它的对象–如果该事务仍未提交,则意味着它仍对该记录具有独占访问权限(它必须修改它,并且只有在提交时才释放“两阶段锁”中的锁),而不会浪费用于显式存储此信息的空间。这种隐式锁不会显示在performance_schema.data_locks中 (这将需要对撤消日志进行扫描以识别所有隐式锁)。创建隐式锁的最常见原因是一项INSERT
操作:成功插入的行在其他事务提交之前对其他事务不可见,并且常见的情况是单个事务插入许多行,因此不创建显式锁对于新插入的行的成本会更低,只是隐式地假定插入事务具有对所有行的独占访问权,因为其ID写入其标题中。如第3部分“死锁”中所述,正确建模和监视谁在等待谁很重要,因此,每当锁系统识别出隐式锁可能是另一个事务必须等待的原因时,它将隐式锁转换为显式锁,以便可以正确地分析,监视,报告等。这被称为隐式到显式转换,并且在语义上没有任何改变–它只是更改了锁的表示。
表锁
与服务器表锁的交互
如前所述,在InnoDB中,大多数锁发生在行的粒度上。这增加了并行的机会,因为多个事务可以同时处理不相交的行,并且服务器仍然可以假装一个事务以可序列化的顺序发生在另一个事务之后。还有表级锁,可让您锁定整个表。由于InnoDB与服务器集成的方式,这些情况很少见。InnoDB位于服务器下方,服务器也具有自己的锁机制,大多数时间InnoDB甚至都不知道事务已锁表,因为它发生在服务器。坦白说,如果现在我们要谈谈表锁,我会有些不知所措:从某种意义上说,它们比记录锁要简单得多,另一方面,InnoDB和服务器协调对表的访问的方式会对事件的理解更加复杂。特别是performance_schema.data_locks不报告服务器本身维护的锁。因此,在默认配置下,您会看到一些令人困惑的事情,例:
# CONFUSING EXAMPLE, DO NOT COPY&PASTE TO YOUR APPLICATION!con1> BEGIN;Query OK, 0 rows affected (0.00 sec)con1> INSERT INTO t1 VALUES (123);Query OK, 1 row affected (0.01 sec)# DO NOT DO THIS IN YOUR APPLICATION!:con1> LOCK TABLES t READ;Query OK, 0 rows affected (0.00 sec)
您可能希望事务已锁定表t,但是看不到任何锁:<