在多版本控制以前,数据库仅通过锁机制来实现并发控制。数据库对读操作上共享锁,写操作上排他锁,这种锁机制虽然解决了并发问题,但影响了并发性。
DM 数据库基于物理记录和回滚记录实现行级多版本支持,数据页中只保留物理记录的最新版本,通过回滚记录维护历史版本,所有事务针对特定的版本进行操作。
两个隐藏字段
在DM8中,索引组织表会为每个使用索引组织表的表添加三个隐藏字段,分别是:TRXID、ROLPTR和ROWID。隐藏字段TRXID、ROLPTR用于实现多版本并发控制(MVCC)。
|------|-----|------|
| 物理记录 | TID | RPTR |
|------|-----|------|
| 回滚记录 | TID | RPTR |
RPTR 指向操作生成的undo log的物理地址
TID是事务号
- TRXID(本行数据的事务版本):记录最近更新或插入这条记录的事务ID。当一个事务对表中的一行数据进行修改时,TRXID会被设置为当前事务的ID值。删除操作在内部也被当作一次更新,并在行的特殊位置设置一个删除标记。
- ROLPTR(回滚指针,指向上一次事务ID):指向被写在Rollback Segment中的Undo Log记录。当事务回滚时,可以通过ROLPTR找到之前版本的数据。该行记录上所有的旧版本,在Undo Log中通过链表的形式组织,将多个版本的值串联在一起,方便其他事务查找时保持事务的一致性。
- ROWID(隐藏单调自增ID):随着新行插入而单调递增的隐藏行ID。索引组织表使用聚集索引,数据存储以聚集索引字段的大小顺序进行存储。当表面没有主键或唯一非空索引时,索引组织表会自动生成一个隐藏主键产生聚集索引。ROWID与MVCC无关。
Undo Log一方面可用于MVCC读时构建记录,在MVCC多版本控制中,通过读取Undo Log的历史版本数据可以实现不同事务版本号都拥有自己独立的数据版本。
可见性原则
实现多版本控制的关键是可见性判断,找到对当前事务可见的特定版本数据。
每个事务都记录在活动事务视图中,记录系统中即将产生的事务号 NEXT_TID。
1.物理记录的 TRXID 等于当前事务号,说明是本事务修改的物理记录,物理记录可见;
2.物理记录的 TRXID 不在活动事务表中,并且 TRXID 小于 NEXT_TID,物理记录可见;
3.物理记录的 TRXID 包含在活动事务表中,或者 TRXID 大于等于 NEXT_TID,物理记录不可见;
如果当前物理记录不可见,则按照该记录的ROLLPTR指示,找到其上一个版本,并再次判断该版本可见性,直到找到可见版本为止。
事务隔离级别
脏读
脏数据所指的就是未提交的已修改数据,一个事务在提交前,另一个事务可以看到结果,就会发生脏读。
不可重复读
一个事务先后读取同一条记录,两次读取的数据不同,我们称之为不可重复读。
幻读
一个事务按相同的查询条件重新读取以前检索过的数据,却发现其他事务插入了满足其查询条件的新数据,这种现象就称为幻读。
DM 数据库支持三种事务隔离级别:读未提交、读提交和串行化。 默认是读提交。
设定事务为读提交隔离级:
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
设定事务为读未提交隔离级:
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
设定事务为串行化隔离级:
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
读未提交(Read Uncommitted)
读未提交情况下,可以读取到其他事务还未提交的数据,多次读取结果不一样,出现了脏读。
读已提交(Read Committed)
读已提交情况下,无法读取到其他事务还未提交的数据,可以读取到其他事务已经提交的数据。
可重复读( REPEATABLE )
可重复读是Innodb的默认隔离级别,使用MVCC实现,可重复读情况下,不会出现脏读,不会读取到其他事务已提交的数据,多次读取结果一致,即可重复读。但是可能导致"幻读"。
MVCC的快照策略+undolog解决不了幻读问题,mysql中可以用select where for update(间隙锁)的办法对表的部分数据锁定,间隙锁的目的是为了防止其他事务在这个范围内插入新的数据,从而保证事务的隔离性,解决幻读问题。
串行化( SERIALIZABLE )
串行化严格的事务顺序控制 **,**在串行化隔离级别下,数据库管理系统会严格控制事务的执行顺序,通过加锁来实现的,如果一个事务正在修改数据,其他试图select或修改同一数据的事务将被阻塞,直到第一个事务完成。
串行化隔离级别通过限制并发访问,避免了读未提交、读已提交和可重复读隔离级别中可能出现的问题,如脏读、不可重复读和幻读。这是因为串行化隔离级别确保了每个事务在执行期间,其他事务不会对其造成干扰。
当一个串行化事务试图更新或删除数据时,而这些数据在此事务开始后被其他事务修改并提交时,DM 数据库将报"串行化事务被打断"错误。
如果系统中存在长时间运行的写事务,并且该长事务所操作的数据还会被其他短事务频繁更新的话,最好避免使用串行化事务。