【9. Mysql常见面试题(三)】

说几条 MySQL 对 SQL 的执行做的优化手段

​ 1、对 SQL 语句的优化,MySQL 会对我们的 SQL 语句做重写,包括条件化简, 比如常量传递、除没用的条件等等还会将一些外连接转换为内连接,然后选择 成本最低的方式执行对 IN 子查询会进行物化、物化表转连接查询、转换为半连接等方式进行

​ 2、在 SQL 语句的执行过程中MySQL 引入了索引条件下推。比如 where 后面的多个搜索条件都使用到了一个二级索引列,这些搜索条件中虽然出现了索 引列,有些却不能使用到索引,像 like ‘%…’查询,MySQL 为了避免不必要的回表, 从二级索引取得的索引记录,先做条件判断,如果条件不满足,则该二级索引记 录不会去回表,这样可以大量的减少回表操作的随机 IO 成本。

​ 3、在回表操作上,因为每次执行回表操作时都相当于要随机读取一个聚簇 索引页面,而这些随机 IO 带来的性能开销比较大。MySQL 中提出了一个名为 Disk-Sweep Multi-Range Read (MRR,多范围读取)的优化措施,即先读取一部分二 级索引记录,将它们的主键值排好序之后再统一执行回表操作。

​ 4、MySQL 在一般情况下执行一个查询时最多只会用到单个二级索引,但存在有特殊情况,也可能在一个查询中使用到多个二级索引,称之为:索引合并, 比如 Intersection 交集合并、Union 索引合并等等

InnoDB 引擎的三大特性是什么?

InnoDB 的三大特性是:Buffer Pool、自适应 Hash 索引、双写缓冲区。

​ 自适应 Hash 索引,InnoDB 存储引擎内部自己去监控索引表,如果监控到某 个索引经常用,那么就认为是热数据,然后内部自己创建一个 hash 索引,称之 为自适应哈希索引( Adaptive Hash Index,AHI),创建以后,如果下次又查询到 这个索引,那么直接通过 hash 算法推导出记录的地址,直接一次就能查到数据。

​ InnoDB 存储引擎使用的哈希函数采用除法散列方式,其冲突机制采用链表 方式。

​ Buffer Pool,为了提高访问速度,MySQL 预先就分配/准备了许多这样的空间,为的就是与 MySQL 数据文件中的页做交换,来把数据文件中的页放到事先 准备好的内存中。数据的访问是按照页(默认为 16KB)的方式从数据文件中读lea取到 buffer pool 中。Buffer Pool 按照最少使用算法(LRU),来管理内存中的页。(LRU: 最近最少使用算法, LFU: 最近最少次使用算法)

Buffer Pool 实例允许有多个,每个实例都有一个专门的 mutex 保护。Buffer Pool 中缓存的数据页类型有: 索引页、数据页、undo 页、插入缓冲(insert buffer)、 自适应哈希索引、InnoDB 存储的锁信息、数据字典信息(data dictionary)等等。

​ 双写缓冲区,是一个位于系统表空间的存储区域,在写入时,InnoDB 先把 从缓冲池中的得到的 page 写入系统表空间的双写缓冲区。之后,再把 page 写 到.ibd 数据文件中相应的位置。如果在 page 写入数据文件的过程中发生意外崩 溃,InnoDB 在稍后的恢复过程中在 doublewrite buffer 中找到完好的 page 副本用 于恢复。

​ doublewrite 是顺序写,开销比较小。所以在正常的情况下, MySQL 写数据 page 时,会写两遍到磁盘上,第一遍是写到 doublewrite buffer第二遍是从 doublewrite buffer 写到真正的数据文件中。

它的主要作用是为了避免 partial page write(部分页写入)的问题。因为 InnoDB 的 page size 一般是 16KB,校验和写入到磁盘是以 page 为单位进行的。 而操作系统写文件是以 4KB 作为单位的,每写一个 page,操作系统需要写 4 个 块,中间发生了系统断电或系统崩溃,只有一部分页面是写入成功的。这时 page 数据出现不一样的情形,从而形成一个"断裂"的 page,使数据产生混乱。

redolog 和 binlog 的区别是什么?

参考下一题

MySQL 崩溃后的恢复为什么不用 binlog?

​ 1、这两者使用方式不一样

​ binlog 会记录表所有更改操作,包括更新删除数据,更改表结构等等,主要 用于人工恢复数据,而 redo log 对于我们是不可见的,它是 InnoDB 用于保证 crash-safe 能力的,也就是在事务提交后 MySQL 崩溃的话,可以保证事务的持久性,即事务提交后其更改是永久性的。

​ 一句话概括:binlog 是用作人工恢复数据,redo log 是 MySQL 自己使用, 用于保证在数据库崩溃时的事务持久性。

​ 2、redo log 是 InnoDB 引擎特有的,binlog 是 MySQL 的 Server 层实现的, 所有引擎都可以使用。

​ 3、redo log 是物理日志,记录的是“在某个数据页上做了什么修改”,恢复 的速度更快;binlog 是逻辑日志,记录的是这个语句的原始逻辑,比如“给 ID=2 这的 c 字段加 1 ”,binlog 有三种日志记录格式 Row、SQL、混合模式。

​ 4、redo log 是“循环写”的日志文件,redo log 只会记录未刷盘的日志,已 经刷入磁盘的数据都会从 redo log 这个有限大小的日志文件里删除。binlog 是 追加日志,保存的是全量的日志。

​ 5、最重要的是,当数据库 crash 后,想要恢复未刷盘但已经写入 redo log 和 binlog 的数据到内存时,binlog 是无法恢复的。虽然 binlog 拥有全量的日志, 但没有一个标志让 innoDB 判断哪些数据已经入表(写入磁盘),哪些数据还没有。

​ redo log 不一样,只要刷入磁盘的数据,都会从 redo log 中抹掉,数据 库重启后,直接把 redo log 中的数据都恢复至内存就可以了。

MySQL 如何实现事务的 ACID?

参考下一题

InnoDB 事务是如何通过日志来实现的?

​ 总的来说,事务的原子性是通过 undo log 来实现的,事务的持久性性是通 过 redo log 来实现的,事务的隔离性是通过读写锁+MVCC 来实现的。

事务的一致性通过原子性、隔离性、持久性来保证。也就是说 ACID 四大特 性之中,C(一致性)是目的,A(原子性)、I(隔离性)、D(持久性)是手段,是为了保 证一致性(所以也经常有人称为CAID),数据库提供的手段。数据库必须要实现 AID 三大特性,才有可能实现 一致性。同时一致性也需要应用程序的支持,应用程序在事务里故意写出违反约 束的代码,一致性还是无法保证的,例如,转账代码里从 A 账户扣钱而不给 B 账户加钱,那一致性还是无法保证。

​ 至于 InnoDB 事务是如何通过日志来实现的,简单来说,因为事务在修改页 时,要先记 undo,在记 undo 之前要记 undo 的 redo, 然后修改数据页,再 记数据页修改的 redo。 Redo(里面包括 undo 的修改) 一定要比数据页先持 久化到磁盘。

​ 当事务需要回滚时,因为有 undo,可以把数据页回滚到前镜像的状态,崩 溃恢复时,如果 redo log 中事务没有对应的 commit 记录,那么需要用 undo把该事务的修改回滚到事务开始之前。如果有 commit 记录,就用 redo 前滚到 该事务完成时并提交掉

​ 更详细的回答是:

redo 通常是物理日志,记录的是页的物理修改操作,用来恢复提交事务修 改的页操作。而 undo 是逻辑日志,根据每行记录进行记录,用来回滚记录到某 个特定的版本。

​ 当事务提交之后会把所有修改信息都会存到 redo 日志中。redo 日志由两部 分组成,一个是在内存里的 redo log buffer,另一个是在磁盘里的 redo log 文件

​ mysql 为了提升性能不会把每次的修改都实时同步到磁盘,而是会先存到 Buffer Pool(缓冲池)里头,把这个当作缓存来用。然后使用后台线程去做缓冲池 和磁盘之间的同步。

​ 系统重启后读取 redo log 恢复最新数据。虽然 redo log 会在事务提交前做一 次磁盘写入但是这种 IO 操作相比于 buffer pool 这种以页(16kb)为管理单位 的随机写入,它做的是几个字节的顺序写入,效率要高得多。

​ redo log buffer 中的数据,会在一个合适的时间点刷入到磁盘中。

​ 这个合适的时间点包括:

​ 1、MySQL 正常关闭的时候;

​ 2、MySQL 的后台线程每隔一段时间定时的将 redo log buffer 刷入到磁盘, 默认是每隔 1s 刷一次;(定时)

​ 3、当 redo log buffer 中的日志写入量超过 redo log buffer 内存的一半时, 即超过 8MB 时,会触发 redo log buffer 的刷盘;(定量)

​ 4、当事务提交时,根据配置的参数 innodb_flush_log_at_trx_commit 来决定是否刷盘。要严格保证数据不丢失,必须得保证 innodb_flush_log_at_trx_commit 配置为 1。

​ redo log 在进行数据重做时,只有读到了 commit 标识,才会认为这条 redo log 日志是完整的,才会进行数据重做,否则会认为这个 redo log 日志不完整, 不会进行数据重做。

undo log 和 redo log 记录物理日志不一样,它是逻辑日志。可以认为当 delete 一条记录时,undo log 中会记录一条对应的 insert 记录(反向操作, 如果update则依然是update),反之亦然,当 update 一条记录时,它记录一条对应相反的 update 记录。当执行回滚时,就可以从 undo log 中的逻辑记录读取到相应的内容并进行回滚。

​ 而事务的隔离性,也可以通过 undo log 来实现的:当读取的某一行被其他事务锁定时(行读锁),它可以从 undo log 中分析出该行记录以前的数据是什么,从而提供该 行版本信息帮助用户实现一致性非锁定读取,这也是 MVCC 的实现机制的组成 部分。

什么是当前读和快照读?

当前读

​ 像 select lock in share mode(共享锁), select for update ; update, insert ,delete(排他锁)这些操作都是一种当前读,就是它读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。是 一种悲观锁的实现。

快照读

​ 像不加锁的 select 操作就是快照读,即不加锁的非阻塞读;快照读的前提是 隔离级别不是串行级别,串行级别下的快照读会退化成当前读;之所以出现快照 读的情况,是基于提高并发性能的考虑,快照读的实现是基于多版本并发控制, 即 MVCC。

什么是 MVCC?

​ MVCC (Multi-Version Concurrency Control) ,叫做基于多版本的并发控制协议。 他是和 LBCC(Lock-Based Concurrency Control)基于锁的并发控制概念是相对的。 MVCC 是乐观锁的一种实现方式,它在很多情况下,避免了加锁操作,降低了开 销;既然是基于多版本,即快照读可能读到的并不一定是数据的最新版本,而有 可能是之前的历史版本。

​ MVCC 最大的好处:**读不加锁,读写不冲突。**在读多写少的 OLTP 应用中, 读写不冲突是非常重要的,极大的增加了系统的并发性能,现阶段几乎所有的 R-DBMS 包括 MySQL,都支持了 MVCC。

MVCC 的底层实现原理是什么?

​ MVCC 实现原理主要是依赖记录中的隐式字段,undo 日志 ,Read View 来 实现的。

​ MySQL 中每行记录除了我们自定义的字段外,还有数据库隐式定义的 DB_TRX_ID,DB_ROLL_PTR,DB_ROW_ID 等字段。DB_TRX_ID 是最近修改(修改/插入) 事务 ID记录创建这条记录/最后一次修改该记录的事务 ID。DB_ROLL_PTR,回 滚指针,用于配合 undo 日志,指向这条记录的上一个版本

不同事务或者相同事务的对同一记录的修改,会导致该记录的 undo log 成为 一条记录版本线性表,也就是版本链。

​ 事务进行快照读操作的时候产生一个 Read View,记录并维护系统当前活跃 事务的 ID,因为当每个事务开启时,都会被分配一个 ID, 这个 ID 是递增的,所 以最新的事务,ID 值越大。

Read View 主要是将要被修改的数据的最新记录中的 DB_TRX_ID(即当前事 务 ID)取出来(如果当前没有事务就是一个0),与系统当前其他活跃事务的 ID 去对比(由 Read View 维护), 如果 DB_TRX_ID 跟 Read View 的属性做了某些比较,不符合可见性,那就通过 DB_ROLL_PTR 回滚指针去取出 Undo Log 中的 DB_TRX_ID 再比较,即遍历链表的 DB_TRX_ID(从链首到链尾,即从最近的一次修改查起),直到找到满足特定条 件的 DB_TRX_ID, 那么这个 DB_TRX_ID 所在的旧记录就是当前事务能看见的最新 老版本。

RC,RR 级别下 Read View 生成时机的不同,造成 RC,RR 级别下快照读的结果 的不同。RC 隔离级别下,是每个快照读都会生成并获取最新的 Read View,也就 是说事务中,每次快照读都会新生成一个快照和 Read View, 这就是我们在 RC 级 别下的事务中可以看到别的事务提交的更新的原因;而在 RR 隔离级别下,则是 同一个事务中的第一个快照读才会创建 Read View, 之后的快照读获取的都是同一个 Read View,快照读生成 Read View 时,Read View 会记录此时所有其他活动 事务的快照,这些事务的修改对于当前事务都是不可见的。而早于 Read View 创 建的事务所做的修改均是可见。

什么是锁?MySQL 中提供了几类锁?

​ 锁是实现数据库并发控制的重要手段,可以保证数据库在多人同时操作时能 够正常运行。**MySQL 提供了全局锁、行级锁、表级锁。**其中 InnoDB 支持表级 锁和行级锁,MyISAM 只支持表级锁。

什么是全局锁、共享锁、排它锁?

全局锁就是对整个数据库实例加锁,它的典型使用场景就是做全库逻辑备份。 这个命令可以使整个库处于只读状态。使用该命令之后,数据更新语句、数据定 义语句、更新类事务的提交语句等操作都会被阻塞。

​ 共享锁又称读锁 (read lock),是读取操作创建的锁。其他用户可以并发读取 数据,但任何事务都不能对数据进行修改(获取数据上的排他锁),直到已释放 所有共享锁。当如果事务对读锁进行修改操作,很可能会造成死锁。

​ 排他锁 exclusive lock(也叫 writer lock)又称写锁。

MySQL 中的表锁有哪些?

​ MySQL 里表级锁有两种:**普通表级锁、元数据锁(**meta data lock)简称 MDL 和 AUTO-INC 锁。表锁的语法是 lock tables t read/write。

​ 可以用 unlock tables 主动释放锁,也可以在客户端断开的时候自动释放。 lock tables 语法除了会限制别的线程的读写外,也限定了本线程接下来的操作对 象。

​ 对于 InnoDB 这种支持行锁的引擎,一般不使用 lock tables 命令来控制并 发,毕竟锁住整个表的影响面还是太大。

​ MDL:不需要显式使用,在访问一个表的时候会被自动加上。

​ MDL 的作用:保证读写的正确性。

​ 在对一个表做增删改查操作的时候,加 MDL 读锁;当要对表做结构变更操 作的时候,加 MDL 写锁。

​ 读锁之间不互斥,读写锁之间,写锁之间是互斥的,用来保证变更表结构操 作的安全性。

​ AUTO-INC 锁,也就是在执行插入语句时就在表级别加一个 AUTO-INC 锁(所以我们一般使用自己的id生成策略),然 后为每条待插入记录的 AUTO_INCREMENT 修饰的列分配递增的值。

InnoDB 引擎的行锁是怎么实现的?

InnoDB 是基于索引来完成行锁,在锁的算法实现上有三种:

  • Record lock:单个行记录上的锁

  • Gap lock:间隙锁,锁定一个范围,不包括记录本身

  • Next-key lock:record+gap 锁定一个范围,包含记录本身

​ Gap 锁设计的目的是为了阻止多个事务将记录插入到同一范围内,而这会导 致幻读问题的产生,innodb 对于行的查询使用 next-key lock,Next-locking keying 是 Record lock 和 Gap lock 的组合。当查询的索引含有唯一属性时,将 next-key lock 降级为 record key。

​ 有两种方式显式关闭 gap 锁 ,第一种. 将事务隔离级别设置为 RC ;第二种. 将参数 innodb_locks_unsafe_for_binlog 设置为 1。

谈一下 MySQL 中的死锁

​ 死锁是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互 相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态 或系统产生了死锁。

​ 如何查看死锁?

​ 使用命令 show engine innodb status 查看最近的一次死锁。

​ InnoDB Lock Monitor 打开锁监控,每 15s 输出一次日志。使用完毕后建议 关闭,否则会影响数据库性能。

​ 对待死锁常见的两种策略:

​ 通过 innodblockwait_timeout 来设置超时时间,一直等待直到超时;

​ 发起死锁检测,发现死锁之后,主动回滚死锁中的某一个事务,让其它事务 继续执行。

简述下 MySQL8 中的新增特性有哪些

​ MySQL8 在功能上的我们需要关注增强主要有:1、账户与安全;2、索引 3、 InnoDB 增强。

​ 主要表现在:

​ 1、用户的创建与授权需要两条单独的 SQL 语句执行。认证插件更新。密码 管理和角色管理发生变化。

​ 2、隐藏索引,被隐藏的索引不会被优化器使用,但依然真实存在,主要用 于软删除,可以根据需要后续真正删除或者重新可视化。

开始真正支持降序索引,以往的 MySQL 虽然支持降序索引,但是写盘的时 候依然是升序保存。MySQL8.0 中则是真正的按降序保存。

​ 不再对 group by 操作进行隐式排序。

索引中可以使用函数表达式,创建表时创建一个函数索引,查询的时候使用 同样的函数就可以利用索引了。

​ 3、原子 ddl 操作,MySQL5.7 执行 drop 命令 drop table t1,t2; 如果 t1 存在, t2 不存在,会提示 t2 表不存在,但是 t1 表仍然会被删除,MySQL8.0 执行同样 的 drop 命令,会提示 t2 表不存在,而且 t1 表不会被删除,保证了原子性。

自增列持久化,解决了之前的版本,主键重复的问题。MySQL5.7 及其以前 的版本,MySQL 服务器重启,会重新扫描表的主键最大值,如果之前已经删除过 id=100 的数据,但是表中当前记录的最大值如果是 99,那么经过扫描,下一条 记录的 id 是 100,而不是 101。MySQL8.0 则是每次在变化的时候,都会将自增 计数器的最大值写入 redo log,同时在每次检查点将其写入引擎私有的系统表。 则不会出现自增主键重复的问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

岁月玲珑

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值