主要阶段
- 初始化阶段
- 计算全局的时间戳,赋值给当前事务,此时间戳用于确定哪些行可以被看到
- 生成TxId
- 插入行的新版本(但是BeginTs或者EndTs为TxId,根据操作类型生成1-2个delta节点,delta节点指向版本)
- 此阶段检查写写冲突
- 验证阶段(执行commit命令后)
- 递增全局时间戳并将其赋值逻辑上结束时间
- 根据不同的表提示来决定如何进行验证
- 序列化(检查幻读)
- 可重复读(检查不可重复读)
- 快照(检查主键冲突)
- 注意验证阶段只检查开启事务后的冲突
- 此阶段数据即可被外部事务看到而导致出现提交依赖问题
- 提交阶段
- 写事务日志
- 将行版本的开始时间或者结束时间设置为第二步计算出的逻辑结束时间
提交依赖问题
第一个事务执行commit指令之后,第二个事务的第一阶段获取到这些数据(因为只有第一个事务commit指令执行之后这些暂时没有被提交的数据才会被其他事务看见),即会造成提交依赖,第二个事务需要等待第一个事务提交或回滚之后来决定自身的动作(提交或回滚,和第一个事务一致)
什么时间赋值时间戳给事务
经过验证发现,为开启事务后,所执行的第一条语句
通过查询sys.dm_db_xtp_transactionsdmv实现,具体可以参考sys.dm_db_xtp_transactions
例子
第一个例子
| 事务1 |
事务2 |
| begin tran |
begin tran |
|
insert into table values (xxx) |
| select * from table with (serializable) |
|
|
commit |
| 失败 |
|
| 解释:第一个事务可以看见第二个事务插入的行,序列化检查失败 |
|
第二个例子
| 事务1 |
事务2 |
| begin tran |
begin tran |
|
insert into table values (xxx) |
|
commit |
| select * from table with (serializable) |
|
| commit |
|
| 解释:第二个事务执行完毕之后第一个事务还没有真正开启,不会触发可序列化的检查 |
|
第三个例子
| 事务1 |
事务2 |
| begin tran |
begin tran |
|
update table set xxx = aaa |
|
commit |
| update table set xxx = bbb |
|
| commit |
|
| 解释:第二个事务执行完毕之后第一个事务还没有真正开启,所以两个事务都可以正常提交 |
|