一条SQL更新语句是如何执行的?

针对链接中文章的读后思考:https://time.geekbang.org/column/article/68633

redo log概念

        redo log记录的是新数据的备份。在事务提交前,只要将Redo Log持久化即可,不需要将数据持久化。当系统崩溃时,虽然数据没有持久化,但是RedoLog已经持久化。系统可以根据RedoLog的内容,将所有数据恢复到最新的状态。

        InnoDB有buffer pool(简称bp)。bp是数据库页面的缓存,对InnoDB的任何修改操作都会首先在bp的page上进行,然后这样的页面将被标记为dirty并被放到专门的flush list上,后续将由master thread或专门的刷脏线程阶段性的将这些页面写入磁盘(disk or ssd)。这样的好处是避免每次写操作都操作磁盘导致大量的随机IO,阶段性的刷脏可以将多次对页面的修改merge成一次IO操作,同时异步写入也降低了访问的时延。然而,如果在dirty page还未刷入磁盘时,server非正常关闭,这些修改操作将会丢失,如果写入操作正在进行,甚至会由于损坏数据文件导致数据库不可用。为了避免上述问题的发生,Innodb将所有对页面的修改操作写入一个专门的文件,并在数据库启动时从此文件进行恢复操作,这个文件就是redo log file。这样的技术推迟了bp页面的刷新,从而提升了数据库的吞吐,有效的降低了访问时延。带来的问题是额外的写redo log操作的开销(顺序IO,当然很快),以及数据库启动时恢复操作所需的时间。

WAL技术是什么,好处?

         WAL的全称是Write-Ahead logging,预写式日志是数据库系统提供原子性和持久化的一系列技术。在使用WAL的系统中,所有的修改都先被写入到日志中,然后再被应用到系统状态中。通常包含redo和undo两部分信息。

为什么需要使用WAL,然后包含redo和undo信息呢?举个例子,如果一个系统直接将变更应用到系统状态中,那么在机器掉电重启之后系统需要知道操作是成功了,还是只有部分成功或者是失败了(为了恢复状态)。如果使用了WAL,那么在重启之后系统可以通过比较日志和系统状态来决定是继续完成操作还是撤销操作。

redo log称为重做日志,每当有操作时,在数据变更之前将操作写入redo log,这样当发生掉电之类的情况时系统可以在重启后继续操作。

undo log称为撤销日志,当一些变更执行到一半无法完成时,可以根据撤销日志恢复到变更之间的状态。

MySQL中用redo log来在系统Crash重启之类的情况时修复数据(事务的持久性),而undo log来保证事务的原子性。

redo log的crash safe机制

CrashSafe指MySQL服务器宕机重启后,能够保证:
- 所有已经提交的事务的数据仍然存在。
- 所有没有提交的事务的数据自动回滚。
前面的文章讲过,Innodb通过Redo Log和Undo Log可以保证以上两点。为了保证严格的CrashSafe,必须要在每个事务提交的时候,将Redo Log写入硬件存储。这样做会牺牲一些性能,但是可靠性最好。为了平衡两者,InnoDB提供了一个系统变量,用户可以根据应用的需求自行调整。

- innodb_flush_log_at_trx_commit
  0 - 每N秒将Redo Log Buffer的记录写入Redo Log文件,并且将文件刷入硬件存储1次。N由innodb_flush_log_at_timeout控制。
  1 - 每个事务提交时,将记录从Redo Log Buffer写入Redo Log文件,并且将文件刷入硬件存储。
  2 - 每个事务提交时,仅将记录从Redo Log Buffer写入Redo Log文件。Redo Log何时刷入硬件存储由操作系统和innodb_flush_log_at_timeout决定。这个选项可以保证在MySQL宕机,而操作系统正常工作时,数据的完整性。

binlog概念,作用

      binlog是一个二进制格式的文件,用于记录用户对数据库更新的SQL语句信息,例如更改数据库表和更改内容的SQL语句都会记录到binlog里,但是对库表等内容的查询不会记录。 默认情况下,binlog日志是二进制格式的,不能使用查看文本工具的命令(比如,cat,vi等)查看,而使用mysqlbinlog解析查看。 binlog有两种模式,statement格式是记录sql语句,row格式会记录行的内容,记两条,更新前后都有。

      当有数据写入到数据库时,还会同时把更新的SQL语句写入到对应的binlog文件里,这个文件就是上文说的binlog文件。使用mysqldump备份时,只是对一段时间的数据进行全备,但是如果备份后突然发现数据库服务器故障,这个时候就要用到binlog的日志了主要作用是用于数据库的主从复制及数据的增量恢复。

binlog 和redo log对比

     ●redo log是InnoDB引擎特有的;binlog是MySQL的server层实现的,所有引擎都可以使用;

     ●redo log是物理日志,记录的是“在某个数据页上做了什么修改”;binlog是逻辑日志,记录的是这个语句的原始逻辑,“比如给ID=2这行的C字段加1”;

     ●redo log是循环写的,空间固定会用完;binlog是可以追加写入的。“追加写”只binlog文件大小写到一定时候会自动切换到下一个文件并不会覆盖原来的内容。

update语句(update T set c=c+1 where ID = 2)执行流程

     1.执行器先取到ID=2这行。因为ID是主键,引擎直接用树搜索找到这一行。如果ID=2这一行所在的数据页本来就在内存中,就直接返回给执行器;否则需要先从磁盘读入内存,然后再返回。

     2.执行器拿到引擎给的行数据,把这个值加1,比如原来N,现在是N+1,得到新的一行数据,再调用引擎接口写入这行数据。

     3.引擎将这行新数据更新到内存中,同时将这个更新操作记录在redo log里面,此时redo log处于prepare状态。然后告知执行器执行完成了,随时可以提交事务。

     4.执行器生成这个操作的binlog,并把binlog写入磁盘中。

     5.执行器调用引擎的提交事务接口,引擎把刚刚写入的redo log改成提交(commit)状态,更新完成。

执行流程图如下:浅色框InnoDB中执行,深色框执行器内执行。

数据库误操作,如何恢复?

当需要恢复到指定某一秒时,比如某天下午两点发现中午十二点又一次误删表,需要找回数据,那么可以这么做:

●首先找到最近的一次全量备份,从这个备份恢复到临时库;

●然后,从备份的时间点开始,将备份的binlog一次取出来,重放到中午误删表之前的那个时刻。

这样得到的临时库就是误删之前的线上库一样了,然后就可以把表数据从临时库中取出来,按需要恢复到线上库中。

两阶段提交

 在开启Binlog后,MySQL内部会自动将普通事务当做一个XA事务来处理:

- 自动为每个事务分配一个唯一的ID

- COMMIT会被自动的分成Prepare和Commit两个阶段。

- Binlog会被当做事务协调者(Transaction Coordinator),Binlog Event会被当做协调者日志。

参考文档:https://en.wikipedia.org/wiki/Two-phase_commit_protocol

- 分布式事务ID(XID)
使用2PC时,MySQL会自动的为每一个事务分配一个ID,叫XID。XID是唯一的,每个事务的XID都不相同。XID会分别被Binlog和InnoDB记入日志中,供恢复时使用。MySQ内部的XID由三部分组成:
- 前缀部分
  前缀部分是字符串"MySQLXid"
- Server ID部分
  当前MySQL的server_id
- query_id部分
  为了保证XID的的唯一性,数字部分使用了query_id。MySQL内部会自动的为每一个语句分配一个query_id,全局唯一

- 事务的协调者Binlog
Binlog在2PC中充当了事务的协调者(Transaction Coordinator)。由Binlog来通知InnoDB引擎来执行prepare,commit或者rollback的步骤。事务提交的整个过程如下:
1. 协调者准备阶段(Prepare Phase)
    告诉引擎做Prepare,InnoDB更改事务状态,并将Redo Log刷入磁盘。
2. 协调者提交阶段(Commit Phase)
    2.1 记录协调者日志,即Binlog日志。
    2.2  告诉引擎做commit。

注意:记录Binlog是在InnoDB引擎Prepare(即Redo Log写入磁盘)之后,这点至关重要。

为什么使用两阶段提交?

反证法论述不使用两阶段提交,那就要么先写redo log 再也binlog,或者反过来,先写binlog再也redo log.

1.先redo log在binlog。假设在redo log写完,binlog还没写完,Mysql异常重启。redo log写完之后,即使系统崩溃了,仍能把数据恢复回来,所以原库的这行数据是1。但是由于binlog没写完就crash了,这时候binlog里就没有记录这次更新的语句,之后通过binlog恢复临时库的话,由于binlog丢失,临时库里会少了这次的更新,恢复出来的c任然是0,与原库的数据不一致。

2.先binlog在redo log。假设在binlog写完,redo log未写完发生crash。由于redo log没写完,发送异常,恢复后这个事物是无效的,所以原c值是0。但是binlog上是有这条更新记录的,根据binlog恢复临时库会有这条更新操作,临时表数据是1,与原库的数据不一致。

可以看到如果不使用“两阶段提交”,那么数据库的状态就有可能和用它的日志恢复出来的状态不一致,这是不合理的。

两阶段提交时,若redo log写入成功,binlog写入失败,那么此时的redo log只是出于prepare状态,崩溃恢复后,这条prepare状态的redo log是无效的,因此原库的数据原值是0,而binlog本身没有这条更新的记录,通过其恢复的临时库也不会有这次更新,临时表依旧是0,与原库一直,因此是合理的。 

猜你喜欢

转载自blog.csdn.net/zxp0727/article/details/84580277