mysql我们最常用的操作,无非是查询、更新和新增记录,那么mysql关于这些操作,从架构设计到底层数据结构,都做了什么呢?
mysql分为server层和存储引擎层,Server 层包括连接器、查询缓存、分析器、优化器、执行器等,涵盖 MySQL 的大多数核心服务功能,以及所有的内置函数(如日期、时间、数学和加密函数等),所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图等。
而存储引擎层负责数据的存储和提取。其架构模式是插件式的,支持 InnoDB、MyISAM、Memory 等多个存储引擎。现在最常用的存储引擎是 InnoDB,它从 MySQL 5.5.5 版本开始成为了默认存储引擎。我们接下来的讨论皆是在innoDB的前提下。
关于查询,我们知道索引的出现,可以帮助我们快速定位到我们所需的数据,提高我们的读写效率,索引的实现有很多种,
比如哈希表,有序数组,搜索树等等,哈希表我们可以通过对特定的hash函数把key转化为一个确定的位置,从而迅速查找到我们所需的数据,
不可避免的由于hash冲突的存在,导致不同的key出现同一个值的情况,进而出现链表以解决这种情况
由于数据的无序,所以当出现范围查询的需求时,hash表的表现并不尽如人意,仅适用于等值查询的情况
有序数组的等值查询和范围查询都非常优秀,可以通过二分法迅速找到所需数据,如果是范围查询,由于是有序的,所以只需继续往后或
往前遍历即可,但是由于我们的数据库,是会经常发生数据的更新的,有序数组的更新,需要挪动改动位置后的所有数据,成本太高。
二叉树的特征是每个节点大于左节点而小于右节点,查询和更新都很快。然而因为索引不仅写在内存中,还需要写在磁盘中,在机械硬盘时代,从磁盘随机读一个数据块需要 10 ms 左右的寻址时间。也就是说,对于一个 100 万行的表,如果使用二叉树来存储,单独访问一个行可能需要 20 个 10 ms 的时间,由于二叉树只有2个分叉,导致这棵树会长得很高进而导致我们的查询次数增多,所以N叉树应运而生,假如N是1000,那么
一颗高为4的树,就能够容纳1000的3次方,10亿的数据,查询一个值访问磁盘的次数最多也就3次(因为根节点一般在内存中常驻)
N 叉树由于在读写上的性能优点,以及适配磁盘的访问模式,已经被广泛应用在数据库引擎中了
不管是哈希还是有序数组,或者 N 叉树,它们都是不断迭代、不断优化的产物或者解决方案。数据库技术发展到今天,跳表、LSM 树等数据结构也被用于引擎设计中,这里我就不再一一展开了。
接下来我们探讨一下innodb关于数据更新做了什么优化。
在我们小时候,我们去村里的小卖部,经常会看到小卖部里面有一块小黑板,上面记录了某某某年某月某日赊了多少钱的账,你想想,小卖部
一般就店老板夫妻两个人,最多孩子放假的时候来帮忙,每天的买卖高峰期是很忙的,如果某个人来赊账了,是不可能临时跑到楼上的房间从
层层被褥中找出那本小账本,然后再一页一页翻出那个人的欠账历史记录再去算出加上今天他总共欠我多少钱,太慢了,通常的做法是立马在
小黑板随手记一笔某某的欠账记录,等到夜深人静关店的时候,再拿出小账本对照小黑板更新一波赊账记录,
映射到mysql,这叫做WAL技术,Write-Ahead Logging,它的关键点就是先写日志,再写磁盘,也就是先写小黑板,等不忙的时候再写账本。
因为磁盘的随机读写,是非常慢的,相较于内存的读写,速度上有几十万倍的差距,我们不可能慢吞吞的去磁盘找到那条数据然后再更新,
mysql中的小黑板,叫做redolog,所以总而言之,mysql关于数据更新是这样做的,先在内存中的一块叫做redolog的缓冲区中先记录
‘某某记录修改成为了什么‘,从逻辑上理解,我们可以把redolog当成一个环,有两个指针,一个指向写到哪了,一个指向更新到哪了,
mysql后台再慢慢的把缓冲区里面的东西更新到磁盘及redolog的物理日志,redolog还可以和binlog通过
两阶段提交从而达到crash-safe的能力,保证在数据库突然挂掉的情况下恢复数据,这里就不拓展了。
无独有偶,mysql关于数据更新还有个小优化,我们知道索引有唯一索引和非唯一索引,在更新上的区别就是,更新唯一索引需要比
更新非唯一索引多一个步骤,就是判断我更新之后的唯一性。对于普通索引而言,我们可以通过chage buffer从而提升我们的更新效率,
通常的更新流程这这样的,我们需要去磁盘里把数据读到内存,再做更新,然后再写回到磁盘,由前面可知,磁盘的操作是数据库中成本最高的操作之一,会大大降低我们的效率,所以我们要尽可能避免磁盘的操作,change buffer是一块专门用来记录数据变更的内存,当需要更新
某条记录时,只需在change buffer中记录即可,当change buffer存到一定量或者某条只存在change buffer不在磁盘中的数据被访问到
时,需要通过merge操作完成真正的数据更新。
因为 merge 的时候是真正进行数据更新的时刻,而 change buffer 的主要目的就是将记录的变更动作缓存下来,所以在一个数据页做 merge 之前,change buffer 记录的变更越多(也就是这个页面上要更新的次数越多),收益就越大。
因此,对于写多读少的业务来说,页面在写完以后马上被访问到的概率比较小,此时 change buffer 的使用效果最好。这种业务模型常见的就是账单类、日志类的系统。