自己整理的1W字MySQL八股——JavaGuide精简口语化版

mysql的八股目前整理得比较浅,一些低频考点也暂时未整理进去。之后的话会去看看小林的八股以及45讲作补充(画个饼先)先拿guide哥引个流~

MySQL

基础

关系型数据库

定义:建立在关系模型上的数据库。关系模型表名了数据库中数据与数据之间的联系。比如多表查询的 一对一,一对多,多对多,外键约束。

SQL

定义:结构化查询语言,专门用于数据库。

场景:几乎所有主流关系型数据库都支持sql,一些非关系型数据库也兼容或者使用类似sql的查询语言。

MySQL

定义:关系型数据库。

好处:

  • 开源免费
  • 功能丰富
  • 事务支持优秀
  • 支持分库分表,读写分离,高可用

字段类型

CHAR 和 VARCHAR 的区别是什么?

char:定长字符串

varchar:变长字符串

char在存储时会填充空格来达到存储指定的长度,检索时会去掉空格

varchar在存储时需要多1或2个字节来记录字符串长度,检索时不需要处理

char适合长度规定好的或者差不多的字符串,比如身份证,Bcrypt算法,md5加密后的密码

varchar适合无法确定或者长度不一的字符串,比如用户名

共同点:

  • 在货号()里设置字符串最大长度,无论什么字母还是中文都只占1个字符

VARCHAR(100)和 VARCHAR(10)的区别是什么?

占用磁盘空间是一样的,但是消耗内存不一样。因为varchar在内存操作中,会分配固定大小的内存块来保存值。

DECIMAL 和 FLOAT/DOUBLE 的区别是什么?

decimal是定点数,其他两个是浮点数。decimal可以避免浮点数的精度损失,而其他两个只是存储近似值。

在java中,decimal对应的是BigDecimal

为什么不推荐使用 TEXT 和 BLOB?

text存储长文本数据

blob存储二进制数据

缺点:

  • 因为不能使用默认值
  • 在使用临时表时无法使用内存临时表,只能在磁盘上创建临时表
  • 不能直接创建索引,需要指定前缀长度

DATETIME 和 TIMESTAMP 的区别是什么?

datatime没有时区信息,而timestamp有。

datatime需要8个字节存储,timestamp只需要4个,但这也造成timestamp能表示的时间范围小。

  • DATETIME:1000-01-01 00:00:00 ~ 9999-12-31 23:59:59
  • Timestamp:1970-01-01 00:00:01 ~ 2037-12-31 23:59:59

NULL 和 '' 的区别是什么?

  • null在select null=null 时认为是不同的,但在distinct的时候,又被认为是相同的。
  • ''的长度为0,而null是占用空间的
  • null会影响聚合函数的结果,比如count在对列计算时,null不会被统计
  • 查询null时不能用比较运算符判断,要用is nullis not null判断,而''可以用比较运算符。

Boolean 类型如何表示?

MySQL 中没有专门的布尔类型,而是用 TINYINT(1) 类型来表示布尔值。TINYINT(1) 类型可以存储 0 或 1,分别对应 false 或 true。

基础架构

简单来说 MySQL 主要分为 Server 层和存储引擎层:

  • Server 层:主要包括连接器、查询缓存、分析器、优化器、执行器等,所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图,函数等,还有一个通用的日志模块 binlog 日志模块。
  • 存储引擎 :主要负责数据的存储和读取,采用可以替换的插件式架构,支持 InnoDB、MyISAM、Memory 等多个存储引擎,其中 InnoDB 引擎有自有的日志模块 redolog 模块。现在最常用的存储引擎是 InnoDB,它从 MySQL 5.5 版本开始就被当做默认存储引擎了。

Server层

  • 连接器: 身份认证和权限相关(登录 MySQL 的时候)。
  • 查询缓存: 执行查询语句的时候,会先查询缓存(MySQL 8.0 版本后移除,因为这个功能不太实用,经常出现缓存大规模失效情况)。
  • 分析器: 没有命中缓存的话,SQL 语句就会经过分析器,分析器说白了就是要先看你的 SQL 语句要干嘛,再检查你的 SQL 语句语法是否正确。
  • 优化器: 按照 MySQL 认为最优的方案去执行。
  • 执行器: 执行语句,先会看看该用户有没有权限,没有就返回错误信息,有就从存储引擎返回数据。

存储引擎

MySQL 支持哪些存储引擎?默认使用哪个?

可以使用show engines来查看所有支持的存储引擎。

默认是InnoDB,只有它支持事务。(5.5版本之前默认是MyISAM)

SELECT VERSION()可以查看mysql版本

SHOW VARIABLES LIKE '%storage_engine%' 命令直接查看 MySQL 当前默认的存储引擎

MySQL 存储引擎架构了解吗?

mysql存储引擎是插件式架构 ,存储引擎是基于表的,所以我们可以为不同表设置不同的存储引擎。甚至,我们还可以自己基于mysql存储引擎实现标准接口来编写自己的存储引擎。比如InnoDB其实一开始就是第三方存储引擎,因为太优秀了被oracle收购了。

MyISAM 和 InnoDB 有什么区别?

  • 行级锁:myisam只支持表级锁,InnoDB支持行级锁
  • 事务:myisam不支持,而InnoDB支持,并且定义了四个隔离级别。
  • 外键:myisam不支持,而InnoDB支持
  • 数据库异常崩溃后的安全恢复:myisam不支持,而InnoDB支持,依赖于redo log
  • MVCC:myisam不支持,而InnoDB支持。
  • 索引实现:虽然都是B+Tree的索引结构,但InnoDB引擎中数据文件就是索引文件。而myisam中,索引文件和数据文件是分离的。
  • 数据缓存策略和机制实现不同:InnoDB使用缓冲池Buffer Pool 缓存数据页和索引页,而myisam使用键缓存Key Cache 只缓存索引页不缓存数据页。

索引

定义:排序好的数据结构,用于检索数据

索引底层数据结构:

  • B树
  • B+树
  • hash

好处:可以增加检索速度

坏处:维护索引需要耗费空间和时间,如索引的空间占用,修改带索引的数据也同时需要修改索引。

索引底层数据结构

Hash表

使用的也是经典的哈希桶+拉链法。

InnoDB存储引擎不支持常规的hash索引,但它存在自适应哈希索引,它结合了B+树的特点,每个哈希桶都存储一个小型的B+Tree结构。这个B+Tree结构可以存储多个键值对,有利于减少hash冲突链表。

缺点:hash索引不支持顺序查询和范围查询

B树 和 B+树

B树全称 多路平衡查找树 , B+树是B树的一种变体。

目前大部分数据库系统都用它们作为索引结构。

B 树& B+树两者有何异同呢?
  • B树所有节点既存key,又存data;B+树只有叶子节点存key和data,其他节点只存key
  • B+树的叶子节点相比B树多了引用链,将它们串起来,便于顺序访问
  • B树的检索效率不定,因为,所有节点既存key又存data;而B+树的检索效率稳定,因为只有叶子节点有data
  • B树中范围查询要先找到下限,然后再中序遍历找到上限;而B+树只要遍历链表就行

InnoDB和MyIsam虽然都使用B+树作为索引结构,但两者实现方式不一样。MyIsam中,索引文件和数据文件是分开的,data域存放的是数据记录的地址。这称为非聚集索引 ;而InnoDB里,数据文件就是索引文件,叶子节点的data保存了完整的数据记录。这称为聚集索引。

索引类型总结

按数据结构分:

  • BTree索引
  • hash索引
  • 全文索引

按底层存储方式:

  • 聚集索引:数据与索引存储在一块
  • 非聚集索引:数据与索引不存储在一块

按应用分:

  • 主键索引
  • 普通索引
  • 唯一索引
  • 覆盖索引
  • 联合索引
  • 单列索引
  • 全文索引

主键索引

一张数据表只能有一个主键,主键不能为null,也不能重复。主键列使用的就是主键索引。如果没有指定主键的话,InnoDB会自动检查有无使用唯一索引并且不允许存在null的字段,有的话就把它作为主键,没有就自动创建一个6字节的自增主键。

二级索引

叶子节点存储的是主键值。

唯一索引,普通索引,前缀索引等索引都属于二级索引。

  • 唯一索引:添加唯一约束会自动添加唯一索引。唯一索引是允许null值,一张表允许创建多个唯一索引。
  • 普通索引:允许数据重复和null值。唯一作用就是快速检索数据。
  • 前缀索引:只适用于字符串类型的数据。可以对文本前几个字符创建索引,相比普通索引更不占内存。
  • 全文索引:为了检索大文本数据中的关键词,用于目前的搜索引擎数据库。

聚集索引与非聚集索引

聚集索引

定义:索引结构和数据一起存放的索引。不是一种单独的索引类型。

InnoDB的主键索引就属于聚集索引。

InnoDB引擎的表的.idb文件就包含了表的索引和数据,索引(B+树)的每个非叶子节点存储索引,每个叶子节点存储索引和数据。

优点:

  • 查询速度快,相比非聚集索引少一次读取数据操作。

缺点:

  • 依赖有序的数据
  • 更新代价大:因为叶子节点包含数据,一改动整个数据也要跟着改。所以一般聚集索引是主键索引。

非聚集索引

定义:索引结构和数据分开的索引。不是一种单独的索引类型。二级索引就是非聚集索引。MyIsam引擎使用的都是非聚集索引。

优点:

  • 更新代价相对聚集索引比较小

缺点:

  • 也依赖有序的数据
  • 可能会回表查询,影响性能

聚簇索引和非聚簇索引

覆盖索引

定义:一个索引覆盖了索引需要查询的字段的值

可以使用explain检测有无覆盖

联合索引

定义:一次性对表中多个字段创建索引。

最左前缀法则

使用联合索引时,从左到右匹配查询条件的字段,匹配到就可以使用索引来过滤数据。

它在遇到> 、<时,会停止匹配,但遇到>= 、 <= Between 和 前缀匹配like时不会停止匹配。

创一个联合索引,相当于创了多个索引。

假设有一个联合索引(column1, column2, column3),其从左到右的所有前缀为(column1)、(column1, column2)、(column1, column2, column3)

创建联合索引时可以把区分度高的字段放在最左边,使用时可以过滤更多数据。

索引跳跃扫描

Index Skip Scan,简称 ISS。

定义:可以在某些情况下避免全表扫描,即使不满足最左前缀法则

好处:可以提高查询效率

索引下推

定义:可以在存储引擎在索引遍历时,执行部分where的判断条件来直接过滤掉不满足条件的记录,减少回表查询。

下推:就是把部分server层的事情,交给存储引擎层处理。

没索引下推时:根据某索引字段找到符合该条件的主键,然后再回表查询获得完整数据,把这些数据返回给server层作下一个条件的判断。

有索引下推时:根据索引字段找到符合该条件的主键,然后直接判断下一个条件是否满足,继续筛选出两个条件都符合的主键,然后再回表查询获得完整数据来返回给server服务层。

好处:可以减少回表查询次数,也减少了存储引擎层和server服务层之间传输的数据。

场景:

  • 子查询不能使用索引下推,因为子查询使用临时表处理结果,临时表是没索引的。
  • 存储过程不能使用索引下推,因为存储引擎无法调用存储函数

使用索引时的建议(同正确使用索引

查询缓存

MySQL 8.0 版本后移除,因为这个功能不太实用

开启后同样的查询语句会在缓存中直接返回结果。

缓存不命中的情况:

  • 查询语句有不同
  • 查询中包含自定义函数,存储函数,用户变量啥的,查询结果不会缓存
  • 查询缓存数据涉及的表结构发生变化,对应缓存将失效。

缺点:

  • 查询缓存带来了额外开销,包括时间和空间,失效还要销毁。

日志

分类:

  • 错误日志
  • 查询日志
  • 慢查询日志
  • 事务日志
  • 二进制日志

redo log

属于事务日志。

作用:能让mysql具有崩溃恢复的能力。可以保证事务的持久性。

mysql里的增删改查操作会先从buffer pool缓冲池 中找,如果没有再从硬盘去加载数据页,这样做可以减少硬盘的IO开销。然后buffer pool会把我们的操作记录缓存到 redo log buffer重做日志缓冲区, 然后刷盘到redo log文件里。

刷盘时机

  • 事务提交
  • log buffer空间不足时
  • checkpoint(检查点):InnoDB会定期执行检查点操作,把脏数据刷新到磁盘
  • 后台刷新线程:InnoDB启动一个后台线程,会定期把脏页刷新到磁盘
  • 正常关闭服务器时

刷盘策略

innodb_flush_log_at_trx_commit 默认为1

  • 0 :每次事务提交不进行刷盘 ,性能高,安全低 。如果mysql挂了可能会丢失1s内的事务
  • 1 :每次事务提交都进行刷盘 ,性能低,安全高
  • 2 :每次事务提交把log buffer里的redo log内容写入page cache(文件系统缓存)里。page cache是专门用于缓存文件的,里面的文件就是redo log

后台刷新线程 会定时把redo log buffer的内容写到page cache里,然后调用fsync刷盘

一个没有提交事务的 redo log 记录,也可能会刷盘。

日志文件组

硬盘上存储的redo log文件并非只有一个,而是以一组的形式存储,每个文件大小相同。

它采用环形数组形式,以达到无限循环写的目的。

其中有两个属性:

  • write pos:当前记录的位置,随着写入而后移
  • checkpoint:当前擦除的位置,随着擦除而后移

它们之间空着的部分就是可写入内容的容量,如果write pos追上了checkpoint,就表明日志组满了,需要清理。

在使用 MySQL 8.0.30 及之后的版本时,日志文件组的文件数则固定为 32,文件大小则为 innodb_redo_log_capacity / 32 。使用 innodb_redo_log_capacity 变量可以配置日志文件组总容量

为什么使用redo log而不是直接刷盘?

因为数据页刷盘是随机写,一个数据页位置可能在硬盘的随机位置,性能很差。而使用redo log,可以实现顺序写,刷盘速度更快。

其实内存的数据页在一定时机也会刷盘,我们把这称为页合并,讲 Buffer Pool的时候会对这块细说

binlog

redo log 是物理日志,记录内容是"在某个数据页上做了什么修改",属于 InnoDB 存储引擎。

binlog 是逻辑日志,记录内容是语句的原始逻辑,类似于"给 ID=2 这一行的 c 字段加 1",属于MySQL Server 层。不管用什么存储引擎,都会产生binlog日志

作用:可以做数据库的数据备份,主从复制,用于同步数据。

binlog是顺序写

记录格式

binlog日志有三种格式,使用binlog_format指定。

  • statement:记录sql原文,但在执行now()函数时会产生与原数据不一致的值
  • row:记录操作的具体数据,能更好地保证一致性,但也因此更占用空间,同步时更消耗IO资源
  • mixed:折中方案,让mysql判断该sql语句是否会引起不一致情况,会就用row格式,不会就使用statement格式

写入机制

事务执行时,先把日志写入binlog cache,事务提交时,再把binlog cache写入binlog文件中。

系统给每个线程都分配了一个块内存作为binlog cache,因为事务不可分割,所以如果比较大,也要确保一次性写入。

可以使用binlog_cache_size来控制线程binlog cache的大小。如果超过了,就要暂存到磁盘(swap

sync_binlog :控制写入page cache和同步到磁盘的时机

  • 0:由系统判断什么时候执行刷盘
  • 1:每次提交事务时都会执行刷盘
  • n(n>1):每次提交事务都write,积累提交n次事务,执行一次刷盘。

两阶段提交

redo log 和 binlog 都是持久化的保证,区别在于:

  • 侧重点不同:redo log是对于自身的数据恢复,binlog是对于其他节点的数据一致。
  • 写入时机不同:redo log 在事务执行过程中就能写入(有后台刷新线程等),binlog只有在事务提交时才能写入

redo log 与 binlog 两份日志之间的逻辑不一致,会出现什么问题?

比如写完redo log日志,但写binlog日志时发生异常

主库因为redo log日志恢复数据,而bin log日志里没有对应sql,所以和它同步的数据库的数据将出现和主库不一致的情况。

解决方法 :使用两阶段提交 方法。把redo log的写入拆成preparecommit两阶段。一个在事务执行中,一个在提交事务时。这样即使写入binlog失败,数据库发现redo log还在准备阶段,就会回滚该事务。而如果此时redo log提交失败,我们发现binlog日志中已经有对应sql,说明redo log也有准备阶段的数据,就会继续提交事务。(根据事务id可以在日志中寻找事务)

undo log

作用:记录事务中对数据的修改,在执行事务时出现错误或者需要回滚时,可以使用undo log对数据进行恢复到事务开始之前的状态。可以保证事务的原子性。

属于逻辑日志。记录的是sql语句,与原sql相反。undo log也会被记录到redo log中,因为也要实现它的持久化。undo log中,插入操作在事务提交后就可以直接删除,而更新和删除操作在事务提交后还不会立马删,会先加入history list,由后台线程purge进行清理。

undo log 采用 segment(段)的方式记录,每个undo操作占用一个undo log segment段,undo log segment包含在 rollback segment(回滚段)中。事务开始时,需要分配一个回滚段,一个回滚段里包含1024个重写日志段。

rollback segment header在回滚段的第一页,用于管理回滚段。它包含history list,用于记录已经提交但没被清理的事务的undo log,可以让purge线程借助它来清理这些事务。

事务

定义:一组操作,要么都执行,要么都不执行。

ACID

  • 原子性:事务是最小执行单位,要么全部成功,要不全部失败
  • 一致性:执行事务后,数据要保持一致。比如交易买卖方金额之和一致。
  • 隔离性:事务之间彼此独立,互不干扰
  • 持久性:事务提交后,对数据的改变就是永久的

只有保证事务的原子性,隔离性,持久性,才能保证事务的一致性。

并发事务带来了哪些问题?

脏读

一个事务读到另一事务未提交的数据。

丢失修改

两个事务对同一事务进行读取和修改,其中一个事务的更改被后来的事务所替代。比如两个事务对a=a-1,在都读取为20时,两个先后-1最后还是得到19

不可重复读

一个事务内多次读同一数据发现数据不同。

幻读

一个事务在查询数据的时候,发现比之前的查询多了或少了一些数据。

不可重复读和幻读有什么区别?

  • 不可重复读的侧重点是在于内容的修改
  • 幻读的侧重点在于记录的新增或减少

幻读可以看做不可重复读的特殊情况,单独拎出来只是两者解决方法不一样。

比如:插入数据时,要依赖间隙锁,防止位置莫名被其他事务抢了;而不可重复读只需要锁住已经存在的记录就行了。

并发事务的控制方式

MVCC。锁可以看做是悲观控制,MVCC可以看做是乐观控制

mysql主要通过读写锁来实现并发事务控制

  • 共享锁(S):多个事务可以同时获取
  • 排他锁(X):只能单个事务获取

读写锁可以做到读读并行,但不能读写和写写并行。InnoDB默认使用行级锁。不论行级锁还是表级锁,都有共享锁和排他锁这两类。

MVCC

定义:多版本并发控制。对每一份数据存储多个版本,通过事务可见性让事务能看到自己应该看到的版本。有一个全局的版本分配器为每行数据设置版本号,版本号是唯一的。

作用:在多个并发事务同时读写数据库时保持数据的一致性和隔离性。

读操作

使用快照读取。在事务开始时,会创建一个快照,在事务执行过程中,快照会被事务读取。事务不会读取到其他事务未提交的修改。

如果数据有多个版本,事务会选择不晚于其事务开始时间的最新版本,确保事务只读它开始之前的数据。(不用隔离级别情况不同)

事务读的是快照数据,所以其他并发事务对数据行的修改不会影响该事务的读取(包括对记录上锁也能读取,因为是读的是快照)。

写操作

事务会为新的数据创建一个新版本。新版本带有该事务的版本号,并且原始数据仍然存在,这保证了其他事务不会被该操作影响。

事务提交和回滚

事务提交:它的修改会成为数据库最新版本,并对其他事务可见

事务回滚:它的修改会被撤销,其他事务不可见。

版本回收

为了防止数据库中数据版本无限增长,MVCC会定期对不需要的旧版本进行回收来释放空间。

一致性非锁定读

通常是通过更新数据的同时,更新时间戳或版本号来实现。查询时,同时将当前可见的版本号和记录的版本号进行比对,如果记录版本号小,则表示该记录可见。在InnoDB下,表现为多版本控制,也就是快照读。

Repeatable ReadRead Committed这两个隔离级别下,执行普通的select语句(不加锁),就会使用一致性非锁定读。

Repeatable Read下,使用快照读,能够实现可重复读和防止部分幻读(查询的幻读可以防止,因为每次都是读取原来的快照;但如果有执行插入语句,还是可能会遇到幻读,可能刚好插入的语句在其他事务里存在并已经提交)

锁定读/当前读

定义:读取的是数据的最新版本。会对读取的记录进行加锁

  • select ... lock in share mode 对记录加S锁
  • select ... for update 对记录加X锁
  • insert、update、delete 操作

如果执行当前读,我们需要使用临键锁来防止其他事务在间隙间插入数据,防止出现不可重复读和幻读。

隐藏字段

InnoDB引擎为每行数据都添加了三个隐藏字段

  • DB_TRX_ID(6字节):表示最后一次插入或更新该行的事务id。delete命令也算更新,它会在记录头Record headerdeleted_flag字段标记为删除
  • DB_ROLL_PTR(7字节):回滚指针,指向该行的undo log
  • DB_ROW_ID(6字节):如果没有设置主键并且没有唯一非空索引时,InnoDB会使用它来生成聚集索引
read view

定义:读视图,记录着对本事务不可见的其他活跃事务。

主要有以下字段:

  • m_low_limit_id:目前出现过的最大的事务 ID+1,即下一个将被分配的事务 ID。大于等于这个 ID 的数据版本均不可见
  • m_up_limit_id:活跃事务列表 m_ids 中最小的事务 ID,如果 m_ids 为空,则 m_up_limit_id 为 m_low_limit_id。小于这个 ID 的数据版本均可见
  • m_ids:Read View 创建时其他未提交的活跃事务 ID 列表。创建 Read View时,将当前未提交事务 ID 记录下来,后续即使它们修改了记录行的值,对于当前事务也是不可见的。m_ids 不包括当前事务自己和已提交的事务(正在内存中)
  • m_creator_trx_id:创建该 Read View 的事务 ID
undo log

定义:用于记录数据的多个版本。

作用:

  • 事务回滚时对数据的恢复
  • MVCC时,读取记录,如果该记录被其他事务占用或最新版本对该事务不可见,就可以通过undo log读取到以前版本的数据,以此实现非锁定读(快照读)。

在InnoDB里,undo log分两种:

  • insert undo log:指insert操作产生的undo log。因为插入操作记录只对该事务可见,对其他事务不可见,所以该undo log可以在事务提交后直接删除。
  • update undo log:update或delete产生的undo log。因为它可能需要被用于MVCC机制,所以在事务提交后不能立马删除。需要放入undo log链表,等待purge线程来删除。

不同事务或者相同事务的每次对同一记录行的修改,会使该记录行的 undo log 成为一条链表,链首就是最新的记录,链尾就是最早的旧记录。

数据可见性算法

在InnoDB下,在事务里执行普通select语句时,会创建一个快照(read view)(每次还是只创一次与隔离级别有关)。数据记录的事务id会与快照中的变量进行比较,来判断当前版本的数据是否对该事务可见。

判断后发现记录对该事务可见时,就返回该记录的值;如果发现该版本记录对该事务不可见,那就通过记录的DB_ROLL_PTR读取到上个undo log,并使用它的事务id继续与快照的变量进行判断,直到发现某个版本记录对该事务可见,此时返回该undo log里该记录的值。

RC 和 RR 隔离级别下 MVCC 的差异

在事务隔离级别 RCRR (InnoDB 存储引擎的默认事务隔离级别)下,InnoDB 存储引擎使用 MVCC(非锁定一致性读),但它们生成 Read View 的时机却不同

  • RC:每次普通select时都生成一个快照。
  • RR:只有在事务开始后的第一个普通select时生成一个快照。

事务隔离级别

  • READ-UNCOMMITTED(读取未提交) :最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。
  • READ-COMMITTED(读取已提交) :允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
  • REPEATABLE-READ(可重复读) :对同一字段的多次读取结果都是一致的,可以阻止脏读和不可重复读,但幻读仍有可能发生。
  • SERIALIZABLE(可串行化) :最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。

MySQL 的隔离级别是基于锁实现的吗?

基于MVCC 机制共同实现。

SERIALIZABLE 隔离级别是通过锁来实现的,READ-COMMITTED 和 REPEATABLE-READ 隔离级别是基于 MVCC 实现的。不过, SERIALIZABLE 之外的其他隔离级别可能也需要用到锁机制,就比如 REPEATABLE-READ 在当前读情况下需要使用加锁读来保证不会出现幻读。

MySQL 的默认隔离级别是什么?

REPEATABLE-READ(可重复读)。可以通过SELECT @@transaction_isolation;查看。

表级锁和行级锁对比:

  • 表级锁:与存储引擎无关,MyIsam和InnoDB都支持表级锁。
  • 行级锁:针对索引字段加的锁。加锁开销大,可能出现死锁。和存储引擎有关,在存储引擎层面实现的。

行级锁的使用有什么注意事项?

如果索引没命中,扫描全表会导致所有行记录都加锁。当然有时候走索引了也会走全表扫描,这是优化器的原因。

InnoDB 有哪几类行锁?

  • 记录锁(Record Lock):单行记录上锁
  • 间隙锁(Gap Lock):锁一个范围,但不包含本身
  • 临键锁(Next-Key Lock):=记录锁+间隙锁,锁范围+锁本身。主要目的是为了解决幻读。

InnoDB默认隔离级别是Repeatable-Read,行锁默认使用临键锁。如果操作的索引是主键索引或唯一索引,InnoDB会对临键锁优化,降级成记录锁。(因为值唯一,能够确切地锁住该记录)

共享锁和排他锁呢?

无论是表级锁还是行级锁,都存在这两类。

由于MVCC存在,一般select不会加锁,需要自己显性加锁

ini 复制代码
# 共享锁 可以在 MySQL 5.7 和 MySQL 8.0 中使用
SELECT ... LOCK IN SHARE MODE;
# 共享锁 可以在 MySQL 8.0 中使用
SELECT ... FOR SHARE;
# 排他锁
SELECT ... FOR UPDATE;

意向锁有什么作用?

用于快速判断表里记录有没有添加行锁。(一般情况我们要遍历来判断有无行锁)如果没有,我们就可以对某个表添加表锁。

意向锁分两类:

  • 意向共享锁(Intention Shared Lock,IS锁):事务有意向为表中某些记录添加共享锁,在这之前会先获取该表的IS锁。
  • 意向排他锁(Intention Exclusive Lock,IX锁):事务有意向为表中某些记录添加排他锁,在这之前会先获取该表的IX锁。

意向锁之间是互相兼容的。(原因是因为只是表达了意图而非实际锁定)

意向锁和共享锁和排他锁(这里指表级别的共享锁和排他锁,意向锁不会和行级别的共享锁和排他锁互斥)

当前读和快照读有什么区别?

快照读

定义:一致性非锁定读。是单纯的select语句,而不能使用意向共享锁/共享锁(会触发当前读,因为需要读最新数据并加锁)

快照就是记录着数据的历史版本

快照读在RC(读取已提交)和RR(可重复读)下才会使用快照读:

  • RC下,每次读取最新的快照数据
  • RR下,每次读取本事务最开始时的快照数据

快照读适合对一致性要求不高,而对性能要求高的业务场景

当前读

定义:一致性锁定读。会给行数据加X锁或S锁。

bash 复制代码
# 对读的记录加一个X锁
SELECT...FOR UPDATE
# 对读的记录加一个S锁
SELECT...LOCK IN SHARE MODE
# 对读的记录加一个S锁
SELECT...FOR SHARE
# 对修改的记录加一个X锁        也会触发当前读
INSERT...
UPDATE...
DELETE...

自增锁有了解吗?

一个事务在插入有设置AUTO_INCREMENT字段的表时,需要先获取自增锁,如果获取不到就会阻塞。阻塞只是一种形式,其他具体实现要看innodb_autoinc_lock_mode

交错模式下,所有插入语句都不使用表级锁,而是使用轻量级互斥锁实现,并发能力更强。

但如果有主从同步需求,并且binlog存储格式为Statement时,不能设置为交错模式,因为可能多条插入语句顺序没法保障。

性能优化

能用 MySQL 直接存储文件(比如图片)吗?

可以,存二进制数据就行,但很消耗存储空间。建议使用云服务厂商提供的文件存储服务,或者自己使用MinIO来实现分布式文件服务。

MySQL 如何存储 IP 地址?

使用mysql提供的方法:

  • INET_ATON():把 ip 转为无符号整型 (4-8 位)
  • INET_NTOA() :把整型的 ip 转为地址

插入时和显示时分别调用即可

有哪些常见的 SQL 优化手段?

避免使用select *

  • 消耗更多的cpu资源
  • 无用字段会增加网络消耗,减缓操作时间
  • 无法使用覆盖索引优化

分页优化

mysql并不是跳过offset行,而是返回offset+n行,然后去掉offset前面的行。这会导致offset大的时候,查询效率特别低。

方法:使用覆盖索引+延迟子查询

比如在我们原查询语句是查询某些字段时从xx行开始的yy行,我们可以先利用主键索引查询到这几行,然后把该子查询作为一张表,查询出来的主键字段的值作为主查询的条件进行where比较,这样我们又可以走覆盖索引来优化速度。

缺点:子查询会产生新表,也会影响性能,不能大量使用。

尽量避免多表做 join

原因:它是使用嵌套循环实现关联查询,三种实现效率都不高:

  • Simple Nested-Loop Join:没有进过优化,直接使用笛卡尔积实现 join,逐行遍历/全表扫描, 效率最低。
  • Block Nested-Loop Join :利用 JOIN BUFFER 进行优化,性能受到 JOIN BUFFER 大小的影响,相比于 Simple Nested-Loop Join 性能有所提升。不过,如果两个表的数据过大的话,无论如何优 化,Block Nested-Loop Join 对性能的提升都非常有限。
  • Index Nested-Loop Join :在必要的字段上增加索引,使 join 的过程中可以使用到这个索引,这样可以让 Block Nested-Loop Join 转换为 Index Nested-Loop Join,性能得到进一步提升。

避免方法:

  • 单表查询后通过应用程序在内存中自己做关联:性能更好,而且代码复用性更高
  • 数据冗余:把数据在表中做冗余,避免关联查询。方法很笨,需要考虑表结构设计

选择合适的字段类型

存储字节越小,空间占用就越小,性能越好。

比如可以用方法处理ip地址把它变成整型存储。

批量操作

减少请求数据库次数,提高性能

Show Profile 分析 SQL 执行性能

SHOW PROFILE CPU,IPC FOR QUERY 8 ;

优化慢 SQL

可以开启慢查询日志功能,mysql会生成一个日志文件,专门记录查询速度超过阈值的sql

找到慢查询sql后,我们可以通过explain命令对该语句进行分析,可以看到它使用了什么索引,执行方式是什么,扫描了多少行...

正确使用索引

选择合适的字段创建索引
  • 不为null的字段:数据库比较难优化
  • 被频繁查询的字段
  • 被作为条件查询的字段
  • 频繁需要排序的字段
被频繁更新的字段应该慎重建立索引

维护索引是有成本的,包括空间占用,修改字段等。

尽可能的考虑建立联合索引而不是单列索引

可以减少过多索引占用磁盘,对于索引的复用更好。

注意避免冗余索引

如果一个联合索引包含了某单列索引的字段。

考虑在字符串类型的字段上使用前缀索引代替普通索引

可以减少索引占用内存空间。

如何分析 SQL 的性能?

使用explain命令,来分析sql的执行计划。执行计划:sql在经过mysql查询优化器优化后的具体执行方式

explain不会真的执行sql,只是通过查询优化器对语句进行分析。

explain适用于其他增删改查语句,只是用于select语句多些

读写分离和分库分表了解吗?

常见的数据库优化方法有哪些?

  • 读写分离和分库分表
  • 数据冷热分离
  • sql优化
  • 适当冗余数据
  • 提升硬件配置
相关推荐
代龙涛2 小时前
WordPress 主题初体验:从 style.css 到 index.php、single.php 简单实战
后端·php·wordpress
zzb15808 小时前
RAG from Scratch-优化-query
java·数据库·人工智能·后端·spring·mybatis
一只鹿鹿鹿8 小时前
信息安全等级保护安全建设防护解决方案(总体资料)
运维·开发语言·数据库·面试·职场和发展
堕2748 小时前
MySQL数据库《基础篇--数据库索引(2)》
数据库·mysql
wei_shuo8 小时前
数据库优化器进化论:金仓如何用智能下推把查询时间从秒级打到毫秒级
数据库·kingbase·金仓
雷工笔记9 小时前
Navicat Premium 17 软件安装记录
数据库
wenlonglanying9 小时前
Ubuntu 系统下安装 Nginx
数据库·nginx·ubuntu
数据库小组9 小时前
10 分钟搞定!Docker 一键部署 NineData 社区版
数据库·docker·容器·database·数据库管理工具·ninedata·迁移工具
爬山算法9 小时前
MongoDB(38)如何使用聚合进行投影?
数据库·mongodb
必胜刻9 小时前
RESTful 基础:资源、路径与方法对应关系详解
后端·restful