[关闭]
@boothsun 2018-02-18T18:11:40.000000Z 字数 7499 阅读 1144

InnoDB和MyISAM对比

MySQL


参考原文地址如下:
1. MySQL数据库InnoDB存储引擎Log漫游(1)
2. 轻松理解MYSQL MVCC 实现机制
3. 关于innodb中MVCC的一些理解

MyISAM与InnoDB的对比

特性 MyISAM InnoDB
事务 不支持 支持
锁粒度 表级锁 行级锁 + 表级锁
外键 不支持 支持
全表行数查询 通过内置计数器保存表具体行数 全表扫描计算
构成上的区别 每个MyISAM在磁盘上存储为三个文件。文件名字都是以表名开头。 主要分为以下三种:
1. .frm(表定义)
2. .MYD(MyData 表数据)
3. .MYI(My Index 索引文件)
InnoDB使用聚簇索引,所有数据都存储在一个文件中

事务的基础知识点

事务语句

开始事务:BEGIN TRANSACTION
提交事务:COMMIT TRANSACTION
回滚事务:ROLLBACK TRANSACTION

事务分类

扁平事务:

事务要么都执行,要么都回滚,也就是我们最常见、最常使用的事务。扁平化事务只能以COMMIT或者ROLLBACK结束。

带有保存点的扁平事务:

假设事务的操作过程有begin,A,B,C,D,COMMIT 几个过程,那么带有保存点的扁平事务过程大致如下:
begin --> 隐含保存点1(save work 1) --> A --> B(save work2)--> C --> D(rollback work2) --> commit。

上述过程中如果遇到rollback work2,只需要回滚到保存点2,不需要全部回滚。

简单来说,带有保存点的扁平事务就是有针对的回滚操作。

缺点:带有保存点的扁平事务,当发生系统崩溃时,所有的保存点都将消失,因为其保存点是易失的(volatile),而非持久的(persistent)。这意味着当进行恢复时,事务需要从开始处重新执行,而不能从最近的一个保存点继续执行。

链事务:

链事务提交一个事务时,释放不需要的数据对象,然后将必要的处理上下文隐式地传递给下一个要开始的事务。注意,提交事务操作和开始下一个事务操作将合并为一个原子操作。这意味着下一个事务可以看到上一个事务的结果,就好像在一个事务中。

带有保存点的扁平事务可以回滚到任意正确的保存点,链事务只能回滚到当前事务,即只能恢复到最近一个的保存点。

扁平事务全程持有锁,链事务在执行commit后即释放了当前事务所持有的锁。

链事务如下:T1 --> T2 --> T3

嵌套事务:

可以理解为一棵事务树,顶层事务控制着下面各个层次的子事务,所有的叶子节点都是扁平事务,实际工作是由叶子节点完成的。子事务既可以提交也可以回滚。但是它的提交操作并不马上生效,除非其父事务已经提交。

也就是说,任何子事务都在顶层事务提交后才真正的提交。树中的任意一个事务的回滚会引起它的所有子事务一同回滚,故子事务仅保留A、C、I特性,不具有D的特性。

分布式事务:

分布式环境下运行的扁平事务。

InnoDB支持链事务和扁平事务。

InnoDB事务的实现

InnoDB的隔离性是由锁实现的;原子性、一致性、持久性是通过数据库的redo日志和undo日志来完成的。redo log 称为重做日志,用来保证事务的原子性和持久性。undo log用来保证事务的一致性。

redo log:

redo log是新数据的备份,在事务COMMIT操作完成之前,InnoDB会要求必须先将该事务的所有操作日志写入到redo log中,而不需要将数据持久化。当系统崩溃后,虽然数据还没有持久化,但是Redo log 已经持久化了,MySQL可以根据Redo log的内容,将所有数据恢复到最新的状态。通过这种方式实现事务的持久性和原子性。

在细化一点的内部操作是,在事务执行过程中,InnoDB会将执行过程写到内存中的重做日志缓冲区(redo log buffer),在事务执行成功执行完毕后,InnoDB会将内存中的日志缓冲区内log同步到磁盘上的重做日志文件(redo log file)中,从而以文件的形式固化;如果事务执行过程中失败或者回滚等,可由应用程序进行回滚或者在Connection超时断开时自动Rollback;如果事务执行过程中,发生MySQL故障或者机器故障,则内存中的重做日志缓冲区将会丢失,自动回滚,也不会持久化到MySQL中,这样就实现了事务的原子性。

在MySQL宕机恢复后,写到磁盘的redo log将会由MySQL自动持久化到MySQL数据区,也不会产生丢失。这样就实现了事务的持久性。

undo log:

记录了事务修改前的数据状态,是对修改前数据的一种备份,是为了事务回滚操作做的准备。假设修改tb表中id=2的行数据,把Name="B"修改为Name="B2",那么undo日志就会用来存放Name=“B”的记录,如果这个修改出现异常,可以使用undo日志来实现回滚操作,保证事务的一致性。

对数据的变更操作,主要来自 insert update delete,而undo log中分为两种类型,一种是 insert_undo( insert操作 ),记录插入的唯一键值;一种是update_undo(包含update及delete操作),记录修改的唯一键值以及old column记录。

当事务回滚时,InnoDB将执行相反的操作。对于每个insert,会从undo日志中获取唯一键值来完成delete操作;对于每个delete,InnoDB存储引擎会执行一个insert将 old column记录再次插入进去。对于每个update,InnoDB存储引擎会执行一个相反的update,将修改前的行放回去。

处理回滚操作,undo的另一个作用是MVCC,即在InnoDB存储引擎中MVCC的实现时通过undo来完成的。当用户读取一行记录时,若该记录已经被其他事务占用,当前事务可以通过undo读取之前的行版本信息,以此实现非锁定读取。

undo log 过程中也会产生redo log,这是因为undo log也需要持久性的保护。

Undo Log的过程可以简述如下:

假设有A、B两个数据,值分别为1,2,现在需要将其值改为3和4:

Undo + Redo事务的简化过程:

IO性能:

Undo + Redo的设计主要考虑的是提升IO性能。虽说通过缓存数据,减少了写数据的IO;但是却引入了新的IO,即写Redo Log的IO。如果Redo Log的IO性能不好,就不能起到提高性能的目的。

为了保证Redo Log能够有比较好的IO性能,InnoDB 的 Redo Log的设计有以下几个特点:

A. 尽量保持Redo Log存储在一段连续的空间上。因此在系统第一次启动时就会将日志文件的空间完全分配。
        以顺序追加的方式记录Redo Log,通过顺序IO来改善性能。

B. 批量写入日志。日志并不是直接写入文件,而是先写入redo log buffer。当需要将日志刷新到磁盘时(如事务提交),将许多日志一起写入磁盘。

C. 并发的事务共享Redo Log的存储空间,它们的Redo Log按语句的执行顺序,依次交替的记录在一起, 以减少日志占用的空间。例如:Redo Log中的记录内容可能是这样的:
    记录1: < trx1, insert …>
    记录2: < trx2, update …>
    记录3: < trx1, delete …>
    记录4: < trx3, update …>
    记录5: < trx2, insert …>

D. 因为C的原因,当一个事务将Redo Log写入磁盘时,也会将其他未提交的事务的日志写入磁盘。

E. Redo Log上只进行顺序追加的操作,当一个事务需要回滚时,它的Redo Log记录也不会从Redo Log中删除掉。

事务恢复策略

前面说到未提交的事务和回滚了的事务也会记录Redo Log,因此在进行恢复时,这些事务要进行特殊的处理。具体的恢复策略可以有以下两种:

  1. 进行恢复时,只重做已经提交了的事务。
  2. 进行恢复时,重做所有事务包括未提交的事务和回滚了的事务。然后通过Undo Log回滚那些未提交的事务。

InnoDB存储引擎的恢复机制

InnoDB是使用了第二种策略,InnoDB存储引擎中的恢复机制有以下几个特点:

  1. 在重做redo log时,并不关心事务性。恢复时,没有BEGIN,也没有COMMIT、ROLLBACK的行为,也不关心每个日志是哪个事务的。尽管事务ID等事务相关的内容其实已经记录到了Redo Log中,这些内容只是被当做要操作的数据的一部分。

  2. 使用第二种策略就必须要将undo log持久化,而且必须要在写redo log之前将对应的undo log写入磁盘。
    undo和redo log的这种关联,使得持久化变得复杂起来。为了降低复杂度,InnoDB将undo log看作数据,因此记录undo log的操作也会记录到redo log中。这样undo log就可以像数据一样缓存起来,而不用在redo log之前写入磁盘了。
    包含udo Log操作的redo Log,看起来是这样的:
        记录1: <trx1, Undo log insert <undo_insert …>>
        记录2: <trx1, insert …>
        记录3: <trx2, Undo log insert <undo_update …>>
        记录4: <trx2, update …>
        记录5: <trx3, Undo log insert <undo_delete …>>
        记录6: <trx3, delete …>

  3. 到这里,还有一个问题没有弄清楚。既然Redo没有事务性,那岂不是会重新执行被回滚了的事务?确实是这样。同时Innodb也会将事务回滚时的操作也记录到redo log中。回滚操作本质上也是对数据进行修改,因此回滚时对数据的操作也会记录到Redo Log中。一个回滚了的事务的Redo Log,看起来是这样的:
        记录1: <trx1, Undo log insert <undo_insert …>>
        记录2: <trx1, insert A…>
        记录3: <trx1, Undo log insert <undo_update …>>
        记录4: <trx1, update B…>
        记录5: <trx1, Undo log insert <undo_delete …>>
        记录6: <trx1, delete C…>
        记录7: <trx1, insert C>
        记录8: <trx1, update B to old value>
        记录9: <trx1, delete A>
    一个被回滚了的事务在恢复时的操作就是先redo再undo,因此不会破坏数据的一致性。

MVCC实现

什么是MVCC

MVCC是一种多版本并发访问控制机制。我们都知道,锁机制可以控制并发操作,但是其系统开销较大,而MVCC可以在大多数情况下代替行级锁,使用MVCC 能够降低其系统开销。

InnoDB中对MVCC的实现

Innodb MVCC主要是为Repeatable-Read事务隔离级别做的。在此隔离级别下,A、B客户端所操作的数据相互隔离,互不干扰。

Innodb存储的最基本row中包含一些额外的存储信息DATA_TRX_ID,DATA_ROLL_PTR,DB_ROW_ID,DELETE BIT:

image.png-33.7kB

假设 我们有下列表和数据:

  1. create table yang(
  2. id int primary key auto_increment,
  3. name varchar(20)
  4. );

表内容如下(后面两列时隐藏列,我们通过查询语句并看不到):

ID NAME DATA_TRX_ID DATA_ROLL_PTR DELETE BIT
1 yang 1 undefined undefined
2 long 1 undefined undefined
3 fei 1 undefined undefined

SELECT:

InnoDB会根据以下两个条件检查每行记录:
1. InnoDB只会查找版本早于当前事务版本的数据行(也就是 行的系统版本号 小于或等于当前事务的版本号),这样就可以确保当前事务读取到的行,要么是在自己开始前已经存在,要么是自己本身做的插入或者修改。
2. 行的删除版本要么未定义,要么大于当前事务版本号,这可以确保事务读取到的行,在事务开始之前未被删除。

只有上述两点同时满足的记录,才能返回作为查询结果。

DELETE操作:
InnoDB会为删除的每一行记录保存当前事务的事务ID作为删除标识。假设现在有事务ID为2的Connection进行如下操作:

  1. start transaction ;
  2. select * from yang ; //(1)
  3. select * from yang ; //(2)
  4. commit ;

假设1:

假设在执行这个事务ID为2的过程中,刚执行到(1)时,有另一个事务ID为3往这个表里插入了一行数据。事务Id为3的Connection进行了如下操作:

  1. start transaction ;
  2. insert into yang values(NULL,'tian');
  3. commit ;

这时表中的数据如下:

ID NAME DATA_TRX_ID DATA_ROLL_PTR DELETE BIT
1 yang 1 undefined undefined
2 long 1 undefined undefined
3 fei 1 undefined undefined
4 tian 3 undefined undefined

然后接着执行事务2中的(2),由于id=4的数据的事务ID为3,执行当前事务的ID为2,而InnoDB只会查找事务ID小于等于当前事务ID的数据行,所以id=4的数据行并不会在执行事务2中的(2)被检索出来,在事务2中的两条select 语句检索出来的数据都只会下表:

ID NAME DATA_TRX_ID DATA_ROLL_PTR DELETE BIT
1 yang 1 undefined undefined
2 long 1 undefined undefined
3 fei 1 undefined undefined

假设2:

假设在Id为2的事务执行完(1)操作后,接着Id为3的事务也执行完毕,又开始执行了Id为4的事务。事务Id为4的操作如下:

  1. start transaction;
  2. delete from yang where id=1 ;
  3. commit ;

此时数据库表中的表如下:

ID NAME DATA_TRX_ID DATA_ROLL_PTR DELETE BIT
1 yang 1 undefined 4
2 long 1 undefined undefined
3 fei 1 undefined undefined
4 tian 3 undefined undefined

接着执行事务Id为2的事务(2),根据SELECT检索条件可知,它会检索 更新事务Id小于等于当前事务ID的行和删除事务Id大于当前事务的行,而Id=4的行上面已经 说过了。Id=1的行由于删除事务Id大于当前事务的Id,所以事务2的(2)的查询语句也会把id=1的数据检索出来。所以,事务2中的两条select语句检索出来的数据如下:

ID NAME DATA_TRX_ID DATA_ROLL_PTR DELETE BIT
1 yang 1 undefined 4
2 long 1 undefined undefined
3 fei 1 undefined undefined

UPDATE:

InnoDB执行UPDATE,实际上是新插入了一行记录,并保存其更新事务Id为当前事务的ID,同时保存当前事务ID到要UPDATE的行的删除事务Id上。

假设3:

假设在执行完事务2的(1)后又执行,其它用户执行了事务3、4,这时,又有一个用户对这张表执行了UPDATE操作(此时事务Id为5):

  1. start transaction;
  2. update yang set name='Long' where id=2;
  3. commit;

根据update的更新原则:会生成新的一行,并在原来要修改的列的删除时间列上添加本事务ID,得到表如下:

ID NAME DATA_TRX_ID DELETE BIT
1 yang 1 4
2 long 1 5
3 fei 1 undefined
4 tian 3 undefined
2 Long 5 undefined

继续执行事务2的(2),根据select 语句的检索条件,得到下表:

ID NAME DATA_TRX_ID DELETE BIT
1 yang 1 4
2 long 1 5
3 fei 1 undefined

purge(清除操作)操作

delete和update操作可能并不直接删除原有的数据。例如对于下述SQL语句:

  1. DELETE FROM t WHERE A = 1 ;

表t上列a有聚簇索引,列b上有辅助索引。对于delete操作,通过前面关于undo log的介绍已经知道仅是将主键列等于1的记录delete flag设置为1,记录并没有被删除,即记录还是存在于B+树中。其次,对于辅助索引上a等于1,b等于1的记录同样没有做任何处理,甚至没有产生undo loh。而真正删除这行记录的操作其实被“延时”了,最终在purge操作中完成。

purge用于最终完成delete和update操作。这样设计是因为InnoDB存储引擎支持MVCC,所以记录不能在事务提交时立即进行处理。这时其他事务可能正在引用这行,故InnoDB存储引擎需要保存记录之前的版本。而是否可以删除该记录通过purge来进行判断。若该行记录已不被任何其他事务引用,那么就可以进行真正的delete操作。可见,purge操作是清理之前的delete和update操作,将上述操作“最终”完成。而实际执行的操作为delete操作,清理之前行记录的版本。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注