ACID特性:
- 原子性:事务是最小的执行单位 ==》 undo log进行回滚
- 一致性:执行前后数据保持一致 ==》 锁机制或者MVCC里面的快照readview,快照读读取的是统一版本的数据
- 隔离性:各事务之间相互独立 ==》 锁机制或者MVCC里面的快照readview,读写分离。
- 持久性:数据库发生故障也不会对其有任何影响 ==》 redo log日志
事务的隔离级别有哪些?
- 读取未提交(READ_UNCOMMITTED):允许读取尚未提交的数据变更,可能会导致脏读、幻读或者不可重复读。
- 读取已提交(READ_COMMITTED):允许读取并发事务已经提交的数据,可以防止脏读,但是不可重复读或者幻读仍有可能发生。
- 可重复读(REPEATABLE_READ):对同一字段的多次读取结果都是一致的,除非数据是被本身事务修改,可以防止脏读、不可重复读,但是幻读仍有可能发生。
- 可串行化(SERIALIZABLE):完全服从ACID原则、对于同一行记录,读写都会加锁。可以防止脏读、不可重复读、幻读。
事务隔离的实现
每条记录在更新的时候都会同时记录一条回滚操作。同一个记录在系统中存在多个版本,也就是数据库的多版本并发控制(MVCC)
事务隔离级别的实现,依靠视图
- 读取未提交:没有视图的概念,直接返回记录上的最新值。
- 读取已提交:每个SQL开始执行的时候创建的。
- 可重复读:在事务开启时创建的 。
- 可串行化:直接使用加锁的防止避免并行访问。
回滚日志什么时候被删除:当没有事务需要这些回滚日志的时候。
read-views什么时候不需要了:当系统中没有比这个回滚日志更早的read-views的时候。
为什么尽量不要使用长事务?
- 长事务意味着系统会存在很老的事务视图。由于这些事务随时可能访问数据库里面的任何数据,所以在这个事务提交之前,数据库的它可能用到的回滚记录都必须保留,这就会导致占用大量存储空间。
- 占用锁资源、可能会拖垮整个库。
- 事务拿锁是用到那个拿哪一行的锁,但是所有锁的释放要等到事务提交才释放。所以可能会造成死锁、或者占用整个表的字段导致别的SQL语句无法执行。
具体介绍可能出现的问题
- 脏读:当一个事务正在访问数据并且对数据进行了修改,而这个修改还有提交到数据库中,这时另外一个事务也访问了这个数据,然后使用了这个没有提交的数据,那么另外一个事务读取到的这个数据是脏数据。
- 不可重复读:指在一个事务内多次读取同一个数据。在这个事务还没有结束时,另一个事务也访问了该数据。那么在第一个事务中两次读数据之间,由于第二次事务的修改导致第一个事务两次读取的数据可能不太一样。这就导致了在一个事务内两次读取的数据不一样的情况。重点在于修改。
- 幻读:一个事务读取了几行数据,接着另一个并发事务插入了一些数据时。在随后的查询中,第一个事务发现多了一些原本不存在的记录。重点在于数据是被增加或者删除的。
如何防止幻读?
- 通过行锁里面的(next-key locks)间隙锁、记录锁,阻止其他事务在间隙中插入数据,从而防止幻读。
- MVCC通过快照读;参考:https://blog.nowcoder.net/n/d2be56910ab5430a82e702427f5b4a7e
分布式事务
分布式事务用于分布式系统中保证不同节点之间的数据一致性;
XA分布式事务协议
两阶段提交(2PC)
引入协调者来协调参与者的行为,并最终决定这些参与者是否要真正执行事务
- 准备阶段:协调者询问参与者事务是否执行成功,参与者发起事务执行结果;(执行事务、仍未提交)
- 提交阶段:如果事务在每个参与者上都执行成功,事务协调者发送通知让参与者提交事务;否则,协调者发送通知让参与者回滚事务
存在问题:
- 同步阻塞所有事务参与者在等待其他参与者响应的时候都处于同步阻塞状态、无法进行其他操作;
- 协调者单点故障问题:事务协调者是整个XA模型的核心,一旦事务协调者节点挂掉,参与者收不到提交或者回滚的通知,会一直处于中间状态无法完成事务;
- 丢失消息导致不一致问题:在提交阶段,如果发生局部网络问题,一部分事务参与者收到了提交消息,另一部分事务参与者没有收到提交消息,那么就导致了节点之间数据的不一致;
拓展
- 三阶段提交(3PC):在两阶段提交的基础上增加了CanCommit阶段,并且引入超时机制;一旦事务参与者迟迟没有接到协调者的commit请求,会自动进行本地commit。这样有效解决了协调者单点故障的问题。但是同步堵塞和丢失消息导致不一致问题仍然没有根本解决;
- MQ事务:利用消息中间件来异步完成事务的后一半更新,实现系统的最终一致性。避免了XA协议的性能问题(同步阻塞);
- TCC事务:包括try、commit、cancel,逻辑模式类似于XA两阶段提交;但是是针对代码层面认为实现的。