大家好,又见面了,我是你们的朋友全栈君。
本文章来源于:https://github.com/Zeb-D/my-review ,请star 强力支持,你的支持,就是我的动力。
[TOC]
在线应用业务中,数据库是一个非常重要的组成部分,特别是现在的微服务架构为了获得水平扩展能力,我们倾向于将状态都存储在数据库中,这要求数据库能够正确、高性能处理请求,但这是一个几乎不可能达到的要求,所以数据库的设计者们定义了隔离级别这一个概念,在高性能与正确性之间提供了一个缓冲地带,明确地告诉使用者,我们提供正确性差一点但是性能好一点的模式和正确性好一点但是性能差一点的模式,使用者可以按照你们的业务场景来选择使用。
从本质上讲,隔离级别是定义数据库并发控制的。在应用程序的开发中,我们通常利用锁进行并发控制,确保临界区的资源不会出现多个线程同时进行读写的情况,这对应数据库的隔离级别为可串行化(最高的隔离级别)。现在发现离级别其实是和我们日常开发经常碰到的一个概念了吧,那么现在肯定会有一个问题,为什么在应用程序中可提供可串行化的隔离级别,而数据库却不能提供呢?其根本的原因是应用程序的对临界区都是内存操作,数据库要保证持久性(ACID中的Durability)需要把临界区的数据持久化到磁盘,磁盘操作比内存操作要慢好几个数量级(一次随机访问内存、SSD磁盘和SATA磁盘对应的操作时间分别为几十纳秒、几十微秒和几十毫秒),这会导致临界区持有锁时间变长,对临界区资源竞争将变的异常激烈,数据库的性能会大大降低。
数据库的隔离级别,SQL-92 标准定义了 4 种隔离级别:读未提交 (READ UNCOMMITTED)、读已提交 (READ COMMITTED)、可重复读 (REPEATABLE READ)、串行化 (SERIALIZABLE)。详见下表:
但是由于各数据库的具体实现各不相同,导致同一隔离级别可能出现的异常情况也不相同,所以本文直接从各个隔离级别会带来的异常情况来分析隔离级别的定义。
从读未提交到可串行化,数据库可能出现的异常为:
事务a覆盖了其他事务尚未提交的写入。
事务a读到了其他事务尚未提交的写入。
事务a在执行过程中,对某一个值在不同的时间点读到了不同的值,也叫不可重复读。
两个事务同时执行读-修改-写入操作序列,出现了其中一个覆盖了另一个的写入,但是没有包含对方最新值的情况,导致了被覆盖的数据发生了更新丢失。
事务先查询了某些符合条件的数据,同时另一个事务执行写入,改变了先前的查询结果。
事务先查询数据库,根据返回的结果而作出某些决定,然后修改数据库。在事务提交的时候,支持决定的条件不再成立。写倾斜是幻读的一种情况,是由于读-写事务冲突导致的幻读。写倾斜也可以看做一种更广义的更新丢失问题。即如果两个事务读取同一组对象,然后更新其中的一部分:不同的事务更新不同的对象,可能发生写倾斜;不同的事务更新同一个对象,则可能发生脏写或者更新丢失。
对应四个隔离级别,我们分别来看看他们有什么异常情况,以及怎么通过应用层的优化来避免该异常的发生:
在前面的讨论中,我们提供了很多种方式来避免更新丢失,那么在写倾斜的时候可以使用吗?
TiDB 实现了快照隔离 (Snapshot Isolation, SI) 级别的一致性。为了与 MySQL 保持一致,又称其为“可重复读”。该隔离级别不同于 ANSI 可重复读隔离级别和 MySQL 可重复读隔离级别。
当事务隔离级别为可重复读时,只能读到该事务启动时已经提交的其他事务修改的数据,未提交的数据或在事务启动后其他事务提交的数据是不可见的。对于本事务而言,事务语句可以看到之前的语句做出的修改。对于运行于不同节点的事务而言,不同事务启动和提交的顺序取决于从 PD 获取时间戳的顺序。处于可重复读隔离级别的事务不能并发的更新同一行,当时事务提交时发现该行在该事务启动后,已经被另一个已提交的事务更新过,那么该事务会回滚并启动自动重试。示例如下:
create table t1(id int);
insert into t1 values(0);
start transaction; | start transaction;
select * from t1; | select * from t1;
update t1 set id=id+1; | update t1 set id=id+1;
commit; |
| commit; -- 事务提交失败,回滚
尽管名称是可重复读隔离级别,但是 TiDB 中可重复读隔离级别和 ANSI 可重复隔离级别是不同的。按照 A Critique of ANSI SQL Isolation Levels 论文中的标准,TiDB 实现的是论文中的快照隔离级别。该隔离级别不会出现狭义上的幻读 (A3),但不会阻止广义上的幻读 (P3),同时,SI 还会出现写偏斜,而 ANSI 可重复读隔离级别不会出现写偏斜,会出现幻读。
MySQL 可重复读隔离级别在更新时并不检验当前版本是否可见,也就是说,即使该行在事务启动后被更新过,同样可以继续更新。这种情况在 TiDB 会导致事务回滚,导致事务最终失败,而 MySQL 是可以更新成功的。MySQL 的可重复读隔离级别并非快照隔离级别,MySQL 可重复读隔离级别的一致性要弱于快照隔离级别,也弱于 TiDB 的可重复读隔离级别。
本文我们讨论了数据库出现隔离级别这个概念的根本原因是数据库设计者因为要保证持久性,因而有大量的磁盘操作,导致临界区变长,性能急剧下降,提出的一个trade-off的方案,让使用者根据自己的业务场景来选择不同的隔离级别,然后我们讨论了不同的隔离级别导致的异常情况的处理方法,确保可以写出高性能并且正确的程序,最后我们介绍了tidb隔离级别的情况。
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/141508.html原文链接:https://javaforall.cn