PHP,DDD,CQRS,Event Sourcing,Kubernetes,Docker,Golang

0%

了解mysql的并发控制

关于mysql的并发控制,主要涉及“锁”这个概念。本篇博文为纯文字理论,看似可能有点枯燥,但易于理解也易懂。 何谓“锁”,“锁”是用来干嘛的?

我们先来看一个例子,我们以Unix系统的email box为例,这是一个邮箱,邮箱中的所有邮件都串行在一起,彼此首尾相连。这种格式对于读取和分析邮件信息非常友好,同时投递邮件也很容易,只要在文件末尾附加新的邮件内容即可。 那么,问题来了! 如果有两个进程同一时刻对同一个邮箱投递邮件,会发生什么情况?显然,邮箱的数据会被破坏,两份邮件的内容会交叉的附加在邮箱文件的末尾。那么我们就需要一种东西来控制邮件的投递,使得一个客户投递邮件时,将邮箱锁住,此时其他客户无法对此邮箱进行投递而进入等待,直到这个客户投递完毕解开锁才能进行投递。这就是所谓的“锁”,与mysql中的“锁”概念类似。

在mysql中主要有两种锁,一个是共享锁(shared lock),一个排他锁(exclusive lock),也分别称为读锁(read lock)和写锁(write lock)。 读锁是共享的,或者说是相互不阻塞的。多个客户在同一时刻可以同时读取同一个资源,而互不干扰。写锁则是排他的,也就是说一个写锁会阻塞其他的写锁和读锁,这是处于安全策略的考虑,只有这样,才能确保在给定的时间内只有一个用户能执行写入,并防止其他用户读取正在写入的同一资源。

锁粒度

mysql该如何加锁?修改数据时将全部数据都锁定?还是只是锁定要修改的那一块数据?加锁会不会影响性能?这就是要讨论的锁的粒度大小。 锁的粒度影响着共享资源的并发性,所以加锁时应该选择明确的锁定对象。尽量只锁定需要修改的部分数据,而不是所有数据。更理想的方式是,只对会修改的数据片进行精确的锁定。任何时候,在给定的资源上,锁定的数据量越少,则系统的并发程度越高。 however,加锁也是需要消耗资源的,锁的各种操作,包括获得锁、检查锁是否已经解除、释放锁等,都会增加系统开销。

因此,我们需要在追求并发性和数据安全性之间寻求平衡,即如何加锁,制定一个锁策略。 mysql中提供了多种的加锁选择,每种mysql存储引擎都可以实现自己的锁策略和锁粒度。在存储引擎的设计中,锁管理是个非常重要的决定。将锁粒度固定在某个级别,可以为某些特定的应用场景提供更好的性能,但同时却会失去对另外一些场景的良好支持。好在mysql支持多个存储引擎的架构,所以不需要单一的通用的解决方案。 下面介绍两种锁策略:

表锁(table lock)

表锁是mysql中最基本的锁策略,并且是开销最小的策略。表锁非常类似前文描述的邮箱加锁机制:它会锁定整张表。一个用户在对表进行写操作(插入、修改、删除)钱,需要先获得写锁,这会阻塞其他用户对该表的所有读写操作。只有没有写锁时,其他读取的用户才能获得读锁,读锁之间是不会相互阻塞的。

在特定的场景中,表锁也可能有良好的性能。写锁比读锁有更高的优先级,因此一个写锁请求可能会被插入到读锁队列的前面,而读锁则不能插入到写锁的前面。 尽管存储引擎可以管理自己的锁,mysql本身还是会使用各种有效的表锁来实现不同的目的。例如,服务器(详见前面的逻辑架构一文)会为诸如ALTER TABLE之类的语句使用表锁,而忽略存储引擎的锁机制。

行级锁(row-level lock)

行级锁可以最大程度的支持并发处理,同时也带来了最大的锁开销。在InnoDB以及其他一些存储引擎中实现了行级锁。行级锁只在存储引擎层实现,而mysql服务器层没有实现。 以上这些就是对mysql并发控制的理解,通过锁机制来有效控制并发。

如有疑问欢迎留言探讨,毕竟我也是刚开始深入学习mysql。