运维日记|MySQL/Oracle深度解析之一:逻辑读
mhr18 2024-09-17 23:08 20 浏览 0 评论
前言
THE FIRST
比较数据库优劣、异同的文章有很多了,使用压测工具,进行不同压力下的测试,就能大致上比较出来哪种数据库是“最快”的数据库。但从有经验的数据架构、DBA等专业人士角度,仅仅“最快”是不够的,数据库是十分复杂的体系,要考虑方方面面的问题。
本文从“竞争”的角度,使用调试技术,在MySQL和Oracle的代码世界中畅游,像使用显微镜一样,以“最近”的距离,分析两种数据库由于代码设计的不同,而造成的竞争与锁机制的异同。
数据库的竞争与锁机制,是数据库最为复杂的部分,不可能在一篇短短的文章中描述完,本文以“逻辑读”为样本,从细枝末节中对比MySQL、Oracle的优劣、异同。逻辑读是数据库最基础的操作,也是最繁忙的操作。
我们先从MySQL入手,这里的研究对象当然是MySQL的InnoDB引擎。其他常用引擎比如Myisam根本没有缓存,没有数据库层面的逻辑读。
再说一下调试技术,它不同于直接一上来就开始的源码阅读,调试技术重点在于要读“活”着的源码。我们不去看磁盘文件中静止的.c、.cpp、.h等文件,而是要把程序运行起来,再结合源码,观察它运行起来的行为,理解源码其中的细枝末节。
有了对细节的探索,可以像他一样:
否则,只能像他一样:
第一节
基础知识介绍:MySQL中的HASH表与CBC Latch
MySQL的逻辑读,网上有相关函数,从buf_page_get_gen函数开始,一直到MySQL8都是这样。不过相比5.7,这个函数在MySQL8中有了很大的改动。我们后面的所有分析,都是针对MySQL 8。
数据页或块的缓存,在MySQL中称为Buffer Pool,在Oracle中称为Buffer Cache,下面统一简称数据缓存。无论MySQL还是Oracle,数据缓存的管理方式类似,如下图:
其实不只MySQL、Oracle,此图适用于所有主流关系型数据库的数据缓存管理。
在MySQL中,HASH表由hash_cell_t型的结构组成,每一个Bucket,都是一个hash_cell_t型的结构变量。hash_cell_t的定义如下:
plugin/innodb_memcached/innodb_memcache/include/innodb_utility.h的126行中你可以找到它的定义。
我建议你打开它看一看,这样,你也是读过MySQL源码的人了,酷不酷。
每一个HASH Bucket都是一个void型的指针,也就是内存地址。在64位系统中,占8字节:
每一个HASH Bucket后,都是一个链表,就是如下的部分:
Oracle中称其为Cache Buffer Chains,简称CBC链。MySQL中没有专门的称呼,也称为CBC链吧,反正和Oracle都是一样的东西。
MySQL中,链表中的每个Node,都是buf_page_t型的类,它的定义在storage/innobase/include/buf0buf.h的1156行,这个就比较复杂了,如果不准备入调试技术的坑,只是看个热闹,我就不建议打开看了。它主要包含文件号、页号、下一个Node的地址、Buffer地址等成员变量。
数据库在HASH Table中搜索Buffer Pool中某个Buffer的过程,我们就不详细说了,有过很多资料讲述这一过程。MySQL中的相关代码在torage/innobase/include/buf0buf.ic的948行buf_page_hash_get_low函数中,代码量并不算复杂,读一下可以使你更拉轰。Oracle中参考《Oracle内核技术揭密》吧,第三章专门讲这个。
无论MySQL、Oracle,在HASH Table中搜索Buffer的过程都需要锁的保护。Oracle中称这个锁为Cache Buffer Chains Latch,简称CBC Latch,MySQL中也没有专门的称呼,我们也称它为MySQL的CBC Latch吧。
好了,基础知识介绍完毕。下面进入本次分享的最主要内容了,近距离分析MySQL、Oracle的异同。下面先从CBC Latch的数量开始。
第二节
MySQL CBC Latch数量造成的竞争
Oracle中CBC Latch数量是很多的(由隐藏参数“_db_block_hash_buckets”计算得到),缺省情况下根据你所设定的Buffer cache大小设定,至少几千个。Buffer Cache越大,CBC Latch数量也越多。
MySQL中呢,CBC Latch的数量就少的可怜了,16个(准确来说,是一个Buffer Pool Instance 16个。一个Buffer Pool Instance就是一个Buffer Pool的子池)。而且,这个数字并不会随着Buffer Pool加大而增大。再大的Buffer Pool,它也是16个。
我猜测,可能是MySQL逻辑读时都是以共享方式持有CBC Latch。因此,MySQL的开发者认为CBC Latch的竞争不会太激烈,所以CBC Latch数量不必太多。
虽然逻辑读都是以共享的模式持有CBC Latch,但物理读可就要以独占方式持有CBC Latch了。如果只有16个CBC Latch的话,可以想像,在物理I/O比较多时,竞争一定是激烈的。
做个测试,验证一下逻辑读、物理读时CBC Latch的阻塞情况。测试非常简单,因为CBC Latch锁只有16个,只要找17个页,这17个页中,必然有两个页共用一个CBC Latch。
我使用下面的存储过程,向一个表中插入了50行。每一行都占6000字以上,50行数据,使用的页数量绝对在17个以上,一定有两个页用同一个CBC Latch保护。
可以得到T1表所有页的HASH值,HASH值相同的,就是使用同一个CBC Latch的页。在我的测试环境,显示的结果id1为1行所在页,和id1为34的行所在页,共用一个CBC Latch,测试过程如下图:
简单说一下这个测试,先开始一个逻辑读(查询多次id1=1的页),使用gdb,当线程获得id1=1页上的CBC Latch后停住(也就是在执行完pfs_rw_lock_s_lock_func()函数后停住)。
接下来再开启一个连接,查询id1=34的行。Id1=34的行所在页之前没被读过,它是一次物理读,它会被前面的逻辑读阻塞。然后我们可以慢慢观察MySQL出现CBC Latch阻塞后的情况。首先查看show processlist中的状态:
红框中的Session,正在执行id1=34的查询,它已经被阻塞了。但你无法得知它被阻塞,它的状态还是Query和Statistics。Oracle中可不是这样,你可以在v$session中看到Session的准确状态,是WAITING、WAITED SHORT TIME和ACTIVE、INACTIVE。
MySQL中除了show processlist,还可以在show engine innodb status中看到更多信息:
在“show engine innodb status”中可以看到CBC Latch出现阻塞。上图红框中的信息说明有一个线程已经被阻塞了,它在等待X-LOCK锁。
但是show engine innodb status的结果不是标准的二维表,而且这里只显示有阻塞,阻塞的次数、时间都不知道。而且CBC Latch的持有时间都是很快的,多了一小会儿竞争时间,你也感觉不到。可能为让一次毫秒级耗时的逻辑读,增加几十、或几百微秒。
Oracle中可以SQL统计计算CBC Latch被调用的总次数、阻塞次数、阻塞时间、……等等信息。通过这些信息,美创运维中心中有经验的DBA一眼就可以看出来,CBC Latch是否竞争过于激烈。
我们还回到CBC Latch的数量问题上来。我们可以得到一点结论了,在MySQL中,因为只有16个CBC Latch锁,平均17个页会有两个页共用一个CBC Latch锁,也就是说平均17次物理读,会有一次CBC Latch竞争。
我们展开说一下,MySQL其实不是SSD友好型数据库。SSD大大提高了物理读的速度,但由于CBC Latch数量过少,在出现物理读时,较容易引发阻塞。
而且你很难察觉这些阻塞。它只是多占了点CPU、让你本来可以更快的SQL,慢了一点。至于慢了多少就很难说了,我粗略的说一下,物理读稍多的情况下有大概5%到10%的性能损耗。有没有可能解决这种问题呢?
当然有,就是增加CBC Latch的数量。当你使用了SSD、特别是高性能的pci-e槽的SSD时,一定要记得增加CBC Latch的数量。
MySQL使用参数“innodb_page_hash_locks”控制CBC Latch的数量,该参数的缺省值是16。修改它也是很简单的事。但是,等等,为什么从来没听人说过要修改这个参数呢?网上搜索了一下,也鲜有针对这个参数的介绍:
你可以换种搜索引擎,反正我用某度搜索“innodb_page_hash_locks”,啥都没搜到。并不是它不重要,刚才我们说了,是它造成的竞争很难察觉,大家不知道而已。
那么这个参数具体应该设为什么值呢?这要结合你的系统、主机CPU/内存等来确定。也可以参照Oracle中CBC Latch的数量,这可是经过了无数大厂验证过的值。或者,我建议你找美创科技的专业运维团队,为你的系统进行健康检查后,再决定是否需要修改、如何修改此参数的值。
如果你的Oracle数据库,遇到了CBC Latch或其他情况的竞争,当然也可以找我们,记住一句口号:
“有情况,找美创”
接下来我们再来看看访问Buffer页时的情况。
第三节
MySQL与Oracle的Buffer Pin Lock比较
搜索CBC链、找到目标Buffer之后,就要对页进行访问了。访问页也需要在锁的保护下进行,这个锁在Oracle中,称为Buffer Pin Lock,有兴趣了解Oracle的Buffer Pin Lock的话,可以看我的那本《Oracle内核技术揭密》第三章3.1.2小节。MySQL中的“Buffer Pin Lock”,限于篇幅,原理咱们就不展开介绍了。我们只说Buffer Pin Lock的相容、阻塞规则。
MySQL的Buffer Pin Lock规则很简单:
① 读与读不阻塞。
② 读与写互相阻塞。
③ 写与写互相阻塞。
读就是共享锁,写就是独占锁。Select就是读,DML就是写。上面的规则,其实就是,当两个线程同时操作同一页时:Select与DML互相阻塞、DML互相阻塞、Select互不阻塞。这种规则很容易理解。所有的数据库也都是这样,但是,Oracle不一样。
Oracle的规则是:
① 读与读不阻塞。
② 读不阻塞写。
③ 写阻塞读。
④ 写与写互相阻塞。
看出来没,Oracle与MySQL的区别:“读不阻塞写”。
Oracle比MySQL向前走了一步,实现了读不阻塞写的Buffer Pin Lock。在读多写少的环境,读不阻塞写,可以有效的减少竞争。不像CBC Latch数量的问题,有一个“innodb_page_hash_locks”参数可以简单的弥补MySQL在此点上的不足。Buffer Pin Lock的相容、阻塞规则,是无法被改变的。只能期待后续MySQL代码的进步。
第四节
MySQL的棋高一着:共享的CBC Latch
当然,也不是所有地方Oracle都比MySQL棋高一着。有一个地方,MySQL还是有优势的。MySQL的CBC Latch只保护搜索HASH表:
如上图红色虚线框中的画,假设Bucker 2后的Node2是目标,在找到Node2后,MySQL将马上释放CBC Latch。Oracle不一样,如下图:
Oracle中在找到目标的Node 2后,并不马上释放CBC Latch,还要再修改Buffer Pin Lock的锁变量、设置Buffer Pin Lock锁,然后才能释放CBC Latch。(注:Oracle的Buffer Pin Lock锁就在图中的Node 2的内存中)
有看官说,“不就是多了个加个锁的操作吗,速度是很快的,Oracle的CBC Latch持有时间也不会比MySQL的长多少啊!”
话不能这么说,加锁虽然是耗时很短的操作,特别是Buffer Pin Lock这种“低级内存级锁”,也就是修改个内存标志位的事。但是,正是因为“修改”二字,导致Oracle的CBC Latch变成独占的了。所以,在Oracle 10G之前,CBC Latch没有模式,只有持有、不持有。因此只要持有CBC Latch,就是独占的,没有共享CBC Latch这一说。
但Oracle当然也非浪得虚名,在11G后,对CBC Latch、Buffer Pin Lock机制做了很大的调整。最大的改变发生在11.2.0.4后,在这个版本中,共享模式的CBC Latch得到了更为普及的使用。但总体上来说,独占模式的CBC Latch,还是比MySQL中要多。在这一点上,MySQL算是扳回一局。并不是所有地方,Oracle都比MySQL先进。Oracle的最低层内核,其实还有提升空间。
好了,为了照顾看热闹的群众,我并没有对MySQL、Oracle的部分原理做太过细致的剖析,省略了调试过程的细节。对调试技术、数据库源码有兴趣的朋友,请持续观注美创新运维新数据公众号,精彩持续中。
相关推荐
- 一文读懂Prometheus架构监控(prometheus监控哪些指标)
-
介绍Prometheus是一个系统监控和警报工具包。它是用Go编写的,由Soundcloud构建,并于2016年作为继Kubernetes之后的第二个托管项目加入云原生计算基金会(C...
- Spring Boot 3.x 新特性详解:从基础到高级实战
-
1.SpringBoot3.x简介与核心特性1.1SpringBoot3.x新特性概览SpringBoot3.x是建立在SpringFramework6.0基础上的重大版...
- 「技术分享」猪八戒基于Quartz分布式调度平台实践
-
点击原文:【技术分享】猪八戒基于Quartz分布式调度平台实践点击关注“八戒技术团队”,阅读更多技术干货1.背景介绍1.1业务场景调度任务是我们日常开发中非常经典的一个场景,我们时常会需要用到一些不...
- 14. 常用框架与工具(使用的框架)
-
本章深入解析Go生态中的核心开发框架与工具链,结合性能调优与工程化实践,提供高效开发方案。14.1Web框架(Gin,Echo)14.1.1Gin高性能实践//中间件链优化router:=...
- SpringBoot整合MyBatis-Plus:从入门到精通
-
一、MyBatis-Plus基础介绍1.1MyBatis-Plus核心概念MyBatis-Plus(简称MP)是一个MyBatis的增强工具,在MyBatis的基础上只做增强不做改变,为简化开发、提...
- Seata源码—5.全局事务的创建与返回处理
-
大纲1.Seata开启分布式事务的流程总结2.Seata生成全局事务ID的雪花算法源码3.生成xid以及对全局事务会话进行持久化的源码4.全局事务会话数据持久化的实现源码5.SeataServer创...
- Java开发200+个学习知识路线-史上最全(框架篇)
-
1.Spring框架深入SpringIOC容器:BeanFactory与ApplicationContextBean生命周期:实例化、属性填充、初始化、销毁依赖注入方式:构造器注入、Setter注...
- OpenResty 入门指南:从基础到动态路由实战
-
一、引言1.1OpenResty简介OpenResty是一款基于Nginx的高性能Web平台,通过集成Lua脚本和丰富的模块,将Nginx从静态反向代理转变为可动态编程的应用平台...
- 你还在为 Spring Boot3 分布式锁实现发愁?一文教你轻松搞定!
-
作为互联网大厂后端开发人员,在项目开发过程中,你有没有遇到过这样的问题:多个服务实例同时访问共享资源,导致数据不一致、业务逻辑混乱?没错,这就是分布式环境下常见的并发问题,而分布式锁就是解决这类问题的...
- 近2万字详解JAVA NIO2文件操作,过瘾
-
原创:小姐姐味道(微信公众号ID:xjjdog),欢迎分享,转载请保留出处。从classpath中读取过文件的人,都知道需要写一些读取流的方法,很是繁琐。最近使用IDEA在打出.这个符号的时候,一行代...
- 学习MVC之租房网站(十二)-缓存和静态页面
-
在上一篇<学习MVC之租房网站(十一)-定时任务和云存储>学习了Quartz的使用、发邮件,并将通过UEditor上传的图片保存到云存储。在项目的最后,再学习优化网站性能的一些技术:缓存和...
- Linux系统下运行c++程序(linux怎么运行c++文件)
-
引言为什么要在Linux下写程序?需要更多关于Linux下c++开发的资料请后台私信【架构】获取分享资料包括:C/C++,Linux,Nginx,ZeroMQ,MySQL,Redis,fastdf...
- 2022正确的java学习顺序(文末送java福利)
-
对于刚学习java的人来说,可能最大的问题是不知道学习方向,每天学了什么第二天就忘了,而课堂的讲解也是很片面的。今天我结合我的学习路线为大家讲解下最基础的学习路线,真心希望能帮到迷茫的小伙伴。(有很多...
- 一个 3 年 Java 程序员 5 家大厂的面试总结(已拿Offer)
-
前言15年毕业到现在也近三年了,最近面试了阿里集团(菜鸟网络,蚂蚁金服),网易,滴滴,点我达,最终收到点我达,网易offer,蚂蚁金服二面挂掉,菜鸟网络一个月了还在流程中...最终有幸去了网易。但是要...
- 多商户商城系统开发全流程解析(多商户商城源码免费下载)
-
在数字化商业浪潮中,多商户商城系统成为众多企业拓展电商业务的关键选择。这类系统允许众多商家在同一平台销售商品,不仅丰富了商品种类,还为消费者带来更多样的购物体验。不过,开发一个多商户商城系统是个复杂的...
你 发表评论:
欢迎- 一周热门
-
-
Redis客户端 Jedis 与 Lettuce
-
高并发架构系列:Redis并发竞争key的解决方案详解
-
redis如何防止并发(redis如何防止高并发)
-
开源推荐:如何实现的一个高性能 Redis 服务器
-
redis安装与调优部署文档(WinServer)
-
Redis 入门 - 安装最全讲解(Windows、Linux、Docker)
-
一文带你了解 Redis 的发布与订阅的底层原理
-
Redis如何应对并发访问(redis控制并发量)
-
oracle数据库查询Sql语句是否使用索引及常见的索引失效的情况
-
Java SE Development Kit 8u441下载地址【windows版本】
-
- 最近发表
- 标签列表
-
- oracle位图索引 (63)
- oracle批量插入数据 (62)
- oracle事务隔离级别 (53)
- oracle 空为0 (50)
- oracle主从同步 (55)
- oracle 乐观锁 (51)
- redis 命令 (78)
- php redis (88)
- redis 存储 (66)
- redis 锁 (69)
- 启动 redis (66)
- redis 时间 (56)
- redis 删除 (67)
- redis内存 (57)
- redis并发 (52)
- redis 主从 (69)
- redis 订阅 (51)
- redis 登录 (54)
- redis 面试 (58)
- 阿里 redis (59)
- redis 搭建 (53)
- redis的缓存 (55)
- lua redis (58)
- redis 连接池 (61)
- redis 限流 (51)