1.2. 并发控制

来源:互联网 发布:js最简单的无缝轮播图 编辑:程序博客网 时间:2024/04/28 19:02

1.2. 并发控制

任何时候只要有多个查询语句想要改变同一条数据时并发控制的问题就出现了。根据这一章的描述,MySQL在需要在两个层面来解决这个问题:服务器层面和存储引擎层面。并发控制是一个很大的课题,同时也有一大批关于并发控制的专著,但是本书的着眼点并不在于并发控制的理论或者MySQL的内部实现。因此我们只打算给读者一个关于如何处理并发读操作和并发写操作的概述,因此读者可以在本章的其余部分看到这部分内容。

我们用Unix系统中的电子邮件箱来作为例子说明这个问题。经典的mbox文件格式很简单。mbox邮件箱中的所有邮件被一个一个连接在一起。这使得邮件消息很容易阅读和解析。它也使得邮件接收变得很简单,只需要将新邮件附加到mbox文件尾就可以了。

但是当两个不同的进程同时接收邮件到同一个邮箱时会发生什么事情呢?很显然那个邮件箱会崩溃,因为它里面会出现两个交织在一起的邮件。一个好的邮件系统会使用锁来阻止这种崩溃。当一个客户在邮件箱被锁定的情况下往邮件箱里写入第二封邮件的时候,它必须一直等待直到它拿到那个锁。

这个方案在现实世界中运行地很好,但是它却不能提供并发性。这种方式在一个大容量邮箱的情况下会很有问题,因为在指定时间里只有一个进程可以改变邮箱的内容。

1.2.1. 读写锁

从邮箱中读邮件不是问题。多个客户端同时读一个邮箱一点问题都没有,因为他们不会做出任何改动,因此所有的事情都不会出错。但是当有人试图删除第25个邮件而另外的程序正在读这个邮件时会发生什么事呢?这就不一定了,读者可能得到一个崩溃和或者不一致的邮箱视图。因此从安全上来说,即使是从邮箱中读邮件也需要额外注意。

如果你把邮箱看成数据库中的表,而把一个个邮件看成数据库表中的数据行,那么前面谈论到的邮箱中存在的问题在数据库中同样存在。从各方面来说,邮箱可以看成一个简单的数据库。修改数据库表中的数据行与删除或者更新邮箱中的邮件十分相似。

解决这个经典的并发控制问题的方法十分简单。处理并发读写访问的系统实际上实现了一个由两种不同类型的锁组成的锁定系统。这些锁被称为“共享锁”和“互斥锁”或者叫“读锁”和“写锁”。

如果不用关心实际的锁定技术,那么这些概念可以描述如下:一个资源上的读锁是共享的,或者互助非阻塞的,多个客户端可以同时读取同一个资源而不互相干扰;另一方面,写锁是互斥的,也即它同时阻止读锁和其他写锁,因为唯一安全的策略是保证在指定时间里同时只有一个客户端在写入,并且在写入时没有任何一个客户端在读取数据。

在数据库的世界里,锁定时时发生:MySQL必须阻止一个客户在修改数据时另一个客户读取这段数据。它多数情况下会在内部透明地执行这个锁管理。

1.2.2. 锁的粒度

改善一个共享资源的并发性的一个途径是使得你锁定的东西更加具有可选择性。不是锁定整个资源,而是只需要锁定那些你需要改变数据的部分。一个比较好的做法是只锁定你想要改变的数据部分。任何时候一次只锁定尽可能少的数据,只要这些数据之间没有冲突,那么就可以使得指定资源同时发生改变。

问题是锁会消耗资源。每个锁操作,包括获取一个锁,检查一个锁是否空闲,释放一个锁等,它们都会产生额外的开销。如果系统把大量的时间花在管理锁而不是在数据的存取上,那么这个数据库的性能将是无法忍受的。

锁策略是对于锁开销和数据安全之间的一种妥协,而这种妥协会影响效率。大多数商业数据库不会让你有过多的选择:你会得到数据库表上的一个所谓行级锁,以及许多利用很多锁来提高效率的方法,而这些方法经常会很复杂。

另一方面,MySQL却为用户提供了很多选择。不同的存储引擎各自实现了不同的锁策略以及锁粒度。锁管理是存储引擎管理中一个非常重要的决策;对于特定的应用来说,将粒度固定在某一个特定级别可以提高效率,但是这可能会使得那个引擎对于其他应用来说不太适合。但是由于MySQL的存储引擎架构,MySQL并不需要提供一个通用的解决方案。接下来让我们来看两个最重要的锁策略。

1.2.2.1. 表锁

MySQL中可以使用的最基本的锁策略,同时也是开销最小的锁策略,是表锁定。表锁类似于之前描述过的邮箱锁:它将会锁定整张表。当一个客户想要往表中写数据时(如insert, update, delete等),它就需要一个写锁。它将阻止其他所有的读写操作。当没有人写入时,读者可以获取读锁,这个读锁不与其他的读锁相冲突。

表锁定在一些特定情况下为了效率会有所不同。比如Read Local表锁定允许一些类型的并发写操作。另外写锁比读锁的优先级要高,因此一个写锁的请求将会被提前到锁队列的开头,即使有读者已经在队列开头了(写锁可以跨越读锁到锁队列开头,但是读锁不能跨越写锁)。

尽管存储引擎可以管理它们各自的锁,MySQL本身也用了很多高效的表级别的锁以应对各自情况。比如服务器为aleter table这种类型的语句使用了表级别的锁,而不管是什么存储引擎。

1.2.2.2. 行锁

可以提供最大并发性的锁类型是行锁(同时也是资源开销最大的一个)。InnoDB以及Falcon存储引擎以及其他一些引擎都提供了对行级别的锁的支持。行锁是在存储引擎而不是服务器上来实现的(如果你不知道MySQL的整个架构的话,可以去参考MySQL的逻辑架构图)。服务器对于在存储引擎中实现的锁是一无所知的,在本章的后续部分以及整本书中,你可以看到,存储引擎一般都以自己的方式来实现锁机制。

原创粉丝点击