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、悲观锁

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

相关推荐
IvorySQL1 小时前
PostgreSQL 分区表的 ALTER TABLE 语句执行机制解析
数据库·postgresql·开源
·云扬·2 小时前
MySQL 8.0 Redo Log 归档与禁用实战指南
android·数据库·mysql
IT邦德2 小时前
Oracle 26ai DataGuard 搭建(RAC到单机)
数据库·oracle
惊讶的猫2 小时前
redis分片集群
数据库·redis·缓存·分片集群·海量数据存储·高并发写
不爱缺氧i2 小时前
完全卸载MariaDB
数据库·mariadb
纤纡.2 小时前
Linux中SQL 从基础到进阶:五大分类详解与表结构操作(ALTER/DROP)全攻略
linux·数据库·sql
jiunian_cn3 小时前
【Redis】渐进式遍历
数据库·redis·缓存
橙露3 小时前
Spring Boot 核心原理:自动配置机制与自定义 Starter 开发
java·数据库·spring boot
冰暮流星3 小时前
sql语言之分组语句group by
java·数据库·sql
符哥20083 小时前
Ubuntu 常用指令集大全(附实操实例)
数据库·ubuntu·postgresql