一、前言
在单用户环境下,在操作数据库是不需要考虑其他用户会修改同一个数据。但是在多用户的情况下,多个事务可能会修改同一个数据,最终会得到错误的数据结果。
Oracle数据库是通过 multiversion consistency model(多版本数据一致性模型)、还有不同类型的锁、事务隔离保证数据的一致性。
通过这种方式,数据库可以向多个并发用户提供在某一个时间点所对应的数据库数据。由于不同版本的数据块可以同时存在,事务可以查询所需时间点已经提交的数据版本,并返回对应时间点已提交的数据查询结果。(数据块的头部存储了历史事务信息)
提问:什么是数据一致性呢?
在数据库中,数据一致性是指在多个并发事务同时访问数据库时,确保读取操作的结果对于所有事务都是一致的。
这意味着当一个事务正在读取数据时,如果其他事务正在对相同的数据进行修改或写入操作,读取操作不应该看到未完成的或部分更改的数据。相反,读取操作应该看到已经提交的、完整的数据,以确保事务之间的数据一致性。
二、数据读一致性
数据的读一致性分为两个层面,一个是语句级别的读一致性,另外一个是事务级别的读一致性。
2.1、语句级别读一致性(Statement-Level Read Consistency)
Oracle数据库强制使用语句级别的读一致性,这样保证每次返回的数据是在这个时间节点之前已经提交的数据。
当进行一次查询时,发生在 SCN 10023(SCN是Oracle数据库的一种顺序标识,代表事务先后),只会看到10023之前已经提交的数据。在图中有两个大于SCN10023的数据块,是SCN1024,这时 Oracle数据库会将这两个数据块拷贝到一个缓冲区中,然后根据undo segment(撤销段)中的数据重新构建这两个数据块(CR,consistent read clones)在1024前已经提交的数据,最后正确的检索就是SCN100021->SCN100021->SCN10006->SCN10008->SCN10021->SCN10011->SCN10021,过这种机制每次查询的时候查询到的都是事务已经提交的数据,防止了脏读。
提问:那什么是SCN呢?
SCN的全称是System Change Number,它在数据库中充当时钟的角色,是数据库内部使用的一个逻辑数据,Oracle数据库可以根据它的大小来判断事务发生的先后顺序。Oracle数据库是在SGA(Sytem Global Area)中完成SCN增量操作。当事务修改数据时,数据库会将新的SCN保存到与事务相对应的撤销段中(undo segment)中。
提问:即使有了SCN机制,数据库能够知道事务的先后顺序,那数据库是怎么知道事务提交了还是没有提交?
每一个数据库块(block,Oracle数据库底层逻辑存储最小单元),都有一个数据块头(block header),里面包含了在数据库块上进行的事务活动(事务执行历史),里面记录了事务的状态(活跃 Active、提交 Commit、回滚 RollBack)。
2.2、事务级别的读一致性(Transaction-Level Read Consistency)
Oracle数据同样也可以对一个事务中的多条查询语句提供读一致性。在事务中的每条语句看到的都是同一节点(同一SCN)的已经提交的数据。
2.3、读一致性的数据存储
提问:Oracle数据库是如何给每个不同SCN对应的事务看到之前已经提交的数据呢?这些数据又是放到那里呢?
要管理多版本读取一致性模型(multiversion read consistency model),数据库必须在同时查询和更新表时创建一组读取一致性数据。
这里就涉及到了撤销段,撤销段(undo segment)是数据库的一个逻辑结构,用于管理extents(区),而 extents 是由多个数据库块(block)组成。
每当用户修改数据时,Oracle 数据库都会产生一个撤销数据实体(undo entries),之后会将这个实体写入到撤销段(undo segment)中。撤销段中包含已提交事务或者未提交事务所作更改之前数据库中的旧数据,
通过这种方式,同一个数据,在不同时间节点的不同版本数据可以存在于数据库中。这样数据库可以提供不同时间节点,不同版本的数据库快照视图,来保证读一致性。
2.4、锁机制
Oracle数据的某一条事务在修改数据时,其他事务不能在进行修改,实现这种操作就要使用到锁机制。
锁我的理解是它是一种机制,一种用于防止破坏性交互行为的机制,破坏性行为就是指并发修改某一个数据,修改后的结果是错误的,通过锁这种机制来避免错误的修改结果,保证数据库数据的一致性。
锁大致可以分为两类,一种是排他锁,另外一种是共享锁。多个资源在竞争锁时,只有一个资源能够获取到排他锁,多个资源可以获取到共享锁。只有当修改数据时,数据才会被锁定,正常情况下是锁定一行数据,而不是整张数据库表。
三、事务隔离级别
Oracle数据库提供三种事务隔离级别分别是RC(read committed)、Serializable、read-only。
3.1、RC隔离级别
RC隔离级别是Oracle数据库默认的隔离级别,在RC隔离级别下可以保证每次查询到的数据都是事务已经提交的数据,不会读取到事务未提交的数据,保证数据的一致性。也就说当一个查询正在查询一个表中id为2的数据时,此时另外一个事务把该数据修改了,但是查询出的结果是修改之前的结果,不会查询到事务未提交的结果。
在RC隔离级别下,可能会出现更新丢失的问题。这是一个很诡异的现象。举个例子说一下:
假设某公司要给小张涨工资1k,现在有两个财务操作员A和B,它们进行了以下操作:
1.操作员A查询出了小张现有的工资,选择了薪水修改,并输入了要提薪的1000数据,但此时有事,临时走开了,没有提交(事务未提交)。
2.操作员B查询出小张现有的工资,选择了薪水修改,并输入了要提薪的1000数据,然后提交了修改。
3.操作员A忙完事情回来了,然后提交了修改。
小张高兴坏了,自己涨了两千的工资。
在这个例子中操作员A没有提交,但是在提交的时候要重新查询一下看一下薪水有没有被修改。这样不会造成操作员B的修改丢失。
比如可以通过乐观锁的形式,给表上添加版本列进行解决,版本列可以是日期,一个递增的版本数字等。这是一种比较好的方式,当然也存在其他方式。
3.2、Serializable隔离级别
Serializable隔离级别是Oracle数据库最高级的事务隔离级别,在这个级别下,数据库确保事务之间的操作不会相互干扰(行级锁定、多版本并发控制等),从而保证了数据的一致性和完整性。
在序列化事务隔离级别下,事务只能看到在事务开始时做的更改以及事务本身所做的更改。如果某个事务开始后,其他事务已经提交了对相同数据的修改,那么这个事务就会被视为尝试修改已经被其他事务更改的数据,此时Oracle数据库会抛出ORA-08177: Cannot serialize access for this transaction异常。
3.3、Read-Only隔离级别
read-only事务隔离级别,是一个简单粗暴的事务隔离级别,它只允许事务进行读取数据,不允许修改数据,但是当用户是sys用户时将允许进行数据修改。