Java应届生求职八股(2)---Mysql篇

底层原理

1. 基础架构

MySQL可以基本划分为Server层存储引擎层两部分。

服务层分别有连接器、分析器、优化器、执行器

2. 存储引擎及其底层

1. InnoDB(默认引擎)

底层实现B+树索引(非叶子节点只存储指针,叶节点由双向链表链接,可范围查找)

支持事务,外键,MVCC行级锁

2. Memory(内存引擎)

底层实现哈希索引 (默认)或B树索引。(数据完全在内存)

3. MyISAM

底层实现B树

支持事务、外键;表级锁。

B+树相比B树的优势?

技术对比

维度 B+树 B树
数据存储 仅叶子节点存数据 所有节点存数据
查询性能 稳定O(log n) 最好O(1)最差O(log n)
范围查询 叶子节点链表支持高效遍历 需中序遍历

优化

1. 慢查询

造成原因:聚合查询多表查询 ,表数据量过大查询,深度分页查询

定位方案

  • 调试期MySQL开启慢日志查询,设置为 2 秒 (slow_query_log = 1)
SQL执行计划

(执行慢,如何分析?)

可以采用 EXPLAIN 或者 DESC 命令获取如何执行SELECT语句的信息。

展示的参数:

  • possible_keys:当前sql可能用到的索引。
  • key:当前sql实际命中的索引。
  • key_len:索引占用的大小。
  • type:性能排序为const(主键) > eq_ref(唯一索引) > ref(索引) > range > index(索引树)。
  • extra:优化建议。

2. 索引

索引是一种有序数据结构

底层: MySQL存储引擎的底层数据结构B+树满足索引的特定查找算法,提高查找效率。

1. 聚簇索引和非聚簇索引

分类 含义 特点
聚簇索引 数据与索引一起存储,保存行数据 必须有且仅有一个
二级索引(非聚簇) 数据和索引分开存储,保存 对应主键 可存在多个

聚簇索引选取规则:

  • 存在主键,则主键索引就是聚簇。
  • 没有主键,第一个唯一(UNIQUE)索引就是聚簇。
  • 没有主键和唯一索引,则InnoDB生成一个 rowid 作为隐藏聚簇。
回表查询

通过二级索引 找到对应的主键值,再到聚簇索引中拿到整行数据。

2. 覆盖索引

定义:查询使用索引,需要返回的列在该索引中能全找到。(回表查询 的就不是覆盖索引,避免select *

超大分页如何处理?
  • 问题:大数据量,limit分页查询,需要对数据排序,效率低。
  • 解决方案:覆盖索引 + 子查询。(走覆盖索引,获取主键,再到原来的表做关联查询。)

3. 索引创建原则

  • 尽量使用联合索引
  • 单表超10w数据,查询频繁
  • 针对查询条件、排序、分组的字段建立索引
  • 控制索引的数量
最左前缀原则

带头大哥不能死,中间兄弟不能断。

例如:在姓名 列和电话列设置联合索引。

  1. 使用两个字段,可以用到联合索引(注:两个字段的顺序颠倒并不影响,因为全值匹配时mysql会优化字段顺序)
  2. 使用左边的name字段,可以用到联合索引
  3. 使用右边的phone字段,无法使用索引,全表扫描

4. 索引失效场景

  1. 索引列运算 (函数、表达式、计算)。因为当前值改变后就无法与索引存的值匹配上。

    复制代码
    SELECT * FROM user_innodb where left(name, 3)='张三';-- left函数返回具有指定长度的字符串的左边部分
  2. 范围查询 (!=,<=>,in)会导致右边列失效。因为二叉树的查找是 = 查找,若是一个范围的话无法继续下探。

    复制代码
    SELECT * FROM user_innodb where name='张三' and age > 22;
  3. like以%开头 ('%abc...'),mysql索引失效会变成全表扫描操作。因为无法判断%代表多少字符。

    复制代码
    SELECT * FROM user_innodb where name like '%三';
  4. 字符串不加' '(类型转换) 索引失效。因为会出现隐式转换,相当于给索引列做了操作。

    复制代码
    SELECT * FROM user_innodb where name = 007;-- "007"从字符串变成了数字007
  5. or,用它连接时很多情况下索引会失效。

    复制代码
    SELECT * FROM user_innodb where name = '张三' or name = '李四';
  6. is null,is not null 无法使用索引。

    复制代码
    SELECT * FROM user_innodb where name is null;
  7. 联合索引违反最左前缀原则。

5. SQL优化有哪些?

  1. 表的设计优化 (数据类型的选择)
  2. 索引优化 (索引创建原则)
  3. SQL语句优化 (避免select *、避免where子句有表达式、使用innerjoin ......)
  4. 主从复制、读写分离
  5. 分库分表

事务

1. 事务特性(ACID)

  • 原子性(Atomicity):事务是不可分割的最小操作单元
  • 一致性(Consistency):事务完成时,所有数据 必须保持一致
  • 隔离性(Isolation):保证事务不受 外部并发 操作影响
  • 持久性(Durability):一旦提交或回滚,对数据库的改变永久

2. 并发事务问题及隔离级别

  • 并发事务问题:脏读、不可重复读、幻读
问题 现象描述 示例场景
脏读 事务A读取到事务B未提交的修改 事务B修改余额未提交,事务A看到100→200,B回滚后实际余额仍为100
不可重复读 事务A内两次读取同一数据 ,期间事务B修改了该数据并提交,导致两次结果不一致 事务A查询库存为10,事务B下单扣减为8并提交,事务A再次查询变为8
幻读 事务A按条件查询范围数据,期间事务B插入/删除符合该条件的记录,导致结果集变化 事务A查询年龄>30的用户(初始2条),事务B插入新用户后,事务A再查得到3条
  • 隔离级别:读未提交、读已提交、可重复读、串行化
隔离级别 脏读 不可重复读 幻读 实现机制 性能
读未提交 ❌ 可能 ❌ 可能 ❌ 可能 无锁,直接读最新数据 最高
读已提交 ✅ 避免 ❌ 可能 ❌ 可能 MVCC快照读(每次查询生成新ReadView)
可重复读(RR) ✅ 避免 ✅ 避免 ❌ 可能* MVCC快照读(事务首次 查询生成ReadView) 中等
串行化 ✅ 避免 ✅ 避免 ✅ 避免 全表锁(读加共享锁,写加排他锁) 最低

注:InnoDB在RR级别通过间隙锁可避免幻读,但标准SQL规范中RR允许幻读

3. undo log 和 redo log的区别

维度 undo log redo log
日志类型 逻辑日志(记录反向SQL) 物理日志(记录页修改)
主要作用 事务回滚 + MVCC多版本并发控制 崩溃恢复 + 事务持久性
写入时机 事务开始前 事务执行过程中
解决问题 保证事务原子性,一致性 保证事务持久性
关联事务 每个事务独立记录 所有事务混合记录

4. MVCC多版本并发控制

本质是通过多版本并发控制避免读写冲突 ,主要依赖于隐藏字段、undo log日志、ReadView

组件 作用 物理表现
隐藏字段 记录行数据的事务信息 DB_TRX_ID、DB_ROLL_PTR、DB_ROW_ID
Undo Log 存储数据的历史版本,形成版本链 回滚段(Rollback Segments)
ReadView 事务执行时生成的"快照",决定哪些版本对当前事务可见 m_idsmin_trx_idmax_trx_id
隐藏字段
  • 修改它的事务IDDB_TRX_ID):每进行一次事务操作,就会自增1。
  • 指向上一版本的指针DB_ROLL_PTR):结合undolog进行回滚。
ReadView

**通过readview我们才知道自己能够读取哪个版本。**在一个readview快照中包括四个字段:

  • m_ids:活跃的事务就是指还没有commit的事务。
  • max_trx_id预分配事务id。例如m_ids中的事务id为(1,2,3),那么下一个应该分配的事务id,即max_trx_id就是4。
  • creator_trx_id:执行select读这个操作的事务的id。

readview如何判断版本链中的哪个版本可用呢?

(1)如果要读取的事务id等于进行读操作的事务id,说明是我读取我自己创建的记录,可读取。

(2)如果要读取的事务id小于最小的活跃事务id,说明要读取的事务已经提交,可读取。

(3)如果要读取的事务id大于max_trx_id,说明该id已经不在该readview版本链中了,无法读取。

如何实现RC和RR的隔离级别
  1. RC 的隔离级别下,每个快照读 都会生成 并获取最新的readview。
  2. RR 的隔离级别下,只有在同一个事务第一个快照读 才会创建 readview,之后的每次快照读都使用的同一个readview ,所以每次的查询结果都是一样的

5. 主从同步

MySQL主从复制的核心就是二进制日志(BinLog)。

  1. Master 主库事务提交,数据变更记录在BinLog
  2. slave 从库读取BinLog,写入 到从库的中继日志Relay Log
  3. slave重做relay log的事务。

6.分库分表

  1. 垂直分库:以 为依据,根据业务将不同表拆分到不同库。
  2. 垂直分表:以字段 为依据,根据字段属性将不同字段拆分到不同表。
  3. 水平分库:将一个库的数据拆分到多个库。
  4. 水平分表:将一个表的数据拆分到多个表。(可以在同一个库)

MySQL锁

一、死锁

两个事务对一张表进行操作

sql 复制代码
session1										    session2
begin;										 	    begin;

select * from test where id = 3 for update;     	select * from test where id = 4 for update;

insert into test(id, name) values(3, "test1");    
												 	insert into test(id, name) values(4, "test2");

锁等待中
锁等待解除
													死锁,session 2的事务被回滚

上面两个并发事务一定会发生死锁(只有RR和Serializable 隔离级别下才会有间隙锁/临键锁,而这是导致死锁的根本原因)。

select ... for update虽然可以用于解决数据库的并发操作,但在实际项目中却不建议使用,原因是当查询 条件对应的记录不存在 时,很容易造成死锁

二、锁的分类

1.区间划分

1、间隙锁(Gap Locks)

实例 : (3, 4)

间隙锁是开区间 的,是一个在索引记录之间的间隙上的锁。
作用 :保证某个间隙内的数据 在锁定情况下不发生 任何变化

当使用唯一索引来搜索唯一行的语句时,不需要间隙锁定。如下面语句的id列有唯一索引,此时只会对id值为10的行使用记录锁。

复制代码
select * from t where id = 10 for update;// 注意:普通查询是快照读,不需要加锁
  • 如果,上面语句中id列没有建立索引 或者是非唯一索引 时,则语句会产生间隙锁
  • 如果,搜索条件里有多个查询条件 (即使每个列都有唯一索引),也是会有间隙锁的。

根据检索条件向下寻找最靠近检索条件的记录值A作为左区间,向上寻找最靠近检索条件的记录值B作为右区间,即锁定的间隙为(A,B)

2、临键锁(Next-key Locks)

临键锁是行锁+间隙锁 ,即临键锁是是一个左开右闭的区间,比如(- ∞, 1 ] |(1, 3 ] |(3, 4 ] | (4, + ∞)。

2.粒度划分

1、表级锁

直接给整个表添加锁:

复制代码
select * from student where name = 'tom' for update

InnoDB在使用过程中只要不通过索引检索 数据时,全部是表锁

开销小,加锁快;不会 出现死锁 ;锁定粒度大,发生锁冲突的概率最高,并发度最低

MyISAM在执行查询语句(SELECT)前,会自动给涉及的所有表加读锁,在执行更新操作(UPDATE、DELETE、INSERT等)前,会自动给涉及的表加写锁,这个过程并不需要用户干预,因此用户一般不需要直接用LOCK TABLE命令给MyISAM表显式加锁。

2、行级锁

InnoDB中给指定的行添加锁:

复制代码
select * from student where id > 10 for update

InnoDB行锁是通过给索引 上的索引项加锁来实现的,这一点,MySQL于Oracle不同,后者是通过在数据块中对相应的数据行加锁来实现的,InnoDB只有通过索引条件检索数据,InnoDB才使用行级锁

行锁的劣势:开销大;加锁慢; 出现死锁

行锁的优势:锁的粒度小,发生锁冲突的概率低;处理并发的能力强

3、页级锁

页级锁是 MySQL 中比较独特的一种锁定级别,在其他数据库管理软件中并不常见。

页级锁和行级锁一样, 发生死锁 。页级锁主要应用于 BDB 存储引擎。

3.级别划分

1、共享锁(即S锁)

共享锁(S):又称读锁 ,自己和其他事务都只能读不能写。

2、排它锁 / 独占锁(即X锁)

排它锁(X):又称写锁,自己可读可写,其他事务不能读写。

3、意向锁

事物B对一行数据使用行锁,当有另一个事物A对这个表使用了表锁,那么这个行锁就会升级为表锁。

当一个事务在需要获取资源的锁定时,如果该资源已经被排他锁占用,则数据库会自动给该事务申请一个该表的意向锁。如果自己需要一个共享锁定,就申请一个意向共享锁。如果需要的是某行(或者某些行)的排他锁定,则申请一个意向排他锁。

4.使用方式划分

1、乐观锁

先进行业务操作,不到万不得已不去拿锁

2、悲观锁

先获取锁,再进行业务操作

相关推荐
听雪楼主.2 小时前
Oracle Undo Tablespace 使用率暴涨案例分析
数据库·oracle·架构
我科绝伦(Huanhuan Zhou)2 小时前
KINGBASE集群日常维护管理命令总结
数据库·database
HMBBLOVEPDX2 小时前
MySQL的事务日志:
数据库·mysql
weixin_419658314 小时前
MySQL数据库备份与恢复
数据库·mysql
wml000004 小时前
CentOS启动两个MySQL实例
mysql·centos·3406
专注API从业者6 小时前
基于 Flink 的淘宝实时数据管道设计:商品详情流式处理与异构存储
大数据·前端·数据库·数据挖掘·flink
小猿姐6 小时前
KubeBlocks for Milvus 揭秘
数据库·云原生
AI 嗯啦7 小时前
SQL详细语法教程(四)约束和多表查询
数据库·人工智能·sql
杜子不疼.7 小时前
《Python学习之文件操作:从入门到精通》
数据库·python·学习