Atomicity (原子性) :
定义: 用户发起屡次写请求: 中间某次写失败则全部写操做都被回滚数据库
实现方式: 灾害恢复日志网络
Consistency (一致性):
定义: 应用层面的不变性在事务中被保持并发
隔离级别: 解决事务并发对数据产生影响的问题
常见的并发问题:分布式
隔离级别 | 描述 | 脏读 | 不可重复读 | 幻影读 | 实现方式 |
---|---|---|---|---|---|
serializable | 并发的事务等同于事务一个个执行,彻底没有并发问题,可是性能最差 | false | false | false | 实际序列化执行, 二段锁, 可序列化快照分离 |
repeatable read | 快照分离(snapshot isolation): 事务中读取事务开始前的提交数据,即便其余事务在期间改动数据并提交 | false | false | true | MVCC (Multi-version concurrency control) 记录数据的多个版本,找到事务开始前的数据版本 |
read committed | 事务提交的数据才能被读到, 写操做只能覆盖提交的数据 | false | true | true | 防止脏写: 对事务中写对象持有行级锁直到事务结束. 防止脏读: 记录数据的2个版本,没法解决计数器更新丢失问题 |
Durability (持久性):
定义: 事务提交以后数据被保存到持久化存储,不会由于宕机而丢失ide
实现方式:
单机: 先写日志(write ahead log)
集群: 保证写操做被持久化到多个节点性能
处理复杂对象如JSON等, 须要保证原子性和隔离性
原子性经过先写日志 (write ahead log) 保证
隔离性经过锁或者compare and swap, 有的数据库提供原子性的read-modify-write操做如increment
不一样于事务, 事务通常是针对多个不一样对象的处理
许多分布式数据库由于实现上的困难只保证单对象写而放弃多对象事务
多对象事务仍然在如下场景须要:
关系型数据库:确保外键一致性被正确保持
文档型数据库: 因为数据被DENORMALIZE,一般一次更新会涉及多个文档,须要保证多个文档被同时正确更新
存在索引的状况下, 保证数据更新和索引的一致性优化
放弃执行到一半的事务并重试
重试可能存在的问题:
事务成功执行但由于网络错误没法获得事务结果:
若是错误是由于系统超载,重试只会让问题变得更糟
重试通常能解决暂时问题, 死锁,网络异常,隔离级别错误等, 若是是永久性错误引发的问题(违反外键约束等), 重试没有意义
重试不能解决事务中数据库以外额外影响, 须要辅助二段式事务等方式
重试中若是客户进程崩溃会致使数据丢失线程
写数据时,修改的数据被TAG惟一自增交易ID(用于断定交易发生前后), 例如transactionID = 13的事务修改ID为1的数据从V1->V2, 再修改ID为2的数据从V1->V2, 为每次操做生成修改前和修改后两条交易版本数据:
id=1, dataValue=V1, createdBy=5, deleteBy=13
id=1, dataValue=V2, createBy=13, deleteBy=nil
id=2, dataValue=V1, createdBy=5, deleteBy=13
id=2, dataValue=V2, createdBy=13, deleteBy=nil
MVCC数据可见规则:日志
换而言之, 若是一个对象对于读事务可见: 对象
MVCC索引实现和优化:
问题定义: 两个事务同时读取数据,进行更新,再将数据写回,其中一个事务的写结果被另外一个覆盖. 好比计数器更新
解决方法:
在集群复制场景下(多主复制或无主复制): 比较写入方法不能正常工做, LAST WRITE WIN策略会形成写丢失. 原子化更新有效
冲突实体化 (materialize conflict): 将写冲突转化为一系列的主键锁. 好比建立预订房间记录(房间号+时间范围),并对预订记录加锁. 将并发控制处理渗漏到业务逻辑中,不推荐.
实际序列化执行 (actual serialization):
实现方式:
二阶段锁 (two phase locking):
实现方式:
缺点:
前提锁 (predicate lock):
锁符合查询条件的对象,读写锁获取和二阶段锁相似
索引范围锁 (index range lock):
比predicate lock范围大,不精确匹配全部查询条件而匹配其中某个索引查询,以下午1:00-2:00对某个房间的预订. 直接对房间号加锁或者对时间范围加锁.
若是没有找到合适的索引,对全表加共享锁.
可序列化快照分离 (serializable snapshot isolation):
实现方式:
根据事务开始前预设定的前提决定事务是否能提交, (好比每一个房间/时间段预订数<=1). 而后检查查询结果是否被改变.
事务1读到一个旧的MVCC版本(数据已经被另外一个事务修改但未提交), 再提交时发现新的MVCC版本已经被事务2提交, 而且后续事务1也对这个数据进行了修改. 则事务1必须回滚
事务1开始后事务2修改了数据. 并早于事务2提交. 若是后续事务1也对这个数据进行了修改. 则事务1必须回滚
我的阅读事务章节的一点感想:事务控制无非两种: 乐观: 经过冲突检测决定事务是否回滚. 悲观: 经过加锁阻止并发发生.从另外一个维度看, 不一样的隔离级别决定了两个事务对于数据库的读写操做是否阻塞,隔离级别越低,阻塞的场景越少任何隔离级别一个事务的读操做不会阻塞读读阻塞写, 写阻塞读 (Serializable级别 -- two phase locking)写阻塞读, 但读不阻塞写写阻塞写 ()