事务处理
- 事务的概念来自于两个独立的需求:并发数据库访问,系统错误恢复。
- 一个事务是可以被看作一个单元的一系列SQL语句的集合
事务的特性(ACID)
- A, atomacity
原子性
事务必须是原子工作单元;对于其数据修改,要么全都执行,要么全都不执行。通常,与某个事务关联的操作具有共同的目标,并且是相互依赖的。如果系统只执行这些操作的一个子集,则可能会破坏事务的总体目标。原子性消除了系统处理操作子集的可能性。 - C, consistency
一致性
。事务将数据库从一种一致状态转变为下一种一致状态。也就是说,事务在完成时,必须使所有的数据都保持一致状态(各种 constraint 不被破坏)。 - I, isolation
隔离性
由并发事务所作的修改必须与任何其它并发事务所作的修改隔离。事务查看数据时数据所处的状态,要么是另一并发事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看中间状态的数据。换句话说,一个事务的影响在该事务提交前对其他事务都不可见。 - D, durability
持久性
。事务完成之后,它对于系统的影响是永久性的。该修改即使出现致命的系统故障也将一直保持。
事务的隔离级别
-
未提交读(Read Uncommitted)
- 直译就是读未提交,意思就是即使一个更新语句没有提交,但是别的事务可以读到这个改变。
Read Uncommitted允许脏读。
- 直译就是读未提交,意思就是即使一个更新语句没有提交,但是别的事务可以读到这个改变。
-
已提交读(Read Committed)
- 直译就是读提交,意思就是语句提交以后,即执行了 Commit 以后别的事务就能读到这个改变,只能读取到已经提交的数据。Oracle等多数数据库默认都是该级别。
Read Commited 不允许脏读,但会出现非重复读。
- 直译就是读提交,意思就是语句提交以后,即执行了 Commit 以后别的事务就能读到这个改变,只能读取到已经提交的数据。Oracle等多数数据库默认都是该级别。
-
可重复读(Repeatable Read)
:- 直译就是可以重复读,这是说在同一个事务里面先后执行同一个查询语句的时候,得到的结果是一样的。
Repeatable Read 不允许脏读,不允许非重复读,但是会出现幻象读。
- 直译就是可以重复读,这是说在同一个事务里面先后执行同一个查询语句的时候,得到的结果是一样的。
-
串行读(Serializable)
- 直译就是序列化,意思是说这个事务执行的时候不允许别的事务并发执行。完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻塞。
Serializable 不允许不一致现象的出现。
- 直译就是序列化,意思是说这个事务执行的时候不允许别的事务并发执行。完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻塞。
事务隔离的实现——锁
- 共享锁(S锁)
用于只读操作(SELECT),锁定共享的资源。共享锁不会阻止其他用户读,但是阻止其他的用户写和修改。 - 更新锁(U锁)
用于可更新的资源中。防止当多个会话在读取、锁定以及随后可能进行的资源更新时发生常见形式的死锁。 - 独占锁(X锁,也叫排他锁)
一次只能有一个独占锁用在一个资源上,并且阻止其他所有的锁包括共享锁。写是独占锁,可以有效的防止“脏读”。
MVCC
- mysql的大多数事务型存储引擎实现的都不是简单的行级锁。基于提升并发性能的考虑,他们一般都同时实现了多版本并发控制(MVCC)。可以认为MVCC是行级锁的一个变种,但是它在很多情况下避免了加锁操作,因此开销更低。虽然实现机制有所不同,但大都实现了非阻塞的读操作,写操作也只锁定必要的行。
- MVCC的实现,是通过保存数据在某个时间点的快照来实现的。也就是说,不管需要执行多长时间,每个事物看到的数据都是一致的。根据事务开始的时间不同,每个事务对同一张表,同一时刻看到的数据可能是不一样的。
- 不同存储引擎的MVCC实现是不同的,典型的有乐观并发控制和悲观并发控制。InnoDB的MVCC,是通过在每行记录后面保存隐藏的列来实现的。这两个列,一个保存了行的创建时间,一个保存行的过期时间(或删除时间)。当然存储的并不是实际的时间值,而是系统版本号。每开始一个新的事务,系统版本号都会自动递增。事务开始时刻的系统版本号会作为事务的版本号,用来和查询到的每行记录的版本号进行比较。
在REPEATABLE READ隔离级别下,MVCC工作如下:
- SELECT----InnoDB会根据以下两个条件检查每行记录:①InnoDB只查找版本早于当前事务版本的数据行(也就是,行的系统版本号小于或等于事务的系统版本号),这样可以确保事务读取的行,要么是在事务开始之前已经存在的,要么是事务自身插入或者修改过的;②行的删除版本要么未定义,要么大于当前事务版本号。这可以确保事务读取到的行,在事务开始之前未被删除。
- INSERT----InnoDB为新插入的每一行保存当前系统版本号作为行版本号。
- DELETE----InnoDB为删除的每一行保存当前系统版本号作为删除标示。
- UPDATE----InnoDB会插入一行新纪录,保存当前系统版本号作为行版本号,同时保存当前系统版本号到原来的行作为行删除标示。
保存这两个额外系统版本号,使大多数读操作都可以不用加锁。这样设计使得读数据操作很简单,性能很好,并且也能保证只会读取到符合标准的行。不足之处是每行记录都需要额外的存储空间,需要做更多的行检查工作,以及一些额外的维护工作。
MVCC只在REPEATABLE READ和READ COMMITTED两个隔离级别下工作。其他两个隔离级别都和MVCC不兼容,因为READ UNCOMMITTED总是读取最新的数据行,而不是符合当前事务版本的数据行。而SERIALIZABLE则会对所有读取的行都加锁。
二段提交协议
在分布式系统中,每个节点仅知道自己的操作是否成功,并不知道其余的节点是否操作成功,为了保证数据库事务的ACID,当一个事务跨越多个数据库时,必须引入一个协调者来统一掌控所有的节点(此时,这些节点被称作参与者)的操作结果,全部成功才进行真正的提交,否则就不提交。
简单概括为:参与者将操作成败通知给协调者,再由协调者根据所有的参与者的反馈情报决定各参与者是否要提交操作还是中止操作
-
所谓二段指的是:准备阶段(投票阶段)和提交阶段(执行阶段)
- 准备阶段:
1) 协调者向所有的参与者节点询问是否可以执行提交操作(vote),并开始等待各参与者的回应
2) 参与者节点执行询问发起为止的所有事务操作,并将undo信息和redo信息写入日志
3) 各参与者节点响应协调者节点的询问,如果参与者节点操作成功则返回“同意”,否则返回“中止”消息 - 提交阶段:
如果协调者收到了参与者失败消息或者超时,直接给参与者节点发送回滚(Rollback)消息;否则,发送提交(commit)消息。参与者根据协调者发的消息,决定执行回滚或者提交的操作,最后释放所有事务中使用的资源锁
- 准备阶段:
二段提交的缺点:
1、同步阻塞问题。执行过程中,所有参与节点都是事务阻塞型的。当参与者占有公共资源时,其他第三方节点访问公共资源不得不处于阻塞状态。
2、单点故障。由于协调者的重要性,一旦协调者发生故障。参与者会一直阻塞下去。尤其在第二阶段,协调者发生故障,那么所有的参与者还都处于锁定事务资源的状态中,而无法继续完成事务操作。(如果是协调者挂掉,可以重新选举一个协调者,但是无法解决因为协调者宕机导致的参与者处于阻塞状态的问题)
3、数据不一致。在二阶段提交的阶段二中,当协调者向参与者发送commit请求之后,发生了局部网络异常或者在发送commit请求过程中协调者发生了故障,这回导致只有一部分参与者接受到了commit请求。而在这部分参与者接到commit请求之后就会执行commit操作。但是其他部分未接到commit请求的机器则无法执行事务提交。于是整个分布式系统便出现了数据部一致性的现象。
4、二阶段无法解决的问题:协调者再发出commit消息之后宕机,而唯一接收到这条消息的参与者同时也宕机了。那么即使协调者通过选举协议产生了新的协调者,这条事务的状态也是不确定的,没人知道事务是否被已经提交。