事务隔离性
事务是数据库管理系统中的一种机制,用于确保在多个操作之间维护数据的一致性和完整性。在数据库中,一个事务表示一组相关的操作,这些操作必须被视为单个、不可分割的单元。如果在执行一个事务的过程中出现了错误或故障,那么整个事务将被回滚,使数据库恢复到原始状态,从而确保数据的一致性。
事务还可以解决并发访问数据库时可能出现的问题。在多用户环境下,如果多个用户同时对同一数据进行修改,则可能会导致数据不一致的情况。通过使用事务,多个用户可以同时访问数据库,但只有一个用户能够修改数据,其他用户必须等待该用户完成操作后才能进行操作,从而避免数据冲突和不一致性。
因此,事务对于确保数据库的一致性和完整性非常重要,在高并发的应用程序中尤为重要。
事务特性
就是常讲的 ACID •᷄ࡇ•᷅
Atomicity 原子性
一个事务中的所有操作要么全部完成,要么全部不完成,不会结束在中间某个环节。
TIP
原子性 无法被分割的性质。同理,原语(原子语句) 是指计算机中不能再分的最小操作
Consistency 一致性
数据一致性是指在一个系统或者数据库中,所有的数据都应该保持精确、一致和可靠。当多个用户对同一份数据进行读写时,系统应该能够保证所有用户看到的数据是一致的,并且每次操作之后数据的状态也应该是一致的。如果数据不一致,就会导致系统功能出现错误,影响用户体验,甚至可能会导致数据丢失或损坏。
TIP
在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏。也就是说,在事务执行过程中,如果出现了任何错误,那么数据库将回滚到事务开始前的状态。
Isolation 隔离性
多个事务并发访问时,每个事务都应该感觉不到其他事务的存在,好像它们是顺序执行的一样。
Durability 持久性
一旦事务完成,无论发生什么错误,结果都应该被永久性地保存下来,即使系统崩溃也不影响数据的持久性。
事务的最终目标
事务通过原子性、隔离性、持久性来保证一致性
即 C 是由 AID 来保证的
这里的一致性其实又有点说法,可以从其他地方看出来数据的一致性和数据的完整性是数据库中经常提到的比较大的概念
例如: 在实际应用中,为了保证数据的一致性和完整性,通常会使用各种技术和工具来提高数据的安全性和可靠性,如事务、锁定机制、备份与恢复等等。
因此,数据的一致性的实现其实是需要事务的支撑的,而事务的一致性的实现通过 AID 来保证。
我想说他们其实是不同的东西,但是既然都叫一致性,那当然在概念的某个点上是一样的意思:即都是保证多个用户对数据的访问是一致的
不懂有没有谬论 💩,这些概念只是便于我自己理解,欢迎指正。
TIP
网上找到的: InnoDB 引擎通过什么技术来保证事务的这四个特性的呢?
- 持久性是通过 redo log (重做日志)来保证的;
- 原子性是通过 undo log(回滚日志) 来保证的;
- 隔离性是通过 MVCC(多版本并发控制) 或锁机制来保证的;
- 一致性则是通过持久性+原子性+隔离性来保证;
事务的隔离级别
数据库事务的隔离级别有 4 种,由低到高分别为Read uncommitted 、Read committed 、Repeatable read 、Serializable 。
在事务的并发操作中可能会出现脏读,不可重复读,幻读等等情况,而事务的隔离级别就是 DBMS 提供的用来防止这些影响业务的情况产生的事务级别。
ヽ(´・д・`ノ) 相当于是防止并发操作导致数据不一致(again)的级别保证,当然这些等级是 DBMS 自动做的锁机制,无需手动操作,必要的时候设置就可以了。
Read uncommitted
读未提交,意思是一个事务可以读取到其他未提交事务正在编辑的数据。(坏)
很明显,这样的事务级别会导致很大的问题,这类的名字习惯上被数据库人定义为 Dirty Read 脏读
懒得举例子,请想象一下如果一个事务读取到了另一个事务正在修改的数据,并认为这是正确的数据,然而下个瞬间,另个事务回滚了,那么这个数据实际上是错误的,是并发的中间产物,因此是脏读。
Read committed
读已提交,意思是一个事务仅可以读取到数据,当且仅当编辑这条数据的其他事务都已经提交了。
写入数据库时,只会覆盖已经提交的写入数据(不脏写)。
废话多,就是没有事务在编辑这条数据了,是不是像读写锁,应该就是吧 💩
脏读,正是因为读取了未提交的事务修改过程中的数据才会出现的问题,那么只要限制成提交后才能读取数据就能防止这种情况了。
又有一种情况 non-Repeatable read 不可重复读,指事务多次读取其他事务修改提交的数据,但前后不一致,比如一个事务的持续时间比较长,那么它多次读取的同一条数据却是不同的结果,但这是因为有其他事务同时对该数据进行了修改,并成功地提交了。⚠️ 这里的关键是,存在事务读取某条数据的情况下,其他事务仍然能够修改它,这是造成不可重复读的原因。
那么不可重复读有么坏处吗,或者说在什么场景下会出现问题?
首先,应该大部分的业务都不会连续读取同一个数据多次,但是问题就是问题,由墨菲定律知道它总是会出现的 💩。
这里引用了 别人的 blog 的例子
作者还有详细可行的优秀解决方案,我只摆出了问题
银行做活动。事务 a 查询某地区余额 1000 以下送一包餐巾纸,生成名单。事务 b 小明余额 500,存了 1000,变成 1500。事务 a 查询 1000 到 2000 送一桶油,生成名单,这样小明收到了 2 个礼品。
后果是小明利用数据库的漏洞薅到了羊毛 ꉂ೭(˵¯̴͒ꇴ¯̴͒˵)౨”
但是这明显是银行不想要的后果。
不可重复读对应的是修改操作,即 update 操作
Repeatable read
可重复读,那么意味着开始读取数据时(事务开始),只能读到该事务启动时已经提交的其他事务修改的数据,未提交的数据或在事务启动后其他事务提交的数据是不可见的。
经常有人把 Repeatable read 简称为 RR,我老是反应不过来 💩
如果你认真读了下来,那么应该能理解这个级别为什么能防止不可重复读的问题,
好吧,总有人能从只言片语中找到漏洞,请再看看可重复读的定义:开始读取数据时,不允许其他事务对数据进行修改。
但没说不能进行数据的添加或删除啊?如果事务对一些数据进行了多次的统计操作,那么每次得到的结论都不同 这就是Phantom read 幻读。
一般只有事务对数据进行集合操作才会出现问题,即多次读取的结果集不同(感觉也可以是不可重复读的子类问题哈,不知道为啥要单独分出来 💩)
幻读对应的是增删操作,即 insert、 delete 操作
WARNING
其实这块内容的争议挺大的,关于 Repeatable Read 到底能不能避免幻读,就我实验来看(Mysql、Postgres)在 Repeatable Read 的级别下,大部分的情况的幻读都可以避免的,通过多版本并发控制(MVCC)实现的快照读,但是也有无法避免的情况, 可以通过间隙锁( next-key lock )的select for update...方式去避免。
除了幻读,还有一种情况无法被该级别解决:序列化异常 ( serialization anomaly ),这种情况是指成功提交一组事务的结果与一次运行这些事务的所有可能顺序不一致,即:并发执行事务的结果,与以某种顺序串行执行这一组事务的结果不一致。
我发现国内提这个现象的文章挺少的,不知道是不是我搜索姿势不对 💩
Serializable
这就是最终的等级,序列化意味着所有的事务都是串行执行的,在此级别上运行的事务能够产生相同的结果。
从根本上解决了所有因为并发产生的问题,因为它根本没有并发!
你也可能产生一个顾虑,如果仅仅读取数据就不允许其他事务对数据的修改了,那么会对性能造成影响,这是显然的,隔离级别越高并发性能会越差,因此我们需要在一致性和数据库性能中做权衡。
Serializable 之前的级别都只加上了行级锁,而它加上了表级锁,Serializable 付出的代价较大, 一般来说不会轻易使用的(mysql)。
对于这个级别,不同的数据库有不同的实现机制:
- Mysql 使用加锁,如果事务没拿到锁的话,该事务会直接被关闭,这样防止了死锁。
- Postgres 使用了依赖检测技术来阻止不可重复读取、不一致并发更新和序列化异常,结果来说:如果检测到不一致更新会直接报错。
TIP
使用高级别的隔离,必须要考虑到不同的数据库实现的方式,并为死锁和异常制定合适的重试机制。
常用数据库默认事务隔离级别
Postgres、Sql Server、Oracle 默认 read committed
Mysql 默认 Repeatable read
TODO
TIP
默认并不意味着全部,事实上现代 DBMS 都能修改事务隔离级别,重要的是使用高隔离级别时一定要对 DBMS 的实现方式足够熟悉,并制定正确的策略(重试等等)来处理并发问题。