中间件之MYSQL

article/2025/10/6 7:00:24

体系结构

server层

负责建立连接、分析和执行 SQL

  • 连接器:与客户端进行TCP三次握手;校验用户名和密码;读取权限。
  • 查询缓存:key-value缓存在mysql8.0后默认将其关闭;高级版本默认采用页缓存。
  • 解析器:词法分析 + 语法分析。
  • 预处理器:检查 SQL语句中的表或者字段是否存在;将select * 中的 * 扩展为表上的所有列。
  • 优化器:依据执行成本,指定最优的执行计划。
  • 执行器:与存储引擎进行交互,调用引擎的相关接口,并获取查询结果。

存储引擎层

负责数据的存储和提取。

SQL语句

重难点为高级查询,建议通过做10道题来巩固理解。
查询后排序
select * from … order by col1 asc(查询结果按col1升序排序)
select * from … order by col1 asc col2 desc(查询结果按col1升序排序,col1相等时按col2降序排序)
分组查询
select … from … group by … having …
联表查询
select … from table1 inner join table2 on table1.colA = table2.colB
select … from table1 left join table2 on table1.colA = table2.colB
select … from table1 right join table2 on table1.colA = table2.colB
内连接:只取两张表有对应关系的记录
左连接:在内连接的基础上保留左表没有对应关系的记录
右连接:在内连接的基础上保留右表没有对应关系的记录

索引

索引的作用

  • 相当于一本书的目录,作用是加快查找。
  • 若没有索引或者没有按照索引进行查询,则会进行全表扫描,可能会导致业务崩溃(长时间无响应)。

B+树

  • B+树的一个结点就是页,在innodb中大小为16K,此值可以修改。
  • 数据页内有文件头,页头,目录项,数据区等字段;数据按分组,槽搜索使用二分,槽内数据以单链表的形式组织,需要顺序遍历。
  • 非叶子结点存放索引和磁盘地址信息,叶子结点存放“”数据信息“”,具体是什么数据信息由索引类型决定。
  • 一个索引对应一个B+树。
  • B+树的树高,决定了磁盘IO次数(每找一个数据页,需要一次磁盘io)。

聚集索引

  • 一个叶子结点存放若干个(主键 + 一整行数据)。

辅助索引

  • 一个叶子结点存放若干个(key +主键)。
  • 回表查询:先走辅助索引,再走聚集索引。
  • 若要查找的字段为索引的key或者主键,则无需回表,此时索引又称为覆盖索引。

覆盖索引

  • 是一个相对/动态概念。
  • 聚集索引一定是覆盖索引。
  • 其他索引(又称二级索引/辅助索引)可能是覆盖索引也可能不是,取决于select查询的字段是否在此索引内。

组合索引

  • 组合索引的第一个字段为B+树排序的key,其余字段无序。
  • 组合索引属于辅助索引的一种。

最左匹配原则

对于组合索引,从左到右依次匹配,遇到 > < between like 就停止匹配。

索引下推

  • MySQL 5.6推出优化策略,在存储引擎层实现,将部分检测条件满足的工作放到了存储引擎层。
  • 针对组合索引的范围查询(> < between)和模糊查询(like)。
  • 目的是减少查询时的回表操作。

常见的索引失效

  • 使用组合索引查询时,没有使用第一个字段。
  • 对索引使用左模糊匹配或左右模糊匹配。
  • 索引参与了表达式或函数运算。
  • where语句中 or 中存在非索引列。

索引设计原则

  • 对查询频次较高的字段建立索引。
  • 使用短索引:key‘越小,一个结点中存贮的key越多,B+树高度越小,查询效率越高。
  • 自己创建整数且自增的主键;整数是为了快速比较,自增是为了减少页分裂。
  • 尽量扩展已有索引而不创建新的索引。
  • 设置合理的组合索引,实现索引覆盖,减少回表操作。
  • 尽量避免使用select*:减少网络传输;避免回表操作。
  • 索引列设置为空,防止使用not null。
  • 开启索引下推(默认开启)。

如何找出效率低的sql语句。

  • htop top查看cpu 和 磁盘的占用率。
  • sql-slow-loh(慢查询日志,保存执行时间超过10s的sql语句,这个时间可以修改)。
  • 发布后,用户反馈。

事务

概念

  • 事务由一条或多条sql语句组成,为一个程序执行单元。

事务的状态和控制语句

  • BEGIN命令或start transaction命令:开启事务但未启动事务。
  • BEGIN命令 + 一条sql语句:开启事务并启动。
  • start transaction with consistent snapshot命令:开启事务并启动。
  • COMMIT命令:提交事务,对数据库的所有修改持久化。
  • ROLLBACK命令:回滚事务,结束事务并撤销所有未提交的修改。
    执行单条语句的情况下:mysql默认在语句执行前开启事务,语句执行后提交事务。

commit与数据落盘

  • 执行DML语句后但未提交:修改的数据会作用到缓存上,变为未提交的脏页,是不会被落盘的。
  • 执行DML语句并进行提交:缓存数据由未提交的脏页变为已提交的脏页,是需要被落盘的,但并不是commit操作后旧立刻执行落盘(具体机制由buffet cache + page cache机制决定)。

ACID特性

  • 原子性:事务操作要么都做(提交),要么都不做(回滚)。回滚操作依赖undolog。undolog存放在共享表空间,记录的是事务每步的具体操作,当执行回滚时,倒序进行具体操作的逆运算。
  • 隔离性:多个事务(线程)并发执行时,相互不影响。设置了不同程度的隔离级别(隔离级别越高,并发性能越低)隔离级别通过MVCC + 锁的方式实现。
  • 持久性:事务提交后,相关的DML操作将会持久化(依赖redolog);即使发生宕机故障,也能redolog进行数据恢复。
  • 一致性:一个事务只有提交后才对其他事务可见。

隔离级别

  • 读未提交:指一个事务还没提交时,它做的变更就能被其他事务看到。具体措施:读操作不做任何限制,直接读,读到的是最新的数据,写操作加排他锁。
  • 读已提交:指一个事务提交之后,它做的变更才能被其他事务看到。具体措施:支持MVCC,读取最新的历史数据(最新提交),写操作加排他锁。
  • 可重复读:指一个事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的,MySQL InnoDB 引擎的默认隔离级别。具体措施:支持MVCC,读取当前事务刚开始时的版本(最旧提交),写操作加排他锁。。
  • 可串行化:在多个事务对这条记录进行读写操作时,如果发生了读写冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。具体措施:读操作加共享锁,会导致事务串行化执行,丧失了并发性,因此具有最高的隔离级别。

MVCC实现原理

实现原理:undo版本链 + read view

在这里插入图片描述
在这里插入图片描述
(注:图片来自小林codong图解mysql)

某行记录版本链的更新:和undolog更新时机相同,针对对此记录执行的任何DML语句后都会产生一个版本,最新版本位于链表头,最旧版本位于链表末尾。
在这里插入图片描述

通过当前事务的read view来决定当前事务能够读取到的记录版本,分三种情况讨论:

  • 若读取的记录的trx_id < read view中的min_trx_id,说明该记录在创建此read view前就被其他事务提交了,那么这条记录对当前事务是可见的。
  • 若读取的记录的trx_id >= read view中的max_trx_id,说明此记录是创建此read view后由一个较新的事务修改的版本,那么这条记录对当前事务是不可见的,需要沿着roll_pointer向下查找,找到对应的版本。
  • 若读取的记录的trx_id在min_trx_id和max_trx_id之间,说明此条记录是由较老且未提交的事务所修改的(对于read view而言),那么需要进一步判断此trx_id是否在此read view 的 m_ids中,如果在,那么依然不可见,需要沿着roll_pointer向下查找,找到对应的版本;如果不在那么可见。

可重复读和读已提交的实现由read view创建的时机决定:

  • 对于可重复读:在启动事务前(不是开启!)会生成一个read view,然后再事务活跃期间都使用这个read view做版本判断。
  • 对于读已提交:每次执行sql语句前都会生成一个read view,仅仅此sql语句使用这个read view做版本判断。

innodb的可重复读如何解决幻读

  • 快照读:读取快照中规定版本的数据。当前读(直接读):读取最新的数据。
  • 分为快照读和当前读两种情况。对于快照读使用MVCC来避免幻读,对于当前读使用锁来避免幻读。
  • 在可重复读前提下,普通select语句使用的是快照读,开启事务后的第一条select范围查询语句前会生成一个快照,期间其他事务可以执行插入操作,但对当前事务不可见,因为之后的select范围查询语句都使用的之前的快指,所以这种情况下避免了幻读。
  • 除了普通select语句,DML语句和select … for update/in share mode使用的是当前读,不使用MVCC的快照机制,而是通过加next-key锁来解决幻读,next-key锁属于行锁,当前事务结束才会释放。执行当前读后对某些记录加上next-key锁,之后其他事务在特定范围内执行insert语句尝试插入时会被阻塞(插入意向锁和间隙锁冲突)。因此解决了幻读。

  • 表锁:需要手动使用lock命令添加,分为共享表锁和独占表锁。
  • 行锁:无需使用显式的lock,分为共享行锁和独占行锁、记录锁(共享行锁或独占行锁)、间隙锁、next-key锁。在可重复读的隔离级别下,普通select语句不使用锁而使用快照读,而select…for update语句和DML语句默认加next-key锁。
  • 意向锁:作用是为了快速处理行锁和表锁之间的兼容性判断,不能手动添加,加任何行锁之前都会加意向锁,分为意向共享锁和意向独占锁。只在表锁和行锁同时使用时起作用,大部分情况下不起作用,因为innodb很少使用表锁,绝大部分情况下使用行锁。
  • 有关意向锁和表锁之间的兼容性:共享表锁与独占表锁和独占意向锁冲突;独占表锁与共享表锁和共享意向锁冲突;共享表锁之间不冲突,共享表锁与共享意向锁之间不冲突;任何意向锁之间都不冲突。
  • 插入意向锁:本质上不属于意向锁,而是一种特殊的间隙锁,插入操作时会默认添加。
  • 插入意向锁和间隙锁是冲突的,这也是死锁产生的一大原因之一。
  • 锁的释放时机:除了自增锁是语句执行完释放,其余锁都是事务结束(提交 or 中止)后释放。

查询语句行锁的加锁规则

加锁的前提:使用select…in share mode 或 使用select…for update 或 使用DML。
普通的select语句并不会加锁,而是会执行MVCC机制。

加锁的基本单位是next-key锁,执行过程中会动态变化。

  • 唯一索引等值查询:(1)查询成功:next-key lock 会退化成记录锁 (2)查询失败:next-key lock 会退化成间隙锁。
  • 唯一索引范围查询:在满足一些条件的时候,next-key lock 退化为间隙锁和记录锁。
  • 非唯一索引等值查询:(1)查询成功:除了会加 next-key lock 外,还额外加间隙锁 (2)查询失败:退化为间隙锁。
  • 非唯一索引范围查询:next-key lock 不会退化为间隙锁和记录锁。

插入语句的行锁加锁规则

删除语句的行锁加锁规则

死锁的解决

顺序相反型

锁冲突型

死锁的避免(业务层)

  • 多事务操作的时候顺序加锁。

死锁的解决(DB层)

  1. 设置事务超时等待时间:事务等待时间超时后,会回滚释放锁。
  2. 开启主动死锁检测:用图算法检测死锁的发生,发生死锁后将环路中的一个事务回滚,释放锁。

常见异常

回滚覆盖

丢失更新

幻读

不可重复读和幻读的区别

日志

redolog

undolog

binlog

半成功状态的产生和解决

两阶段提交

组提交

buffer pool的原理

mysql高级应用

读写分离

读策略

缓存中则返回,否则查mysql,中则写缓存加返回,不中直接返回。

写策略(一致性策略)

  • 强一致性:先删除缓存,再更新mysql,最后同步到redis。不会导致丢失更新,但可能导致延迟,
  • 最终一致性:(1)写mysql,然后同步到redis。(2)写redis,设置key过期时间200ms,然后写mysql,最后同步到redis。并发性能高但是可能导致延迟。

同步方案及其原理

1.触发器 + UDF。缺点:触发器不支持事务;效率较低。
2.使用阿里开源的cannel中间件。
3.使用go-mysql-transfer中间件。
原理:主从复制,将同步中间件伪装成mysql的从数据库,将从数据库中的数据写到redis。

缓存故障

缓存穿透

  • 一直读取redis和mysql中都不存在的数据,导致mysql访问频繁从而崩溃。
  • 解决方案一:缓存设置<key, nil>,并设置过期时间。
  • 解决方案二:部署布隆过滤器。

缓存击穿

  • 大量请求一瞬间访问mysql有但是redis中没有的数据,导致mysql崩溃。
  • 可能原因:(1)某个数据突然大量访问(2)之前的某个热点数据缓存过期,再次成为热点。
  • 解决方案一:数据预热,设置热点数据永不过期。
  • 解决方案二:在缓存层加锁,当缓存未命中时,先获取锁,获取成功才查DB并写缓存,否则阻塞等待一会再查缓存。

缓存雪崩

  • 大量redis中的数据集中失效,但是mysql还存在,导致mysql访问频繁崩溃。
  • 可能原因:(1)redis宕机(2)大量缓存数据具有相同的过期时间,导致在某个时间段集体失效。
  • 解决方案一:设置随机过期时间。
  • 解决方案二:宕机重启后,进行数据预热。
  • 解决方案三:设置多级缓存。
  • 解决方案四:设置过期标志与后台更新线程。

http://chatgpt.dhexx.cn/article/xd1Spli2.shtml

相关文章

mysql优化——mycat数据库中间件

文章目录 目录 文章目录 前言 一、mycat原理 二、mycat完成读写分离 2.1搭建MySQL的主从模式 2.2实现读写分离 三、mycat垂直分库 四、水平分表 五、水平拆分表的连表操作 六、全局表 总结 前言 Mycat是数据库中间件,所谓中间件,是一类连接软件组件和应用的计算机软件,以…

mysql中间件汇总

一、写在前面 mysql作为互联网公司都会用到的数据库&#xff0c;在使用过程中。会用主从复制来提高性能。会用分库分表解决写入问题。以下介绍mysql中间件的一些实现方案 二、Atlas Atlas时 360 公司开发维护的一个基于MySQL协议的数据中间层项目。 2.1 atlas架构 Atlas是一…

makfile的编译选项:CFLAGS、CPPFLAGS、LDFLAGS、LIBS

1、LDFLAGS & LIBS 关于makefile的依赖规则以及目标生成的步骤&#xff0c;这篇先不介绍&#xff0c;这里主要关注我在看makefile时遇到的一堆CFLAGS与LDFLAGS定义进行说明。 费了牛劲&#xff0c;才查到GNUmake中文手册中关于CFLAGS与LDFLAGS的说明&#xff0c;他们都是…

Makefile 编译与链接选项及CFLAGS与LDFLAGS说明

CFLAGS与LDFLAGS的说明&#xff0c;他们都是是隐含规则的变量&#xff0c;且是一种命令参数变量&#xff0e; makefile内嵌隐含规则的命令中&#xff0c;所使用的变量都是预定义的变量。我们将这些变量称为“隐含变量”。这些变量允许对它进行修改&#xff1a;在Makefile中、通…

Go新项目-项目添加版本说明,以及参数传递:ldflags、gcflags,外加stripped说明(5)

在Go项目中&#xff0c;会存在展示该项目的版本信息&#xff0c;以及打包日期&#xff0c;项目版本&#xff0c;Go版本等&#xff1b;继Go新项目-编译项目的细节&#xff08;4&#xff09;以后再记录下编译优化方向&#xff0c;刚好也有项目版本输出的需求。 关键词&#xff1…

linux编译参数CPPFLAGS、CFLAGS、LDFLAGS参数的理解

参考到下面博客&#xff0c;把编译步骤分解的很细 linuxC编译参数CPPFLAGS、CFLAGS、LDFLAGS参数的理解 - yongfengnice - 博客园 CPPFLAGS : 预处理器需要的选项 如&#xff1a;-I (大写i指定头文件路径) CFLAGS&#xff1a;编译的时候使用的参数 –Wall –g -c LDFLAGS &…

【Java位运算】异或运算的使用

异或运算有个特性&#xff1a;两个相同的数做异或运算&#xff0c;结果为0。 n ^ n 0;常用的方式是查找数组中只出现一次的数字。 例如&#xff1a;在一个数组中&#xff0c;只有一个数字出现了一次&#xff0c;其余数字都出现了两次。 求这个数字。利用异或运算的特性进行计…

java中与运算,或运算,异或运算

Java的位运算符详解实例——与&#xff08;&&#xff09;、非&#xff08;~&#xff09;、或&#xff08;|&#xff09;、异或&#xff08;^&#xff09; 位运算符主要针对二进制&#xff0c;它包括了&#xff1a;“与”、“非”、“或”、“异或”。从表面上看似乎有点像逻…

Java中的与、或、非以及异或( | ~ ^)运算符的运算原理

首先&#xff0c;我们要知道Java存储数据是保存的补码&#xff0c;如果对原码、反码以及补码不太懂&#xff0c;可以参考我的另一篇博客。 1. Java的 与( & ) 运算 所有的逻辑运算都是按位计算的&#xff0c;所以第一步就是先把数据转成二进制&#xff0c;然后相应位进行…

java运算符 与()、非(~)、或(|)、异或(^)

欢迎大家关注我的公众号【老周聊架构】&#xff0c;Java后端主流技术栈的原理、源码分析、架构以及各种互联网高并发、高性能、高可用的解决方案。 位运算符主要针对二进制&#xff0c;它包括了&#xff1a;“与”、“非”、“或”、“异或”。从表面上看似乎有点像逻辑运算符&…

java中的异或非运算

一、java 与或非 操作 与&#xff08;&&#xff09;、非&#xff08;~&#xff09;、或&#xff08;|&#xff09;、异或&#xff08;^&#xff09; 1&#xff0e;与运算符 与运算符用符号“&”表示&#xff0c;其使用规律如下&#xff1a; 两个操作数中位都为1&#x…

java运算符异或(^)的用法

Java中的位运算符中有一个叫做异或的运算符&#xff0c;符号为&#xff08;^&#xff09;,其主要是对两个操作数进行位的异或运算&#xff0c;相同取0&#xff0c;相反取1。即两操作数相同时&#xff0c;互相抵消。 试例&#xff1a;13 与15 两个整数的异或 public static void…

一文搞懂 位运算 异或(Java实现)

提起异或想必很多小伙伴们既熟悉又陌生&#xff0c;熟悉是因为好像在离散数学或者学某个编程语言 时听过这个东西&#xff0c;而陌生呢&#xff0c;则是因为自己平时并没有用过&#xff0c;以至于当在某个场景 &#xff08;我猜是在看 题解或者某篇博客时&#xff09; 看到这…

java中异或运算的应用

java中异或运算(^)的应用 异或运算法则 1. a ^ b b ^ a   2. a ^ b ^ c a ^ (b ^ c) (a ^ b) ^ c;   3. d a ^ b ^ c 可以推出 a d ^ b ^ c.   4. a ^ b ^ a b. 异或运算 1、异或是一个数学运算符。应用于逻辑运算。   2、例如&#xff1a;真异或假的结果是…

java的异或运算

一、题目 输入两个数&#xff0c;交换后输出 二、解题思路 一开始学习的语言是c语言&#xff0c;里面非常有特色的是指针&#xff0c;通过传引用可以直接交换两个数。但java无法达成这种操作&#xff0c;翻阅资料找到了一种有趣的解法&#xff1a;异或! 异或原理&#xff1a; …

java基础-异或运算

小伙伴们&#xff0c;你们好呀&#xff01;我是老寇&#xff01; 异或运算主要用于判断两个值是否一样 异或运算的3个性质&#xff1a; 1.任何数和0进行异或运算&#xff0c;结果是原来的数&#xff0c;即b⊕0b 2.任何数和其自身进行异或运算&#xff0c;结果为0&#xff0c;即…

Java中的异或运算符^

Testpublic void test2() {int a2;int b3;aa^b;ba^b;aa^b;System.out.println("a"a",b"b);}第一种&#xff1a;用真实值计算 ^是异或运算符&#xff0c;异或的规则是转换成二进制比较&#xff0c;相同为0&#xff0c;不同为1. 一个数a与另一个数b异或的结果…

java中的异或运算

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是Rockey&#xff0c;不知名企业的不知名Java开发工程师 &#x1f525;如果感觉博主的文章还不错的话&#xff0c;请&#x1f44d;三连支持&#x1f44d;一下博主哦 &#x1f4dd;联系方式&#xff1a;he18339193956&…

HTTP Status 505 – HTTP Version Not Supported

这个大部分的原因不是httpserver不支持&#xff0c;而是请求url里有空格&#xff0c;检查有空格UTF-8编码再传 比如&#xff1a;远程调用服务端地址有空格 去除空格

HTTP Status 500 解决办法

HTTP Status 500 解决办法 在Java web 论坛系统中也遇见了这个问题&#xff0c;在这里就提供一个解决的办法。 图中显示 int idInteger.parseInt(request.getParameter("id")); 这一段有错误。这里是因为id的值不能为null&#xff0c;不然就会出现这个错误 修改如…