多情键盘无情键对《MySQL内核:InnoDB存储引擎 卷1》的笔记(5)

多情键盘无情键
多情键盘无情键 (四处野鸭和菱藕,秋收满畈稻谷香)

读过 MySQL内核:InnoDB存储引擎 卷1

MySQL内核:InnoDB存储引擎 卷1
  • 书名: MySQL内核:InnoDB存储引擎 卷1
  • 作者: 姜承尧/蒋鸿翔/饶珑辉/温正湖
  • 页数: 360页
  • 出版社: 电子工业出版社
  • 出版年: 2014-5
  • 第7页 第一章 概览

    作者在本书中选择的是 InnoDB 3.23.49 版本, 估计是 3.x 的最后一个稳定版. 对比了代码行数, 3.23.49是10万行, 4.x到11万, 5.5到20万行, 5.6以后突然变到了30万行. 之所以选择早期版本是因为 InnoDB 框架并没有太大改变, 后面的几十万行代码只是增量操作. 看到10万行这个数字我不禁湿了, 信心爆棚, 才10万啊. 知乎上有个人说, MySQL 设计并不好. 从5.5以后突然增加了许多代码, 也算是一个佐证吧. 我猜 Oracle 收购之后, 进行了大量的增强. 这十万行代码除了功能的增加之外, 肯定还有相当一部分是优化实现. 不过 MySQL server 的设计我觉得还不错. 一般的网络库都力求 scalable, 最好是能加一倍的 CPU 就增加一倍的性能, 事实上这个目标也有很多近似达到了. 所以好的网络库设计都是每个线程负责单一功能, 互相之间没有数据交换. 网络库的性能瓶颈在 CPU, 存储库的瓶颈在存储介质. 本质上来说, 内存是个随机访问的介质, 而硬盘是顺序访问的. SSD 硬盘好像有技术上的革新, 不太了解. 所以有一个 master 线程来调度各个任务是非常好的一个设计, 到了多核心时代也能很好的适应. 作者说, 似乎一夜之间不了解点内核实现都不好意思和别人说你是搞 MySQL 数据库的. 这真是一个装逼大于实际的趋势啊. 我觉得 DBA 这个职位, 还是应该把重点放在 SQL 优化上, 毕竟底层实现的优化不会改变优化效果, 除非出现了新技术比如 SSD 硬盘, 才需要关注. 作者讲解的方式是自下而上. 最底层的有三个模块, Common Utility 是基础数据结构, Concurrency Manager 实现了各种锁, File Manager 实现了各种文件操作. 中间内容分为 Transaction Manager, Lock Manager, Buffer Manager, Storage Manager, Resource Manager, B+ Tree Index, Insert Buffer. 这是 InnoDB 的核心部分. 最上层的三个模块是 Row/Cursor, MySQL Handler API, Embedded API. 这都是跟上层的调用者交换数据用的接口. 其实我还是喜欢从上而下的讲解方法, 先有一个整体的概念, 后面的学习不断地重复加深. 不过这种自下而上的说法对初学者更友好一些, 提前学习了一些基础概念, 后面不会打断学习过程. 看了目录和简介, 似乎本书把内容已经讲完了啊. 第二卷想说什么?

    2014-09-16 19:23:52 1人推荐 回应
  • 第22页 第二章 基本数据结构与算法

    本章内容属于 Common Utility 这个模块. 总共介绍了四个数据结构. 第一个是内存管理, 按包含关系分为 mem_pool_struct, mem_area_struct, mem_block_t 三级. 其中 mem_pool_struct 来源有两个, 要不使用操作系统的 malloc, 此时每次申请一页内存; 要么使用自己的 mem_pool_create, 大小可变. pool 中没有其他的管理功能, 就是一大块连续的内存. 每个 pool 中连接了可变个 mem_area_struct, 这些 area 使用双链表连接起来, 其大小分别为 2^0, 2^1, 2^2 ... 使用 buddy system 算法管理. 申请以后的数据为 block 数据结构. 其在 area 中的分布是栈式, 申请从栈顶开始, 释放也只能从栈顶开始. 第二个是哈希表, 标准哈希表, 不表. 第三个是双链表, 内存双链表是标准双链表,不表; 磁盘双链表因为磁盘不能随机访问的关系, 将指针变换为 page 和 offset 两个量来表示. 操作上有一点点不同. 第四个是动态数组和排序, 没有细讲. 三个思考题, 第一个问内存链表和磁盘链表的关系. 第二个问哈希表的实现为什么是这样的. 第三个问和 Linux 的 buddy system 有什么不同. 前面三个数据结构实现都有一些小的异常, 比如内存管理中有内存大小限制, 如果是数据字典或者自适应索引的话, 内存需要添加一个额外的内存块, 因为超过了此处的限制. 哈希表典型实现是一个 table 结构, 里面只有一个指针, 指向 item 的数组. 每个使用 hash table 的结构都在其数据结构中包含一个 item 结构. 但有时为了内存效率, 有的结构也不包含 item, 此时则需要 table 中有额外的结构来实现这个功能. 双链表的异常则是磁盘的特殊关系. 本书的讲解方式有两个问题, 一是翻译不清楚, 书中一会儿使用结构在代码中的名字比如 mem_pool_struct, 一会儿使用翻译的名字比如内存池. 有些名词没有翻译好的话看着就很痛苦; 第二个是前面说的, 从下到上的分析方法对初学者友好, 但很多问题就不能很好的解释. 比如此处的三个结构为什么这样实现, 都要有一些小的异常. 我连数据字典和自适应索引是什么都不知道, 我哪知道为什么这么实现? 本章涉及的代码有 8000 多行, 都是基本的实现代码, 以后再看吧.

    2014-09-16 21:32:59 回应
  • 第40页 第三章 同步机制

    这绝对是我学计算机以来最烦的一部分内容: 你学吧, 其实没什么用, 这是科学家干的事情. 你不学吧, 少了一个重要的吹牛的资本. 以前是三过其门而不入, 现在是, 虽九浅而不得一深. 本章讲 InnoDB 中锁的实现. 其中使用了相关的各类技术. 不说了. = =||| 参考资料如下, 不定期更新: 1. Weak ordering - a new definition 2. Memory Consistency Models 3. Myths About the Mutual Exclusion Problem 4. Access ordering and coherence in shared-memory multi-processors 5. System Deadlocks 6. http://developer.android.com/training/articles/smp.html 7. http://lwn.net/Articles/250967/ 8. http://segmentfault.com/a/1190000000381287

    2014-09-16 23:18:13 2回应
  • 第97页 第四章, 第五章, 第六章

    看到这儿我要承认, 我还是低估了这本书, 比起前作, 本书进步斐然. 除了行文略有滞纳以外, 结构分析均臻上乘. 本章讲解重做日志部分, 涉及代码约 7000 行, 加上下一章涉及的约 2000 行, 此时分析的代码已经达到 20000 行. 重做日志采用先写日志的方式, 数据提交之前先将日志写到持久存储中. 这样即使发生宕机, 数据可能来不及刷新, 日志可以保证完整恢复. 重做日志支持组提交, 这样多个事务的性能会有很大提高. 恢复时从最早向最近恢复, 后面思考题中问这个方式有什么优化空间. 我想可以从最近向最早恢复, 这样开始之后, 数据库就可以开始服务了, 后面异步的恢复不受影响. (如果有操作涉及到最早的数据, 则将最早数据加载至内存一份, 修改后形成脏页) 两个问题, 一是 InnoDB 支持在线热备, 其原理尚不清楚. 二是有一个概念叫做物理逻辑日志, 它的概念是 physical to a page, logical within a page. 另外 InnoDB 的单点其实还是不少, 比如 LSN 保存在日志文件头, 如果损坏的话, 则重做日志就要从0开始了, 也不能保证数据一致性. 第二个是重做日志的数据部分没有校验, 如果页内损坏的话, InnoDB 完全不能知道, 加上一个类似 TCP/UDP 的校验和最好了. 第五章 mini-transaction 本章讲述上面提到的物理逻辑日志, 不过看完我还是没有明白. InnoDB 修改一个页的逻辑是, 首先锁住该页, 然后修改内容, 然后生成日志, 最后解锁该页. 在生成重做日志的时候, 程序会锁住相关的重做日志页, 这里不存在死锁问题是因为重做日志本身由 log_sys->mutex 保护, 这个过程本身是线性的. 以上就是 mtr 的三个原则: FIX, 修改之前锁住该页; WAL, 日志先写; Force-log-at-commit, 提交之前写日志. 此处的页内逻辑写的意思是, 此处重做日志并不是保存了数据操作的信息, 而是前一个记录位置和与前一条记录的差异信息. 所以只凭本条重做日志无法恢复数据. 更多在 11 章讲述. 思考题中的如果将 log_sys->mutex 锁释放掉, 实现重做日志的并行化, 我目前还没有什么好的思路. 第六章 存储管理 参见: http://blog.jcole.us/innodb/ man pread

    2014-09-17 15:08:16 回应
  • 第172页 第九章 锁

    第三章讲了锁的实现的技术, 本章则描述锁的使用场景. InnoDB 支持行级锁, 不过其实现方式则是为每一个页创建一个锁变量, 将每一行的信息在用 bitmap 在其中表示. 这样兼顾了性能和内存开销, 做到一个很好的平衡. 本章花了很大的篇幅来讲述隐式锁的场景. 所谓隐式锁, 其实就是逻辑上应该存在的锁, 在不需要的时候就可以不出现, 节省资源. InnoDB 本质上来说是一个多重 KV 数据库, 主键是 key, 其他的列合起来是 value. 为了性能上的考虑, 从其他列到主键的影射也必须存在. 即 value 的一部分到 key 的影射. 由于其加锁都是在 key 上的, 所以针对其他列的很多操作就产生了隐式锁. 其他列的操作需要首先将本列数据映射到主键, 然后加锁, 然后本列自身加锁. 这其中又可以分为添加, 删除, 修改等几个操作, 书中分别作了详细的讨论. 行锁的维护中比较重要的有三种情况, 一是本行更新时, 如果更新数据占的空间大于原来的数据, 则 InnoDB 会选择删除本行数据, 重新添加一行新数据. 然而这种删除并非立即删除, 由一个后台的线程定时删除. 所以会出现有同时存在两个相同主键记录的情况, 其中一个标记了 delete 标志. 这时如果上锁, 则会将两个记录都加锁, 会出现不必要的等待. 第二类比较复杂的维护行锁的情况是数据页的分裂和合并, 由于 InnoDB 的锁变量是以页为单位, 那么页上的数据变化时对应的本页的锁也应该有相应的变化. 当页分裂时将分裂出去的数据对应的锁移到新页中去. 当页合并时将两页连接处的锁进行适应的合并. 最后一个小节讲死锁, 在两个线程以不同顺序请求多个资源时死锁会发生. 比如线程 A 请求 sA, sB, 线程 B 请求 sB, sA. 当线程 A 得到 sA 而线程 B 得到 sB 时, 两个线程将进入循环等待, 即死锁. 这种情况从技术上无法避免, 所以需要死锁的检测技术来拆开循环等待的情况. 目前主流的技术是两个, 一个是超时等待, 某个线程等待锁超时则定性为死锁; 其二是建立锁的等待图, 如果图中存在回路则必然存在死锁. 从实现上看, 第一种方法实现简单, 不容易出现 bug, 但是不准确; 而第二种方法结果精确, 实现比较困难, 同时也增加了资源的开销.

    2014-09-18 14:42:49 回应