# 并发控制

只要有多个查询在同一时刻修改数据,就会产生并发控制问题,本章目的是讨论 MySQL 在两个层面的并发控制:服务器层与存储引擎层。由于该话题是一个庞大的话题,本章只简要的讨论 MySQL 如何控制并发读写

比如文件写入,两个线程对同一个文件执行写入操作,数据就有可能产生混乱,那么就会利用锁(lock)来防止数据损坏,但是不支持并发处理了,在任意时刻只有一个线程可以操作该文件

# 读写锁

多个线程从文件读取数据就没有问题,因为读取不会修改数据,但是一个读取,一个修改,这也会导致读取到不一致的数据,所以为了安全起见,即使读取也需要特别注意

如果把上述案例中的文件当成数据库中的一张表,把数据当成表中的一行记录,很容易看出,会存在同样的问题。

解决这类经典问题的方法就是并发控制,通过共享锁(shared lock)和排他锁(exclusive locke),也叫读锁(read lock)和写锁(write)

先不讨论锁的具体实现细节,概念如下:

  • 读锁:是共享的(互不阻塞的),多个客户在同一时刻可以同时读取同一个资源,互不干扰
  • 写锁:是排他的,会阻塞其他的写锁和读锁。只有这样才能确保在给定的时间里,只有一个用户能执行写入,并防止其他用户读取正在写入的同一资源

在实际的数据库系统中,每时每刻都在发生锁定

# 锁粒度

一种提高共享资源并发性的方式是让锁定对象更有选择性,尽量只锁定需要修改的部分数据。任何时候,在给定的资源上,锁定的数据量越少,则系统的并发程度越高,只要不相互发生冲突

那么问题来了,管理锁也需要消耗资源,如获锁、检查锁是否已经解除、释放锁等,都会增加系统的开销。更多的时间来管理锁,而不是用在存取数据上,这会导致系统性能下降,这需要一个权衡

在锁的开销和数据的安全性之间寻求平衡,这就是锁策略。大多数商业数据库系统没有提供更多的选择(策略),一般在表上施加 行级锁(row-level lock),并以各种复杂的方式来实现,使锁比较多的情况下尽可能提供更好的性能

MySQL 则提供了多种选择。每种存储引擎都可以实现自己的锁策略和锁的粒度。将锁粒度固定在某个级别,可以在某些特定的应用场景提供更好的性能,但同时会失去对另外一些应用场景的良好支持。而 MySQL 支持多个存储引擎的架构,则让用户能有更多的选择。下面将介绍两种最重要的锁策略

# 表锁(table lock)

表锁是 MySQL 中最基本的锁策略,并且是开销最小的策略。顾名思义,当一个用户在对表进行写操作(插入、删除、更新等),就会锁定整张表,这会阻塞其他用户对该表的所有读写操作。读锁之间是不相互阻塞

在特定场景中,表锁也可能有良好的性能。比如:READ LOCAL 表锁支持某些类型的并发写操作。另外,写锁比读锁有更高的优先级,因此一个写锁清秋可能会被插入到读锁队列的前面(反之,读锁则不能插入到写锁前面)

尽管存储引擎可以管理自己的锁,但是 MySQL 会使用各种有效的表锁来实现不同的目的。比如,服务器会为诸如 ALTER TABLE 之类的语句使用表锁,而忽略存储引擎的锁机制

# 行级锁(row lock)

行级锁可以最大程度的支持并发处理,同时也带来了最大的锁开销。

在 InnoDB 和 XtraDB,以及其他一些存储引擎中实现了行级锁,而且只在存储引擎层实现。服务器层完成不了解存储引擎中的锁实现

在本章的后续内容以及全书中,所有的存储引擎都以自己的方式显现了锁机制