一、什么是事务
事务上一系列SQL语句组成,用来保证要么一组全部完成,或者都不完成。事务执行中也支持回滚,保证操作中的任何数据修改都不会提交。事务里的操作其实是会立即在数据发生修改的,只是修改的数据不会被其他客户端的查询看见,因为用到了锁机制。
事务通常以BEGIN TRANSCATION开始,事务通常可以包含修改性的语句:insert、update、delete、merge(也就是select for update)。然后最终要么COMMIT或者ROLLBACK。除了显示表达commit或者roolback,还有隐形发生的roolback或者commit。比如
- 隐性的roolback:网络断开连接。
- 隐形的commit:关闭会话
- 隐形的commit:开始一个DDL命令create
通常来说insert/update/delete/merge都包含子查询select语句。查询语句在事务内部看到是当前事务insert/update/delete/merge的最新数据,而在事务外面看不到这些数据更新,除非当前事务被commit之后。
1.1 事务解决问题
事务最经常解决的是不同客户端的数据竞争问题。还可以用来,
- 要执行某个操作非常小心,然后你写个事务,等待提交前检查一遍确认ok后才正式commit
- 打包所有语句成一次性atomic级别的数据更新。不会有人看到中间状态的脏数据
- 组合语句,变成要么提交,要么不提交,比如说
SELECT columns FROM LiveTable
<Do calculations - 可以是SQL或者呼叫其他应用>
BEGIN TRAN
INSERT data INTO DataWarehous
UPDATE LiveTable SET ETLDone = 1 WHERE it was inserted into DW
COMMIT
二、并发控制机制
不同的事务隔离机制提供了不同一致性等级。我们以云上存储系统为例。云存储在多个机器提供了冗余数据以面对不可预知的故障。数据在多台机器之间并不总是保持完全一致。因此数据从不同服务器读到的可能是不同版本的数据。
云产商比如微软Azure提供的是强一致性服务。强一致性服务确保所有客户端总是看到最后一次提交的数据。强一致性还比如经常用于数据中心之间的同步。这些数据中心常常分不在不同地理位置不同大洲的机房。
也有很多云存储系统比如Amazon S3设计为弱一致性的服务。这样设计的理由是强一致性是成本代价很大。通过对一致性的妥协来换取可用性和更好的性能。这种系统里,客户端可能读到的是过时的数据,不一定是最新版本的数据。读取过期数据会发现在还来不及同步其他数据中心的数据之前。这种系统也称为最终一致性。
当然还有些厂商提供了不同的级别来给用户选择,比如Amazon DynamoDB同时提供了强一致性和最终一致性。
还有些厂商提供了一种介于强一致性和最终一致性的方案。比如说读取到不过期5分钟的数据提交。
折衷一致性的背后原理就是CAP准则。CAP理论就是系统必须容忍网络分区的存在,用户必须在Consistency和Availablablity的之间选择。
我们来看一下一般数据库怎么做到事务隔离。
一、事务的隔离级别
1.1 隔离级别对应的读到的状态可能性
事务隔离级别有四种,以下图为例,四种隔离级别的读数据状态会是不一样的:
代码语言:javascript复制事务 A: |---0---|---1---|---2---|---3---|---4---|
事务 B: |---5---|---6---|---7---|---8---|
查询操作:|读事务开始 <--- 不同的隔离级别在不同的时间点读到不同的状态-->|
- READ UNCOMMITABLE: 这种读到状态可能是最多的0-8都有可能
- READ COMMITTABLE : 这种读到0、4、8
- REPEATEDABLE READ:这种读到0
- SERAIABLE:查询操作不可访问,并且事务B的操作会被阻塞。效果如下图
事务 A: |---0---|---1---|---2---|---3---|---4---|
事务 B: |---5---|---6---|---7---|---8---|
查询操作: 无论是读事务或者写事务 都会得到排他锁,如果是对同一记录操作,事务A一旦先开始,事务B就不能操作,或者查询也不能操作。
1.2 从锁机制看事务隔离级别
- READ UNCOMMITABLE: 无任何事务控制,无加任何读锁,写锁
- READ COMMITABLE,写时候加了排他锁,读了时候使用记录粒度的读锁(共享锁,这个共享锁 不锁事务,锁记录)。事务A查询的时候 读到的是就记录,事务B做了提交,事务A再次查询(也就是不可重复读)。这两次会得到不一样的结果。如果在一个事务里,这个事务里做了两次同样的记录数量查询。两次查询的结果不一样(幻读)。
- REPEATEDLE READ,可重复读,和READ COMMITABLE不同的时,读锁(共享锁)锁的是整个事务执行的过程。所以在整个事务的执行过程中,任何其他事务尝试更新完这条记录的结果,这个读事务都是用MVCC提交前的版本)。和READ COMMITABLE区别的是,读记录锁的是记录还是事务过程。两者锁的时间不一样。
- SERAIABLE:RANGE KEY,查询满足这个range范围的这些个数的记录都被锁住。
1.2.1 不可重复读和幻读的区别
不可重复读的重点是修改:
同样的条件 , 你读取过的数据 , 再次读取出来发现值不一样了
幻读的重点在于新增或者删除(导致记录数变化)
同样的条件 , 第 1 次和第 2 次读出来的记录数(强调的是记录数,而不是记录本身,因为读锁的锁粒度是记录自身,而不是整张表)不一样。
1.3 不同隔离级别的错误读取
通过在写的时候加锁,可以解决脏读。
通过在读的时候加锁(或者MVCC提供旧的提交版本),可以解决不可重复读。
通过串行化,可以解决幻读。
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
READ UNCOMMITABLE | 1 | 1 | 1 |
READ COMMITABLE | 0 | 1 | 1 |
REPEATABLE READ | 0 | 0 | 1 |
SERIAL | 0 | 0 | 0 |