自己整理的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优化
  • 适当冗余数据
  • 提升硬件配置
相关推荐
许野平25 分钟前
Rust: 利用 chrono 库实现日期和字符串互相转换
开发语言·后端·rust·字符串·转换·日期·chrono
零炻大礼包34 分钟前
【SQL server】数据库远程连接配置
数据库
zmgst43 分钟前
canal1.1.7使用canal-adapter进行mysql同步数据
java·数据库·mysql
随心............1 小时前
python操作MySQL以及SQL综合案例
数据库·mysql
€☞扫地僧☜€1 小时前
docker 拉取MySQL8.0镜像以及安装
运维·数据库·docker·容器
CopyDragon1 小时前
设置域名跨越访问
数据库·sqlite
xjjeffery1 小时前
MySQL 基础
数据库·mysql
写bug的小屁孩1 小时前
前后端交互接口(三)
运维·服务器·数据库·windows·用户界面·qt6.3
恒辉信达1 小时前
hhdb数据库介绍(8-4)
服务器·数据库·mysql
齐 飞2 小时前
MongoDB笔记01-概念与安装
前端·数据库·笔记·后端·mongodb