MYSQL日志系统:一条SQL查询语句是如何执行的?

下面是一个表的创建语句,该表有一个主键ID和一个整形字段c

mysql>create table T(ID int primary key,c int);

如果要将ID=2这一行的值+1:

mysql>update T set c=c+1 where ID=2;

分析之前再回顾以下mysql的逻辑架构图:
在这里插入图片描述
执行语句前先通过连接器连接数据库。

缓存略过。

接下来分析器通过词汇和语法解析这是一条更新语句。

优化器决定使用ID这个索引。

执行器负责具体执行,找到对应位置,更新。

与查询流程不同的是,更新还涉及了两个重要的日志模块,也就是本文的重点:redo log(重做日志)和bin log(归档日志)

重要的日志模块:redo log

假设酒店掌柜有一个粉板,专门用来记录客人的赊账记录。如果赊账的人不多,那么可以把顾客名和账目写在粉板上,但是如果赊账的人多了,粉板记不下的时候,掌柜一定还有一个专门记录赊账的账本。

如果有人要赊账或者还账,一般掌柜采取两种做法:

  • 直接拿出账本,记录赊账或还账。
  • 在粉板上记下这次的账目,等打烊后再把账本翻出来算。

而在生意红火柜台很忙时,掌柜一定会选择后者,因为前者操作太麻烦了。首先,得找到这个人得赊账总额那条记录,密密麻麻几十页,掌柜要找到那个名字,可能还得带上老花镜慢慢找,找到之后再拿出算盘计算,最后再将结果写入账本。

相比之下,还是粉板记一下方便,你想想,如果掌柜没有粉板的帮助,每次记账都得翻账本,效率是不是就低得让人难以忍受?

在MYSQL里同样存在这个问题,如果每一次更新操作都要写入磁盘,然后磁盘也要找到对应的那条记录更新,整个过程IO成本,查询成本都很高。为了解决这个问题,MYSQL就采用了类似掌柜粉板的思路提升更新效率。

粉板和账本配合的整个过程,也就是MYSQL里常说到的WAL技术(write-ahead-logging),其关键点在于先写日志,再写磁盘,对应粉板账本也就是先写粉板,之后有空闲再写账本。

具体来说,每当有一条记录需要更新,InnoDB就会先把记录写到redo log里,并更新到内存,这个时候更新就完成了。同时,InnoDB会在适当的时候,将这个操作记录更新到磁盘,而这个操作往往是在系统比较空闲的时候去做。

如果今天赊账不多,掌柜可以等打烊后再整理,但如果某天赊账的特别多,粉板写满了怎么办呢?这时掌柜只能把粉板中的一部分赊账记录更新到账本,然后把这些记录在粉板上擦除,腾出新的记账空间。

与此类似,InnoDB的redo log是固定大小的,比如可以配置一组4个文件,每个文件大小1GB,那么这个"粉板"总共可以记录4GB的操作,写到末尾又重头循环写,如下图所示。
在这里插入图片描述
write pos是当前记录位置,一边写一边后移,写到ib-logfile-3末尾回到ib-logfile-0开头。
check point是当前要擦除的位置,一边擦除一边后移,擦除记录前要把当前记录更新到数据文件。

write pos和check point之间的部分也就是"粉板"还空余的部分,可以用来记录新的操作,如果write pos追上check point,则表示"粉板"写满,这时候不能再执行新的操作,需要停下来先擦除记录。

有了redo log,InnoDB就可以保证即使数据库异常重启,之前提交的记录也不会丢失,这个能力被称为crash-safe。可理解为:只要赊账记录在粉板上或写在账本上,即便停业几天后,依然可以通过账本和粉板上的数据明确赊账账目。

重要的日志模块:bin log

redo log是InnoDB引擎特有的日志,而Server层也有自己的日志——bin log(归档日志)

最初MYSQL并没有InnoDB,它自带的引擎是MyISAM,但是MyISAM没有crash-safe的能力,bin log只能用于归档,而InnoDB是以插件的形式引入MYSQL的,既然只依靠bin log不能实现crash-safe,所以InnoDB使用了redo log实现了crash-safe。

bin log和redo log区别:

  1. redo log是InnoDB特有的,bin log是MySQL的Server层实现的,所有引擎都可以使用。
  2. redo log是物理日志,记录的是"某个数据页上做了什么修改",bin log是逻辑日志,记录的是整个语句的原始逻辑,例如"给ID=2这一行的c字段+1"
  3. redo log是循环写的,空间固定会用完,bin log是追加写的,也就是bin log文件写到一定大小会切换到下一个,不会覆盖之前的日志。

有了这两个日志的概念后,再回顾update语句的内部流程:

  1. 执行器先找引擎ID=2这一行,ID是主键,引擎直接用tree search找到这一行。如果ID=2这一行所在的数据原本就在内存中,就直接返回给执行器,否则从磁盘读取到内存后再返回。
  2. 执行器拿到引擎给的行数据,把这个值+1,得到一个新的行数据,再调用引擎接口写入这行新数据。
  3. 引擎将这行的新数据更新到内存,同时将这个更新记录到redo log,此时redo log处于prepare状态,告知执行器执行完成,随时可以提交事务。
  4. 执行器生成这个操作的bin log,并把bin log写入磁盘
  5. 执行器调用引擎的提交事务接口,引擎把刚写入的redo log改成提交(commit)状态,更新完成。

在这里插入图片描述
最后三步,将redo log写入拆分为两个步骤:prepare和commit,这就是两阶段提交。

两阶段提交

两阶段提交是为了让两份日志之间的逻辑一致。

之前有说过,binlog会记录所有的逻辑操作,并采用追加写的方式。如果你的DBA承诺说半个月内可以让数据库恢复到这个期间任何一秒的状态,那么备份系统中一定保存了半个月内所有的bin log,同时系统会定期做整库备份。

当需要恢复到指定的某一秒,例如某天下午2点发现中午12点有一次误删表,需要找回数据:

  • 首先,找到最近一次的全量备份,从这个备份恢复到临时库。
  • 然后,从备份的时间开始,将备份的bin log依次取出,重放到12点误删表之前的那个时刻。

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

那么为什么日志需要两阶段提交呢?
由于redo log和bin log是两个独立的逻辑,如果不采用两阶段提交,那么就是先写完redo log再写bin log,或者采用相反顺序。那么这两种方式会出现什么问题呢?

假设当前ID=2的行,字段c的值为0,执行update语句过程中,在写完第一个日志后,第二个日志还没有写完期间发生了crash,会出现什么情况。

  1. 先写redo log再写bin log:假设redo log写完,bin log还没写完的期间,MYSQL异常重启,redo log写完后,系统即使崩溃,仍然能恢复数据,所以恢复后的这一行c的值为1。
    但是由于bin log没有写完就crash了,这时候bin log里就没有记录这个语句,因此,之后备份日志的时候,存起来的bin log里就没有这条语句。
    如果需要用这个bin log恢复临时库的话,这个语句的bin log丢失,这个临时库就会少一次更新,恢复出来的这一行c的值就是0,与原库不同。

  2. 先写bin log再写redo log:如果bin log写完后crash,由于redo log没写,崩溃后恢复这个事务无效,所以这一行c的值为0。但是bin log里已经记录了"把c从0改成1"这个日志,所以,在之后用bin log恢复临时库的时候就多出了一个事务,恢复出来的c的值就是1,与原库不同。

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

简单来说,redo log和bin log都可以用于表示事务的提交状态,而两阶段提交就是让两个状态保持逻辑上的一致。

参考文章:https://time.geekbang.org/column/article/115538

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在信号处理领域,DOA(Direction of Arrival)估计是一项关键技术,主要用于确定多个信号源到达接收阵列的方向。本文将详细探讨三种ESPRIT(Estimation of Signal Parameters via Rotational Invariance Techniques)算法在DOA估计中的实现,以及它们在MATLAB环境中的具体应用。 ESPRIT算法是由Paul Kailath等人于1986年提出的,其核心思想是利用阵列数据的旋转不变性来估计信号源的角度。这种算法相比传统的 MUSIC(Multiple Signal Classification)算法具有较低的计算复杂度,且无需进行特征值分解,因此在实际应用中颇具优势。 1. 普通ESPRIT算法 普通ESPRIT算法分为两个主要步骤:构造等效旋转不变系统和估计角度。通过空间平移(如延时)构建两个子阵列,使得它们之间的关系具有旋转不变性。然后,通过对子阵列数据进行最小二乘拟合,可以得到信号源的角频率估计,进一步转换为DOA估计。 2. 常规ESPRIT算法实现 在描述中提到的`common_esprit_method1.m`和`common_esprit_method2.m`是两种不同的普通ESPRIT算法实现。它们可能在实现细节上略有差异,比如选择子阵列的方式、参数估计的策略等。MATLAB代码通常会包含预处理步骤(如数据归一化)、子阵列构造、旋转不变性矩阵的建立、最小二乘估计等部分。通过运行这两个文件,可以比较它们在估计精度和计算效率上的异同。 3. TLS_ESPRIT算法 TLS(Total Least Squares)ESPRIT是对普通ESPRIT的优化,它考虑了数据噪声的影响,提高了估计的稳健性。在TLS_ESPRIT算法中,不假设数据噪声是高斯白噪声,而是采用总最小二乘准则来拟合数据。这使得算法在噪声环境下表现更优。`TLS_esprit.m`文件应该包含了TLS_ESPRIT算法的完整实现,包括TLS估计的步骤和旋转不变性矩阵的改进处理。 在实际应用中,选择合适的ESPRIT变体取决于系统条件,例如噪声水平、信号质量以及计算资源。通过MATLAB实现,研究者和工程师可以方便地比较不同算法的效果,并根据需要进行调整和优化。同时,这些代码也为教学和学习DOA估计提供了一个直观的平台,有助于深入理解ESPRIT算法的工作原理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值