作者介绍:bluesea,腾讯金融云专家工程师,从事分布式数据库TDSQL研发工作。出版著作:《数据库查询优化器的艺术 原理解析与SQL性能优化》、《数据库事务处理的艺术 事务管理与并发控制》,广受好评。同时,bluesea还是中国人民大学信息学院工程硕士企业导师。
TDSQL是一个稳定运行了十年之久的分布式数据库,不仅支撑了腾讯公司的计费业务,而且还在微众银行等金融单位的核心业务系统稳定、高效地运行了四年之久。这几年,TDSQL在技术层面不断进步,研发了很多新特性,诸如多级分区、热点更新、隐含主键、分布式事务等,不仅有力的支撑了事务型的数据库应用,而且在体系结构上也朝Spanner架构上迈进,是一个名副其实的NewSQL系统。
MySQL/TDSQL的事务处理技术,主要包括四个方面的内容。其中,核心重点是并发控制技术。
第一,数据异常现象,这里不仅介绍有大家熟知的、SQL标准规定的三种读数据异常,还有其他的八种异常,会极大扩展大家对数据异常的认识。
第二,MySQL的事务处理技术,包括ACID的各个内容。
第三,MySQL的并发访问控制技术。并发控制技术是数据库事务处理的核心技术。可以说,没有事务处理,数据库就不能算是数据库;没有并发控制技术,事务处理也只是一个名词而已。毫不夸张地说,并发控制技术是核心技术的核心。
第四,基于对MySQL的认识,可以理解主流的数据如Oracle、Informix
数据异常
首先,我们谈第一个问题:数据异常现象有哪些?
这列出了大家都熟悉的三种读数据异常,分别是脏读、不可重复读、幻读。我们看其中一个,比如说脏读。第一步,T2事务修改了数据行row;第二步,T1事务对同一个数据行row读取;第三步,T2事务回滚。这对于事务T1而言,读到的数据是将被回滚的数据,这就是脏读。
有朋友会问,脏读,也没有什么大不了的。试想一下,一个骗子T2转帐1000元给事务T1,事务T1检查自己的账户,入账了1000元,然后事务T1把一件衣服卖给了骗子T2,之后骗子T2拿到衣服后回滚了转账1000元的操作,然后逃之夭夭了。事务T1既没有拿到钱还丢失了衣服,损失很大。所以要避免这样的异常发生。
这三个读异常现象,是大家熟知的,也是SQL标准所定义的数据异常现象。那么,除了读异常,还有其他的数据异常吗?
比如说脏写。第一步,事务T1修改数据行row;第二步,事务T2也修改数据行row并提交,数据修改生效。事务T2认为自己的操作是成功的。但不幸的事情发生了,第三步,事务T1回滚了,用旧值替换了被事务T2写过的值。这意味着事务T2存入银行的钱,丢失了,因为帐本上只记着第一步事务T1读取的数据值。这就是写数据发生的数据不一致的现象。
那么,除了这些读和写异常,还有其他的数据异常吗?
接下来,我们继续介绍两种写偏序异常:两个事务写偏序和三个事务写偏序。
我们来看一下两个事务写偏序。首先,这里有个前提:医院向社会承诺,至少有一名医生对外提供电话咨询服务。但是,如果有多于两个医生在提供电话咨询,则需要某个正在进行电话咨询服务的医生停止服务。
可是,大家看这两个事务。事务T1发现,有两个以上的医生正在提供电话咨询,就请Alice停止电话服务;事务T2也发现有两个以上的医生正在提供电话咨询,就请Bob停止了电话服务。这样,如果执行前只有Alice和Bob正在提供电话服务,这两个事务执行完毕后,没有一个医生在对外提供电话咨询服务了。这就违背了“至少有一名医生对外提供电话咨询服务”的约束前提。这样的现象,也是一种数据异常现象。
到现在为止,我们介绍了七种数据异常现象了。
这里,还例举了四种数据异常,大家如果感兴趣,可以翻阅《数据库事务处理的艺术》一书。
我们一共提及了11种数据异常现象,有读操作造成的三种读异常,也有写操作造成的两种写异常,还有写操作涉及的两种写偏序异常等等。
对于数据库系统而言,如果允许上面所说的数据不一致异常发生,我们这个依靠数据库做交易的世界就会发生巨大混乱,数据库在,账面乱了。人活着,钱没了。
那么,数据异常现象,是怎么产生的呢?
刚才谈到的三种读异常,有个一个共同点,就是存在并发的事务,这是第一个原因。刚才所谈的四种读写组合,就是并发造成的。
第二,并发的事务,操作的是同一个数据对象。
第三,并发的事务对同一个数据对象,进行的总是读写或写读或写写这三种,没有读读。这就是上一个页面里用黄色标识的存在数据异常的三种情况。
第四,还有一种特殊情况,对于幻读而言,受谓词条件的影响,这时不是操作物理上的同一个已经存在的对象,而是操作谓词限定的同一个范围内的逻辑意义上的对象。我们把第四种情况概括为“谓词的语义”。
这些合起来,造成了三种读数据异常。
接下来,我们来分析一下写偏序异常。
第一,存在并发的事务。
第二,并发的事务,操作的不是同一个数据对象。这点和刚才的读异常不同。
第三,并发的事务对不同的数据对象,进行的总是读写或写读或写写这三种,没有读读并发。
第四, 操作结果,违反了“语义前提”。
请看第四点,操作数据时,需要遵守一个语义前提,即“至少有一名医生对外提供电话咨询服务”,但是并发操作打破了这个语义前提,出现了没有医生提供咨询的异常现象。
在数据库里,数据操作会被抽象为两种,就是读操作和写操作。
读写操作组合在一起,有四种情况,就是这幅图里面的,读读、读写、写读和写写。
在数据库里面,只有读读操作,不会引发数据异常,而其他三种,都会引发数据异常。这样的总结,算是一个数据异常发生的原因,但是还不是很准确。
接下来,我们谈第二个话题:MySQL的事务处理技术。主要包括事务锁和系统锁,以及事务的ACID四大特性。
MySQL/TDSQL数据库事务处理技术概述
系统锁是事务锁实现的物理基础,事务锁在系统锁的基础上,增加了事务相关的语义。
在事务锁中,又要分为两部分,一个是元数据锁,如DDL操作施加的事务锁。另外一个是我们在谈及数据库时,常常提及的锁,其实是用户数据上的事务锁。前者虽然在数据库引擎中存在,但常常被人忽略,因为多数用户关心的是用户数据部分。
这幅图用蓝线分为两部分,上面是元数据锁,下面是用户数据锁,他们都是事务锁。这幅图中画出了MySQL的重要的内部数据结构和他们之间的关系,大家可以按图索骥,据此深入了解MySQL内核实现事务锁的相关技术。
在事务锁中,所有技术的核心秘密,都在这张表里面。
例如,前面我们谈到脏读,我们看在MySQL中是怎么避免的。
Granted Mode,表示第一个事务中已经授予的锁,而Requested Mode表示并发的第二个事务,第二个事务请求授予新的锁如表所示。对于脏读,第一个事务已经授予X锁即写锁,此时第二个事务申请S锁即读锁,其对应的单元中是空的表示不能授予,这样第二个事务就被迟滞不能继续执行,于是就避免了脏读现象。
大家可以对应这张表,也许会考虑不可重复读的数据异常是怎么避免的?此时,很有可能会觉得下面这张表是错误的。
但是,MySQL因为又使用了MVCC技术实现了RR即可重复读隔离级别,所以RR的处理方式又有所不同,但这不是表明下面这张表是错误的。这一点,后面会再提及。
数据库的事务处理技术,实现ACID四大特性,通常使用的技术,用下面这幅脑图可以很好的概括。
其中,一致性是目的,原子性和持久性是手段,而隔离性是在数据正确或部分正确的基础上为提高数据库性能而提出的特性。
MySQL的并发访问控制技术
接下来,我们谈第三个话题:MySQL的并发访问控制技术。并发访问控制技术是数据核心技术的核心,也是数据库中最难的技术。主要包括事务模型、ACID四大特性之间的关系、C和I特性的实现技术。
数据库的事务管理器,本质上就是一个有限状态自动机,MySQL的事务处理模块也不另外,事务在各个状态之间进行流转和切换。
总结MySQL的事务处理技术,主要包括如下几个方面。
SS2PL和MVCC都是并发访问控制技术。对于并发访问控制技术,可以依据下图理解,在数据库引擎内部,需要先在保证正确性的基础上,再来提供高的性能。
前面,介绍过通过封锁并发访问控制技术怎么避免脏读,现在,再分析一下MySQL、InnoDB是怎么解决幻象数据异常的。示例如图:
MySQL对于其他隔离级别的实现,也就是怎么避免其他数据异常现象,可以参考如下图和图中所附的链接,详情在链接中供深入阅读参考。
p 序列化隔离级别:http😕/blog.163.com/li_hx/blog/static/1839914132017398565727/
p 可重复读:http://blog.163.com/li_hx/blog/static/1839914132017224904641/?suggestedreading&wumii
p 读已提交:http://blog.163.com/li_hx/blog/static/18399141320172198225727/?suggestedreading&wumii
p 读未提交:http://blog.163.com/li_hx/blog/static/18399141320172113321444/?suggestedreading&wumii
主流数据库事务处理技术
接下来我们谈第四个话题:主流数据库的事务处理技术。
可以看这张对比表格,概括了Informix、Oracle、PostgreSQL和MySQL这四个数据库的并发控制技术。
主流的数据库,几乎都使用了封锁技术和MVCC技术。只有Infomix单纯地使用了封锁技术。
Oracle尽管语法上提供了序列化隔离级别的设置,但没有提供真正的序列化隔离级别。这是因为Oracle经常会清理掉商在活跃的事务所需要的旧数据(以多版本形式存在的旧数据)。
反倒是开源的两个数据库系统,PostgreSQL和MySQL实现了序列化。只是MySQL是在读数据时加锁结合SS2PL技术实现了序列化,这种方式的并发度很低,性能不好。而PostgreSQL则使用SSI技术实现了序列化,性能相对较好。
在第一个问题中我们提出了两种写偏序的数据异常,PostgreSQL使用SSI技术,解决了写偏序异常。
如果从正确性和性能这两个角度来衡量数据库的并发控制技术,显然,PostgreSQL在理论上优于MySQL,PostgreSQL采用的SSI技术复杂但高效。
这幅图详细对比PostgreSQL和MySQL的并发控制技术。
我们从系统锁、事务锁、事务锁的元数据锁和记录元组锁的角度进行对比,然后再从隔离级别的角度来看这两个数据库的并发控制技术。
首先,PostgreSQL和MySQL都提供了系统锁,也都尽量利用了底层的硬件指令如TAS指令实现最基本的spinlock。使用操作系统提供的mutex来控制共享资源的并发操作。
其次,在事务锁方面,PostgreSQL统一管理元数据和用户数据,而MySQL则明显把元数据和用户数据分开用元数据锁和记录锁进行管理,并各自进行了死锁检测。
PostgreSQL对于元组上的并发操作,加元组锁到元组上,把事务ID记录在元组头上,用快照技术判断元组的可见性,操作结束则释放锁。而MySQL则是用内存锁表记录元组锁,等到事务结束后才释放。从这点上看,SS2PL技术的实现,在PostgreSQL和MySQL中是不同的。
从隔离级别的角度看,PostgreSQL和MySQL都采用了MVCC技术来实现可重复读和读已提交。
PostgreSQL和MySQL在并发控制技术方面最大的差别,在于对确保数据一致性的序列化的实现上,采取的技术不同,理论上性能不同。这就是两者在并发控制技术方面的最大不同之处。
TEG计费平台部研发的TDSQL,是基于MySQL的分布式数据库,但TDSQL的做了大量的研发工作,TDSQL的技术一直在进步,分布式事务作为数据库里最难的问题,正在被TDSQL逐步攻克。过去的一年多,TDSQL实现了分布式事务的写数据强一致,其原理如下:
在一个全局的事务调度器上,对于多个SET即跨SET的分布式事务,TDSQL实现了SS2PL技术确保了数据的强一致性,实现2PC技术保证了分布式环境下事务的提交的原子性和一致性,把2PC嵌入到SS2PL整体架构中,实现了分布式数据库事务模型。Spanner等主流分布式数据库在分布式事务处理机制上与TDSQL相似。