-
原子性
原子性首先体现在事务对数据的修改,事务包含的所有操作要么全部成功,要么全部失败回滚。因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响。
-
一致性
一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态,这是说数据库事务不能破坏关系数据的完整性以及业务逻辑上的一致性。有时候这种一致性是由
数据库的内部规则
保证的,例如数据的类型必须正确,数据值必须在规定的范围内,另外一些时候这种一致性由应用保证
,比如一般情况下银行帐务不能是负数,信用卡不能超过额度1.数据库机制层面
数据库层面的一致性是,在一个事务执行之前和之后,数据会符合你设置的约束(唯一约束,外键约束,Check约束等)和触发器设置。这一点是由SQL SERVER进行保证的。比如转账,则可以使用CHECK约束两个账户之和等于2000来达到一致性目的
2.业务层面
对于业务层面来说,一致性是保持业务的一致性。这个业务一致性需要由开发人员进行保证。当然,很多业务方面的一致性,也可以通过转移到数据库机制层面进行保证。
强一致性:读操作可以立即读到提交的更新操作。 弱一致性:提交的更新操作,不一定立即会被读操作读到,此种情况会存在一个不一致窗口,指的是读操作可以读到最新值的一段时间。 最终一致性:是弱一致性的特例。事务更新一份数据,最终一致性保证在没有其他事务更新同样的值的话,最终所有的事务都会读到之前事务更新的最新值。如果没有错误发生,不一致窗口的大小依赖于:通信延迟,系统负载等。 其他一致性变体还有: 单调一致性:如果一个进程已经读到一个值,那么后续不会读到更早的值。
会话一致性:保证客户端和服务器交互的会话过程中,读操作可以读到更新操作后的最新值。
在事务T开始时,此时数据库有一种状态,这个状态是所有的MySQL对象处于一致的状态,例如数据库完整性约束正确,日志状态一致等,当事务T提交后,这时数据库又有了一个新的状态,不同的数据,不同的索引,不同的日志等,但此时,约束,数据,索引,日志等MySQL各种对象还是要保持一致性(正确性)。 这就是 从一个一致性的状态,变到另一个一致性的状态。也就是事务执行后,并没有破坏数据库的完整性约束(一切都是对的)。
-
隔离性
隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离,数据库需要保证每个事务在它的修改全部完成之前,对其它事务是不可见的。换句话说,不能让其他事务看到该事务的中间状态。隔离性可以通过不一样的配置达到不一样的隔离级别。
-
持久性
持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。
例如我们在使用JDBC操作数据库时,在提交事务方法后,提示用户事务操作完成,当我们程序执行完成直到看到提示后,就可以认定事务以及正确提交,即使这时候数据库出现了问题,也必须要将我们的事务完全执行完成,否则就会造成我们看到提示事务处理完毕,但是数据库因为故障而没有执行事务的重大错误。
当事务被提交后就无法再回滚,如果想要撤销一个已经提交的事务,那就只能执行一个效果与其相反的事务,这也是持久性的一种体现。关于这点,MySQL依然是通过日志实现的。
-
丢失更新
第一类:两个事务同时修改一个数据项,但后一个事务中途失败回滚,则前一个事务已提交的修改都可能丢失。
第二类:两个并发事务同时读取和修改同一数据项,则后面的修改可能使得前面的修改失效。
-
脏读:一个事务读取另一个未提交事务的数据
当一个事务正在多次修改某次数据,而在这个事务中多次修改都还未提交,这时一个并发的事务来访问该数据,就会造成两个事务得到的数据不一致。例如A向B转账,如果B在只执行了第一条sql的时候查看账户,就会发现100块钱已经到账,但如果最后事务没有提交,整个事务回滚,最后B会发现钱并没有到账,如果B在刚才发现到账时取钱,就会导致最后B多出100快钱。
-
不可重复读
不可重复读指对于数据库中的某个数据,一个事务在事务范围内多次查询该数据,查询出来的结果不一致,这是由于另外的已提交事务修改了该数据。 例如事务A在读取某个数据,而事务B立马修改了该数据,事务A再次读取的时候就会读到修改后的数据。 不可重复读与脏读的区别是:脏读是读另一事务未提交的,不可重复读读的是另一事务已提交的。
-
幻读
幻读指当数据不是独立执行时发生的一种现象,例如一个事务对一个表中的数据进行了修改,涉及表中所有数据行,同时第二个事务向表中插入一条数据,那么就会发生操作第一个事务的用户发现还有没有修改的数据行,就好像发生了幻觉一样。 幻读与不可重复读都是读取另一事务已经提交的数据,区别是不可重复读查询的是同一个数据项,而幻读针对的是一批数据整体(例如数据个数)。
隔离级别 | 脏读 | 不可重复读 | 幻读 | 第一类丢失更新 | 第二类丢失更新 | 实现方式 |
---|---|---|---|---|---|---|
串行化 | x | x | x | x | x | 片区的共享读锁和排他写锁 |
可重复读 | x | x | √ | x | x | 共享读锁和排他写锁 |
读已提交 | x | √ | √ | x | √ | 瞬间共享读锁和排他写锁 |
读未提交 | √ | √ | √ | x | √ | 排他写锁 |
隔离级别最高的是S级别,最低的是RU级别,但是隔离级别越高,执行效率就越低,像S级别,就是以锁表的方式,使得其他线程只能在锁外等待,所以平时使用时选用何种级别可根据实际情况,mysql默认的是Repeatable read (可重复读),oracle默认的是Read committed(读已提交)。
1)事务读其他事务未提交数据,出现脏读; 2)如果一个事务已经开始写数据,则另外一个事务则不允许同时进行写操作,但允许其他事务读此行数据。该隔离级别可以通过“排他写锁”实现。 3)避免了更新丢失,却可能出现脏读。也就是说事务B读取到了事务A未提交的数据。 (读未提交:一个事务写数据时,只允许其他事务对这行数据进行读,所以会出现脏读,事务T1读取T2未提交的数据)
1)允许写事务,所以会出现不可重复读 2)读取数据的事务允许其他事务继续访问该行数据,但是未提交的写事务将会禁止其他事务访问该行。 3)该隔离级别避免了脏读,但是却可能出现不可重复读。事务A事先读取了数据,事务B紧接了更新了数据,并提交了事务,而事务A再次读取该数据时,数据已经发生了改变。 (读已提交:读取数据的事务允许其他事务进行操作,避免了脏读,但是会出现不可重复读,事务T1读取数据,T2紧接着更新数据并提交数据,事务T1再次读取数据的时候,和第一次读的不一样。即虚读)
1)禁止写事务; 2)读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务。 3)避免了不可重复读取和脏读,但是有时可能出现幻读。这可以通过“共享读锁”和“排他写锁”实现。 (可重复读:读事务会禁止所有的写事务,但是允许读事务,避免了不可重复读和脏读,但是会出现幻读,即第二次查询数据时会包含第一次查询中未出现的数据)
RR再加锁的时候只在相应数据上加锁,要防止幻读需要在对应数据的一定范围内加锁,甚至可能需要在表上加锁
INNODB用RR+next-key lock 避免幻读,next-key lock相当于行锁加gap锁(范围锁)
在SNAPSHOT隔离级别下,任何写操作都会将更新之前的数据行保存到tempdb中,读取操作要么从Original Database的数据表中读取数据,要么从tempdb中读取行版本数据。Snapshot隔离级别指定:在一个事务中,任何语句读取的数据,是事务一致性的版本。事务一致性是指在事务开始时,在表级别创建数据快照,只能识别其他事务已提交的数据更新。在事务开始之后,当前事务不会识别其他事务执行的数据更新。Sanpshot隔离级别实现事务级别的数据一致性。SQL Server 使用tempdb来存储行版本化(row versioning)的数据,如果数据更新较多,存储的行版本太多,会导致tempdb成为系统瓶颈。
1,在Snapshot隔离级别下,更新操作创建Row Version
一旦启用Snapshot隔离级别,在事务中执行更新操作时,SQL Server将被更新的数据行的原始版本存储在tempdb中,即在tempdb中保存数据行的Original data,因此,读取行版本的数据,都只能读取到数据行被更新之前的值。每一个事务都拥有一个唯一的,递增的顺序号,记作TSN(Transaction Sequence Number),TSN能够唯一标识一个事务,每一个行版本都存储一个TSN,标识创建该行版本的事务。
Once snapshot isolation is enabled, updated row versions for each transaction are maintained in tempdb. A unique transaction sequence number identifies each transaction, and these unique numbers are recorded for each row version.
2,Snapshot隔离实现事务一致性
Snapshot隔离级别实现事务级别的数据一致性,这意味着,在单个事务中的所有查询语句,看到的是相同版本的数据。在Snapshot隔离级别下,事务在读取数据不需要加行级锁或页级锁,读写操作互不阻塞。
The term "snapshot" reflects the fact that all queries in the transaction see the same version, or snapshot, of the database, based on the state of the database at the moment in time when the transaction begins. No locks are acquired on the underlying data rows or data pages in a snapshot transaction, which permits other transactions to execute without being blocked by a prior uncompleted transaction. Transactions that modify data do not block transactions that read data, and transactions that read data do not block transactions that write data, as they normally would under the default READ COMMITTED isolation level in SQL Server. This non-blocking behavior also significantly reduces the likelihood of deadlocks for complex transactions.
3,Snapshot 使用乐观并发模式
Snapshot隔离级别使用乐观并发模式,如果一个Snapshot 事务尝试去提交数据行的更新,但是该数据行已经被其他事务修改,并且修改的时间早于当前事务开始的时间,那么SQL Server将当前事务作为失败者,并回滚其事务操作。乐观并发模式用于冲突较少的环境中,如果Application在更新数据时经常发生冲突,Snapshot隔离级别可能不是最好的选择。
Snapshot isolation uses an optimistic concurrency model. If a snapshot transaction attempts to commit modifications to data that has changed since the transaction began, the transaction will roll back and an error will be raised.
4,Snapshot 隔离和 Row Version的工作模式
当启用Snapshot隔离级别时,每一个更新数据的操作都会在tempdb中存储该行的原始副本,术语叫作行版本(RowVersion),SQL Server为每个行版本添加事务的TSN,该TSN能够唯一标识更新操作所在的事务。读操作在读数据时,按照以下顺序进行:
- 创建一个新的事务,为其分配TSN,一个唯一,递增的序号;
- snapshot事务从数据表中读取数据行,从tempdb中读取行版本(row version),该行版本的TSN最接近当前事务的TSN,但比当前事务的TSN小;
- 在创建Snapshot时,从已提交的事务中获取行版本数据,如果行版本数据标识的事务尚未提交,那么从更早的事务中获取已提交更新的数据;
- 事务从tempdb中读取行版本数据,事务不会看到新插入的数据,因为插入数据的TSN比当前事务的TSN大;
- 事务能够看到被其他事务删除的数据,前提是删除数据的事务的TSN比当前事务的TSN大,这是因为其他事务将行版本保存到tempdb中,当前事务从tempdb中读取行版本数据;
When the SNAPSHOT isolation level is enabled, each time a row is updated, the SQL Server Database Engine stores a copy of the original row in tempdb, and adds a transaction sequence number to the row. The following is the sequence of events that occurs:
- A new transaction is initiated, and it is assigned a transaction sequence number.
- The Database Engine reads a row within the transaction and retrieves the row version from tempdb whose sequence number is closest to, and lower than, the transaction sequence number.
- The Database Engine checks to see if the transaction sequence number is not in the list of transaction sequence numbers of the uncommitted transactions active when the snapshot transaction started.
- The transaction reads the version of the row from tempdb that was current as of the start of the transaction. It will not see new rows inserted after the transaction was started because those sequence number values will be higher than the value of the transaction sequence number.
- The current transaction will see rows that were deleted after the transaction began, because there will be a row version in tempdb with a lower sequence number value.
The net effect of snapshot isolation is that the transaction sees all of the data as it existed at the start of the transaction, without holding or placing any locks on the underlying tables. This can result in performance improvements in situations where there is contention.
A snapshot transaction always uses optimistic concurrency control, with holding any locks that would prevent other transactions from updating rows. If a snapshot transaction attempts to commit an update to a row that was changed after the transaction began, the transaction is rolled back, and an error is raised.
1)禁止任何事务,一个一个进行; 2)提供严格的事务隔离。它要求事务序列化执行,事务只能一个接着一个地执行,但不能并发执行。如果仅仅通过“行级锁”是无法实现事务序列化的,必须通过其他机制保证新插入的数据不会被刚执行查询操作的事务访问到。 3)序列化是最高的事务隔离级别,同时代价也花费最高,性能很低,一般很少使用,在该级别下,事务顺序执行,不仅可以避免脏读、不可重复读,还避免了幻读。
未提交读(read uncommitted)这个等级是最低等级,也可以认为,事务之间完全不隔离,事务A开始一个事务,接着事务B开始,事务B对数据C继续update,这时候,A读取了B未提交(commit)的数据,这种情况叫做脏读(dirty read)。这个时候要是事务B遇到错误必须rollback,那么A读取的数据就完全是错的。可以想象这样完全不隔离的状态下,我们相对于数据库的业务方程序员写的一个sql,提交个db的执行引擎,返回的结果是多么不可确定啊。
提交读(read committed)既然读取别的事务未提交的数据很不安全,那么在上一个等级完全裸奔的情况下,增加一个要求:事务读取的数据,都是别的事务已经提交了的。但是只要在还没达到串行执行的情况下,总会有问题的,事务A select了一条数据,接着事务B update 这条数据,然后commit,这时候A还未提交,A再回来读这条数据,发现数据居然变了,按照我们之前所说,我们的目标是:对于一个事务本身来说,它所感知的数据库,应该只有它自己在操作,那么A会觉得自己并没有更新数据啊,怎么数据突然变了,这种情况叫做 不可重复读(Non-repeatable reads)
可重复读(repeatable read)可重复读,即是在上一个级别的基础上,保证不会在一个事务内两次select同一条数据会出现变化,即是别的事务对你select的对象进行update操作不会影响。但是,如果是insert操作,在这个隔离级别还是会受到影响。事务A开启事务,并select一段有范围的数据,然后事务B开启事务,在先前A事务select的那段有范围的数据中insert一条数据,然后提交事务,接着事务A再select出来这段数据,发现数据多了一条,这种情况叫幻读(Phantom Read)
序列化读(serializable)这也就是最高级别,保证事务之间不会有任何踩踏,每个事务都可以认为只有它自己在操作数据库。即数据库的事务是可串行化执行的。