From 0eb36e5a9f04061456a28401d01c6639b867d2e0 Mon Sep 17 00:00:00 2001 From: Navyum <36869790+Navyum@users.noreply.github.com> Date: Fri, 17 May 2024 16:51:26 +0800 Subject: [PATCH 1/3] Update how_update.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 依据: 1. 如果先更新内存记录,此时断电就不是crash-safe的 2. flush 链表的脏页需要保存redo log对应的lsn值,用来做checkpoint操作,所以先有redo log写入,再有脏页 --- mysql/log/how_update.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mysql/log/how_update.md b/mysql/log/how_update.md index 94060d81..ab2160f8 100644 --- a/mysql/log/how_update.md +++ b/mysql/log/how_update.md @@ -597,7 +597,7 @@ commit 阶段队列的作用是承接 sync 阶段的事务,完成最后的引 - 如果一样的话就不进行后续更新流程; - 如果不一样的话就把更新前的记录和更新后的记录都当作参数传给 InnoDB 层,让 InnoDB 真正的执行更新记录的操作; 3. 开启事务,InnoDB 层更新记录前,首先要记录相应的 undo log,因为这是更新操作,需要把被更新的列的旧值记下来,也就是要生成一条 undo log,undo log 会写入 Buffer Pool 中的 Undo 页面,不过在内存修改该 Undo 页面后,需要记录对应的 redo log。 -4. InnoDB 层开始更新记录,会先更新内存(同时标记为脏页),然后将记录写到 redo log 里面,这个时候更新就算完成了。为了减少磁盘 I/O,不会立即将脏页写入磁盘,后续由后台线程选择一个合适的时机将脏页写入到磁盘。这就是 **WAL 技术**,MySQL 的写操作并不是立刻写到磁盘上,而是先写 redo 日志,然后在合适的时间再将修改的行数据写到磁盘上。 +4. InnoDB 层开始更新记录,先生成对应redo log,并存入redo log buffer里面,当事务提交时,将redo log写入redo log file,并更新buffer pool中的数据页,将其放入flush 链表并标记脏页和记录redo log对应的lsn到该页的oldest_modification,这个时候更新就算完成了。为了减少磁盘 I/O,不会立即将脏页写入磁盘,后续由后台线程选择一个合适的时机将脏页写入到磁盘。这就是 **WAL 技术**,MySQL 的写操作并不是立刻写到磁盘上,而是先写 redo 日志,然后在合适的时间再将修改的行数据写到磁盘上。 5. 至此,一条记录更新完了。 6. 在一条更新语句执行完成后,然后开始记录该语句对应的 binlog,此时记录的 binlog 会被保存到 binlog cache,并没有刷新到硬盘上的 binlog 文件,在事务提交时才会统一将该事务运行过程中的所有 binlog 刷新到硬盘。 7. 事务提交(为了方便说明,这里不说组提交的过程,只说两阶段提交): From d9e2e6bbd5451f537b9927091f45f6b522eb04fd Mon Sep 17 00:00:00 2001 From: Navyum <36869790+Navyum@users.noreply.github.com> Date: Tue, 21 May 2024 16:35:54 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E8=B7=B3=E8=A1=A8=E8=B7=B3=E8=BD=AC?= =?UTF-8?q?=E8=A7=84=E5=88=99=E6=8F=8F=E8=BF=B0=E4=B8=8D=E6=B8=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 依据下面的例子: - 「元素:abc,权重:3」节点的 leve[1] 的下一个指针指向了「元素:abcde,权重:4」的节点,然后将其和要查找的节点比较。虽然「元素:abcde,权重:4」的节点的权重和要查找的权重相同,但是当前节点的 SDS 类型数据「大于」要查找的数据,所以会继续跳到「元素:abc,权重:3」节点的下一层去找,也就是 leve[0]; 说明:abcde的SDS不满足,权重满足,如果直接到该节点的下一层level0,已经没有后续节点,正确结果无法找到。 --- redis/data_struct/data_struct.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/redis/data_struct/data_struct.md b/redis/data_struct/data_struct.md index daefd026..efdb70f3 100644 --- a/redis/data_struct/data_struct.md +++ b/redis/data_struct/data_struct.md @@ -807,7 +807,7 @@ typedef struct zskiplist { - 如果当前节点的权重「小于」要查找的权重时,跳表就会访问该层上的下一个节点。 - 如果当前节点的权重「等于」要查找的权重时,并且当前节点的 SDS 类型数据「小于」要查找的数据时,跳表就会访问该层上的下一个节点。 -如果上面两个条件都不满足,或者下一个节点为空时,跳表就会使用目前遍历到的节点的 level 数组里的下一层指针,然后沿着下一层指针继续查找,这就相当于跳到了下一层接着查找。 +如果上面两个条件都不满足,或者下一个节点为空时,需要先根据当前节点的*backward 后向指针跳回前一个节点,使用该节点的 level 数组里的下一层指针,然后沿着下一层指针继续查找,这就相当于跳到了下一层接着查找。 举个例子,下图有个 3 层级的跳表。 From 0e54e9045e07a9afad0350c21c56dfa2ae762978 Mon Sep 17 00:00:00 2001 From: Navyum <36869790+Navyum@users.noreply.github.com> Date: Tue, 21 May 2024 17:44:49 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=E8=B7=B3=E8=A1=A8=E8=B7=B3=E8=BD=AC?= =?UTF-8?q?=E9=80=BB=E8=BE=91=E6=8F=8F=E8=BF=B0=E4=B8=8D=E6=B8=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 参考源码: 循环中比较时,实际比的是当前节点的forward指针对应的节点的score和ele ``` for (i = zsl->level-1; i >= 0; i--) { /* store rank that is crossed to reach the insert position */ rank[i] = i == (zsl->level-1) ? 0 : rank[i+1]; while (x->level[i].forward && (x->level[i].forward->score < score || (x->level[i].forward->score == score && sdscmp(x->level[i].forward->ele,ele) < 0))) { x = x->level[i].forward; } update[i] = x; } ``` --- redis/data_struct/data_struct.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/redis/data_struct/data_struct.md b/redis/data_struct/data_struct.md index efdb70f3..92ca2dcc 100644 --- a/redis/data_struct/data_struct.md +++ b/redis/data_struct/data_struct.md @@ -804,10 +804,10 @@ typedef struct zskiplist { 查找一个跳表节点的过程时,跳表会从头节点的最高层开始,逐一遍历每一层。在遍历某一层的跳表节点时,会用跳表节点中的 SDS 类型的元素和元素的权重来进行判断,共有两个判断条件: -- 如果当前节点的权重「小于」要查找的权重时,跳表就会访问该层上的下一个节点。 -- 如果当前节点的权重「等于」要查找的权重时,并且当前节点的 SDS 类型数据「小于」要查找的数据时,跳表就会访问该层上的下一个节点。 +- 如果当前节点指向的下一个节点(forward指向的节点)的权重「小于」要查找的权重时,跳表就会访问该层上的下一个节点。 +- 如果当前节点指向的下一个节点(forward指向的节点)的权重「等于」要查找的权重时,并且下一个节点的 SDS 类型数据「小于」要查找的数据时,跳表就会访问该层上的下一个节点。 -如果上面两个条件都不满足,或者下一个节点为空时,需要先根据当前节点的*backward 后向指针跳回前一个节点,使用该节点的 level 数组里的下一层指针,然后沿着下一层指针继续查找,这就相当于跳到了下一层接着查找。 +如果上面两个条件都不满足,或者下一个节点为空时,跳表就会使用目前遍历到的节点的 level 数组里的下一层指针,然后沿着下一层指针继续查找,这就相当于跳到了下一层接着查找。 举个例子,下图有个 3 层级的跳表。