mysql面试题整理
Posted 潜行前行
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了mysql面试题整理相关的知识,希望对你有一定的参考价值。
1 myisam 和 innodb 引擎的区别
- innodb 支持事务,外键,myisam 不支持
- innodb 支持 mvcc ,myisam 不支持
- innodb 支持表锁、行锁,myisam 仅支持表锁
- innodb 必须有主键,myisam 不需要
2 mysql有几种锁
- 按锁粒度划分有三种:表锁、页锁、行锁
- 加锁机制:可分为 悲观锁和乐观锁
- 兼容性:意向所 和 排他锁
- 实现:记录锁、gap锁、next-key锁、插入意向锁
3 事务和锁
- update、delete、insert 无论是否在事务,都加锁。保证数据的一致性
- 快照读:读取的是快照版本,也就是 mvcc 历史版本 readView 里的数据 ,普通的 SELECT 就是快照读
- 当前读:读取的是最新版本,UPDATE、DELETE、INSERT、SELECT … LOCK IN SHARE MODE、SELECT … FOR UPDATE 是当前读,需要加锁
- RC、RR、SERIALIZABLE 级别的隔离,当前读都会需要借助锁实现
- RR 隔离级别需要先 select … for update 加锁进行当前读操作,才能防止幻读
- 对于SERIALIZABLE隔离级别的事务来说,InnoDB规定使用加锁的方式来访问记录
4 什么情况会加锁
- update、delete、insert 无论是否在事务,都加锁。保证数据的一致性
- 在事务里,for update、LOCK IN SHARE MODE 会分别加一个 排他锁和共享锁,直至事务结束
- select 无论是否在事务,都不加锁
5 隔离级别,具体的实现原理是什么
- 读未提交
- 读已提交
- 可重复读
- 串行化
- 原理
- 事务就ACID四个特性
- 原子性:是使用 undo log来实现的,如果事务执行过程中出错或者用户执行了rollback,系统通过undo log日志返回事务开始的状态
- 持久性:使用 redo log来实现,只要redo log日志持久化了,当系统崩溃,即可通过redo log把数据恢复
- 隔离性:通过锁以及MVCC,使事务相互隔离开
- 一致性:通过回滚、恢复,以及并发情况下的隔离性,从而实现一致性
6 redo log 、 undo log 、 binlog
- redo log:
- innodb 为了提高磁盘I/O读写性能,存在一个 buffer pool 的内存空间,数据页读入会缓存到 buffer pool,事务的提交则实时更新到 buffer pool,
而不实时同步到磁盘(innodb 是按 16KB 一页同步的,一事务可涉及多个数据页,实时同步会造成浪费,随机I/O)。
事务暂存在内存,则存在一致性问题,为了解决系统崩溃,保证事务的持久性,我们只需把事务对应的 redo 日志持久化到磁盘即可
- innodb 为了提高磁盘I/O读写性能,存在一个 buffer pool 的内存空间,数据页读入会缓存到 buffer pool,事务的提交则实时更新到 buffer pool,
- undo log:
- 事务需要保证原子性,也是说事务中的操作要么全部完成,要么什么也不做。如果事务执行到一半,出错了怎么办-回滚。但是怎么回滚呢,靠 undo 日志。undo 日志就是我们执行sql的逆操作
- binlog:
- binlog指二进制日志,它记录了数据库上的所有改变,并以二进制的形式保存在磁盘中,它可以用来查看数据库的变更历史、数据库增量备份和恢复、MySQL的复制
- 一条更新语句,redo log 、 undo log 、 binlog的对应更新顺序流程是怎样的
- 数据的更新插入删除都是两阶段提交的,如果 redo 不是两阶段提交;
- redo 先写,binlog 后写,会导致依赖 binlog 同步的从库数据缺失。
- binlog 先写,redo log 后写,则会导致从库多出未提交的脏修改。主从库数据会不一致
7 double insert buffer 、redo log buffer 、change buffer
- double insert buffer:数据页的更新需要两次写doubleWrite,其原因:重做日志是对页层面的物理操作与备份。如果磁盘页坏了的时候,那么用重做日志去重做是没有意义的,所以需要一个副本,在页损坏的时候,用副本页去还原原本的页,然后再进行重做日志
* 其中doubleWrite 原理结构有一个 doubleWrite buffer 和 共享128表空间页的物理磁盘 - change buffer: Change Buffer与 Insert Buffer一样,适用对象还是非唯一的辅助索引
- redo log buffer 类似 buffer pool,它是申请出来的一片连续内存,然后里面划分出了N多个空的redo log block。redo log 刷新到磁盘的时机如下
- 后台线程每隔1秒自动刷盘
- 关闭 mysql 服务
- 记录达到 log buffer 空间一半时刷盘
- 做 checkpoint 的时候
- 事务提交时会刷盘。数据库中 innodb_flush_log_at_trx_commit 参数就控制了在事务提交时,如何将 buffer 中的日志数据刷新到磁盘file中
- 参数值为0:提交事务也不进行刷盘操作
- 参数值为1:提交事务一次就刷盘一次( 默认刷盘策略)
- 参数值为2:每次提交事务时,只会将buffer中的内容写入页面缓存中,不会在将页面缓存中的数据刷盘到file中
8 check point 是怎么确定的
- Sharp CheckPoint : 数据库关闭时,将所有脏页刷新到磁盘,默认的工作方式。但如果运行时,也使用Sharp方式,会对数据库的可用性造成很大影响
- Fuzzy CheckPoint :每次只刷新一部分脏页
- Master ThreadCheckPoint master thread 会以一定的频率从缓冲池的脏页列表中刷新一定比例的页会磁盘,这个过程是异步的,不会阻塞查询
- Flush_lru_list checkPoint
- LRU列表要保证有差不多100个空闲页可用。
- Page Cleaner线程中,会检查LRU列表中是否有足够的可用空间,没有的话,则移除LRU尾部的页,如果这些页中有脏页,则需要CheckPoint
- Async/Sync Flush CheckPoint
- 重做日志不可用的情况下,需要强制将一些页刷回到磁盘,而此时脏页是从脏页列表中选取的。若将已经写入到重做日志的LSN记为redo_lsn
- 用来保证重做日志的循环使用。MySQL 5.6 以后,该操作移到了 Page Cleaner Thread 中,故不会阻塞用户的查询
- Dirty Page too much CheckPoint
- 总的来说,是为了保证缓冲池中有足够可用的页。InnoDB—max_dirty_pages_pct 标识当缓冲池中脏页的数量占据 75%时,强制 CheckPoint,刷新部分脏页回磁盘
9 mysql 优化的切入点
- explain分析sql语句,查看执行计划,优化sql
- 查看是否涉及多表和子查询,优化Sql结构,如去除冗余字段,避免返回不必要的数据
- 优化索引结构,看是否可以适当添加索引
- 数量大的表,可以考虑进行分库/分表
- 数据库主从分离,读写分离
- 查看mysql执行日志,分析是否有其他方面的问题
10 导致索引失效的几种情况
- 查询条件含 or、is null、is not null、in、exists、not in、!=、<> 都可能会导致索引失效
- 列是字符串类型,查询时条件没有使用引号扣起来
- like 前模糊匹配
- 在列字段使用内置函数,或者做加减乘除运算
- innodb 估算走全表扫描比走索引快,大概是 1/3 ?
- 连接查询,两个表用作关联的字段,其编码不一致
- 联合索引,不遵循最左前缀原则
11 varchar(20) 20 有什么意义 和 char(20) 的20 又有什么不同
- vachar(20) 指明其字段长度最大是 20,其长度可变
- char(20),指明其字段长度是 20,长度不可变,多余部分空格填充
- int(10),指明有效长度是10 ,多余部分前面用零填充
12 给字符串列建索引 需要注意什么
- 选择固定前缀子串作为索引,可以节约空间,缺点可能需要多次IO扫描
- 如果不是字符串全长度作为索引,在索引覆盖时仍需要回表查询
- 固定前缀的离散值必须够大,不然导致索引失效
- 倒序存储
- 字符串计算hash值,建立一个hash值的索引列。范围查询时索引失效
13 有哪几种索引
- 索引结构上可分为两种类型
- 聚簇索引
- 非聚簇索引,二级索引
- 一个表中只能拥有一个聚集索引,而非聚集索引一个表可以存在多
- 聚集索引,索引中键值的逻辑顺序决定了表中相应行的物理顺序;非聚集索引,索引中索引的逻辑顺序与磁盘上行的物理存储顺序不同
- 索引是通过二叉树的数据结构来描述的,我们可以这么理解聚簇索引:索引的叶节点就是数据节点。而非聚簇索引的叶节点仍然是索引节点,只不过有一个指针指向对应的数据块
- innodb的索引语法分为五种
- 主键索引:数据列不允许重复,不允许为NULL,一个表只能有一个主键
- 唯一索引:数据列不允许重复,允许为NULL值,一个表允许多个列创建唯一索引
- 普通索引:基本的索引类型,没有唯一性的限制,允许为NULL值
- 全文索引:是目前搜索引擎使用的一种关键技术,对文本的内容进行分词、搜索
- 组合索引:多列值组成一个索引,用于组合搜索,效率大于索引合并
- innodb对索引的优化
- 索引下推:是 MySQL 5.6 引入的, 可以在索引遍历过程中,对索引中包含的字段先做判断,
直接过滤掉不满足条件的记录,减少回表次数 - 最左前缀原则:MySQl建立联合索引时,会遵循最左前缀匹配的原则,即最左优先。如果你建立一个(a,b,c)的联合索引,
相当于建立了(a)、(a,b)、(a,b,c)三个索引 - 覆盖索引:只需要在一棵索引树上就能获取 SQL 所需的所有列数据,无需回表,速度更快
- 索引下推:是 MySQL 5.6 引入的, 可以在索引遍历过程中,对索引中包含的字段先做判断,
14 innodb 索引的底层实现
- innodb 索引分为 B+树索引 和 hash索引。哈希索引是自适应索引,由innodb 是否建立,DBA无法干预
- B+ 和 hash 索引的区别
- B+树可以进行范围查询,Hash索引不能。
- B+树支持联合索引的最左侧原则,Hash索引不支持。
- B+树支持order by排序,Hash索引不支持。
- Hash索引在等值查询上比B+树效率更高。
- B+树使用like 进行模糊查询的时候,like后面(比如%开头)的话可以起到优化的作用,Hash索引根本无法进行模糊查询。
14.1 为啥使用B+作为数据索引,而不是二叉树,或者平衡树,B树
- 二叉树在特殊情况会退化为链表,查询操作是O(n)
- 平衡二叉树,树的高度太高。而mysql的数据持久化在磁盘,读取磁盘IO会非常多。而B+是矮胖型树,磁盘IO小很多
- B树会在非节点存在键值和数据,innodb的页默认是16KB,其高度不比平衡二叉树低多少。而B+在非叶子节点只存键值,树高度很低
- B+树的叶子节点会使用前后指针连起来,那B+树用来范围查找,排序,分组及去重都非常简单快速
15 now() 和 current_date() 有什么区别
- now()精确到时分秒,current_date() 只精确日期
16 blob 和 text 的区别
- Blob 用于存储二进制数据,而 Text 用于存储大字符串
- Blob 值被视为二进制字符串(字节字符串),它们没有字符集,并且排序和比较基于列值中的字节的数值。
- text 值被视为非二进制字符串(字符字符串)。它们有一个字符集,并根据字符集的排序规则对值进行排序和比较
17 建立索引的原则是什么,是否越多越好,为什么
- 数据量少的不适合加索引
- 更新比较频繁的也不适合加索引
- 区分度低的字段不适合加索引(如性别)
- 创建索引和更新索引要耗费时间
- 索引需要占物理空间,除了数据表占用数据空间之外,每一个索引还要占用一定的物理空间
- 以表中的数据进行增、删、改的时候,索引也要动态的维护
- 定义有外键的数据列一般要建立索引
- 在order by或者group by子句中,创建索引需要注意顺序
18 连接查询,innodb 有哪几种优化机制,了解吗
- Index Nested-Loop Join (NLJ)
select * from t1 straight_join t2 on (t1.a=t2.a);
;t2 的字段 a 上有索引- 从表 t1 中读入一行数据 R
- 从数据行 R 中,取出 a 字段到表 t2 里去查找
- 取出表 t2 中满足条件的行,跟 R 组成一行,作为结果集的一部分
- 重复执行步骤 1 到 3,直到表 t1 的末尾循环结束
- 在形式上,这个过程就跟我们写程序时的嵌套查询类似,并且可以用上被驱动表的索引,所以我们称之为“Index Nested-Loop Join”,简称 NLJ
- Simple Nested-Loop Join
select * from t1 straight_join t2 on (t1.a=t2.b);
;t2 的字段 b 上没有索引- 因为 t2.b 没有索引,只能循环遍历比较。 如果 t1 有100行数据,t2 有 1000 行,则需要 100 * 1000 = 10 0000。
- Block Nested-Loop Join
- Simple Nested-Loop Join 太低效了,innodb 会优化成 Block Nested-Loop Join
- 把表 t1 的数据读入线程内存 join_buffer 中,由于我们这个语句中写的是 select *,因此是把整个表 t1 放入了内存
- 扫描表 t2,把表 t2 中的每一行取出来,跟 join_buffer 中的数据做对比,满足 join 条件的,作为结果集的一部分返回
- Block Nested-Loop Join 算在内存操作,速度快很多
- Multi-Range Read,这个优化的主要目的是尽量使用顺序读盘。
- 根据索引 a,定位到满足条件的记录,将 id 值放入 read_rnd_buffer 中
- 将 read_rnd_buffer 中的 id 进行递增排序
- 排序后的 id 数组,依次到主键 id 索引中查记录,并作为结果返回
- Batched Key Access(BKA),(BKA)是对 NLJ 算法的优化
- 我们就把表 t1 的数据取出来一部分,先放到一个临时内存 join_buffer
- 然后按照 MRR 思想批量去 第二张表 t2 根据 ID 顺序查询数据
- BNL 转 BKA
select * from t1 join t2 on (t1.b=t2.b) where t2.b>=1 and t2.b<=2000;
t2.b 没有索引- 一些情况下,我们可以直接在被驱动表上建索引(或者使用临时表),这时就可以直接转成 BKA 算法了
19 buffer pool
- 预读:用于异步将磁盘的页读取到buffer pool中,预料这些页会马上被读取到
- 自适应哈希索引:提升热点等值查询sql的效率
- double write buffer,保障在innodb崩溃的时候,也能保证数据的一致性
- redo log buffer 保证 redo log 刷盘的效率
20 innodb 什么时候会用到临时表
- sort buffer、内存临时表和 join buffer。这三个数据结构都是用来存放语句执行过程中的中间数据,以辅助 SQL 语句的执行的。
其中,我们在排序的时候用到了 sort buffer,在使用 join 语句的时候用到了 join buffer - 临时表的建立有以下几个场景
- union 执行流程
- group by 执行流程
- 尽量让 group by 过程用上表的索引,使用 explain 确认没有 Using temporary 和 Using filesort。此时优化成不使用临时表,不排序
- 如果 group by 需要统计的数据量不大,尽量只使用内存临时表;也可以通过适当调大 tmp_table_size 参数,来避免用到磁盘临时表
- group by 和 order_id 字段不一致时。( group by order_id order by id)
- join 语句,order by 的列不在驱动表里
- distinct + order by
- from 里的子查询
21 mysql 的主从同步整个流程是怎样的
- 主库的更新事件(update、insert、delete)被写到binlog
- 从库发起连接,连接到主库
- 此时主库创建一个binlog dump thread,把binlog的内容发送到从库。
- 从库启动之后,创建一个I/O线程,读取主库传过来的binlog内容并写入到relay log
- 还会创建一个SQL线程,从relay log里面读取内容,从 Exec_Master_Log_Pos 位置开始执行读取到的更新事件,
将更新内容写入到slave的db - 异步复制、半同步和全同步
- 主库执行完提交事务后,立刻异步执行将结果返给给客户端,并不关心从库是否收到并处理。
如果出现从库并未收到处理的情况,还是会有主从数据不一致的问题 - 主库在执行完客户端提交的事务后不是立刻返回给客户端,而是等待至少一个从库接收到并写到 relay log 中才返回给客户端。
相对于异步复制,半同步复制提高了数据的安全性,同时它也造成了一定程度的延迟 - 当主库提交事务之后,所有的从库节点必须收到、并且提交这些事务,然后主库线程才能继续做后续操作
- 主库执行完提交事务后,立刻异步执行将结果返给给客户端,并不关心从库是否收到并处理。
22 mysql 读写分离时,存在主从延迟,有哪几种策略解决
- 网络延迟
- 当sql在进行锁表时,可能会导致大量 sql 积压,未同步到服务器,此时有主从延时
- 如果服务器硬件性能好,可以设置 sync_binlog = 1 , innodb_flush_log_at_trx_commit = 1
- 读写分离减少主库压力
- 如果读从库存在延迟
- 强制读主库
- 写入redis缓存中间件,从 redis 读取最新数据
- 延迟读
23 自适应哈希索引(AHI)了解过吗,innodb 在什么情况会用到 hash index
- MySql数据库中的索引B+树的一般高度为3~4层,所以如果用索引B+树查找的话一般要进行3-4次的查找
- Innodb存储引擎会监控对表上二级索引的查找,如果发现某二级索引被频繁访问,二级索引成为热数据,建立哈希索引可以带来速度的提升
- 自适应哈希索引是通过缓冲池的B+树页来构造的,因此建立的速度很快,不需要对整张表构建哈希索引
- hash自适应索引会占用innodb buffer pool
- 自适应hash索引只适合搜索等值的查询,如 select * from table where index_col=‘xxx’,而对于其他查找类型,如范围查找,是不能使用的
- 哈希索引数据并不是按照索引值顺序存储的,所以也就无法用于排序
24 B+树,它的优点在哪
- B+Tree 的根节点和枝节点中都不会存储数据,只有叶子节点才存储数据
- B+Tree 的每个叶子节点增加了一个指向相邻叶子节点的指针,它的最后一个数据会指向下一个叶子节点的第一个数据,形成了一个有序链表的结构
- 扫库、扫表能力更强(如果我们要对表进行全表扫描,只需要遍历叶子节点就可以了,不需要遍历整棵 B+Tree 拿到所有的数据)
- B+Tree 的磁盘读写能力相对于 B Tree 来说更强,同数据量下磁盘I/0次数更少(根节点和枝节点不保存数据区,所以一个节点可以保存更多的关键字,一次磁盘加载的关键字更多)
- 范围查询和排序能力更强(因为叶子节点上有下一个数据区的指针,数据形成了链表)
25 最左前缀匹配,索引下推,索引覆盖
- 索引下推:是 MySQL 5.6 引入的, 可以在索引遍历过程中,对索引中包含的字段先做判断,
直接过滤掉不满足条件的记录,减少回表次数 - 最左前缀原则:MySQl建立联合索引时,会遵循最左前缀匹配的原则,即最左优先。如果你建立一个(a,b,c)的联合索引,
相当于建立了(a)、(a,b)、(a,b,c)三个索引 - 覆盖索引:只需要在一棵索引树上就能获取 SQL 所需的所有列数据,无需回表,速度更快
26 数据库的乐观锁和悲观锁是什么,怎么实现
- 悲观锁:当前线程在修改,其他线程阻塞等待。select for update 、select in share lock
- 乐观锁:其他线程过来,先放过去修改。当前线程如果看到别的线程没修改过则修改成功,如果别的线程修改过则修改失败或者重试。
读的时候带一个版本号,修改的时候版本等值比较,未改动则修改成功,不相等则失败
27 union all 和 union 有什么区别,哪个效率更好
- Union:对两个结果集进行并集操作,不包括重复行,同时进行默认规则的排序
- Union All:对两个结果集进行并集操作,包括重复行,不进行排序
- UNION 的效率高于 UNION ALL
28 多表关联查询,有什么优化建议不
- 减少子查询,使用 join 代替
- in和not in也不慎重使用的话,会导致全表扫描
- 使用exists而不是in是一个不错的选择
- 避免索引失效
29 超大表数据分页,怎么处理
- select from table where age > 20 limit 1000000,10这种查询其实也是有可以优化的余地的。
这条语句需要load1000000数据然后基本上全部丢弃,只取10条当然比较慢。
可以修改为select from table where id in (select id from table where age > 20 limit 1000000,10)。
这样虽然也load了一百万的数据,但是由于索引覆盖,减少不必要的回表查询 - 如果使用的索引列 ID 是连续的,我们还可以 select * from table where id > 1000000 limit 10
- 业务上限制翻页次数
30 group by 产生的数据是不是有序的
- group by xxx 默认会按 xxx 排序
- 如果不想排序可以加语句 order by null,选择不排序
31 group by 和 order by 怎么优化
- group by 非执行时选取的索引列时,则额外使用临时表并默认排序,所以可以尽量 group by (index_x) 或者 group by xxx order by null
- group by xxx 和 order by yyy 不同的列时,也会使用临时表,尽量使得 xxx == yyy
32 分表分库,怎么分
- 分表方案(水平分表,垂直分表,切分规则hash等)
- 垂直分表:把长度较大且访问不频繁的字段,拆分出来创建一个单独的扩展表 xxx_ext 进行存储
- 水平分表:分成多个结构相同的表,而每个表只占原表一部分数据,然后按不同的条件分散到多个数据库中
- 库内分表:库内分表虽然将表拆分,但子表都还是在同一个数据库实例中,只是解决了单一表数据量过大的问题,并没有将拆分后的表分布到不同机器的库上,还在竞争同一个物理机的CPU、内存、网络IO
- 分库分表:分库分表则是将切分出来的子表,分散到不同的数据库中,从而使得单个表的数据量变小,达到分布式的效果
- 切分规则
- ID RANGE: 从0到10000一个表,10001到20000一个表
- HASH取模: 例如电商的 order 和 order_ext 关联一个 user_id ,然后user_id hash取模,分配到不同的数据库上
- 地理区域:比如按照华东,华南,华北这样来区分业务,七牛云应该就是如此
- 时间:按照时间切分,就是将6个月前,甚至一年前的数据切出去放到另外的一张表,因为随着时间流逝,这些表的数据 被查询的概率变小,所以没必要和“热数据”放在一起
- 分库分表一些问题(事务问题?跨节点Join的问题)
- 分布式事务
- 跨节点Join的问题
- 分页、排序,分组的坑
- 全局唯一主键问题
- 分库分表中间件(Mycat,sharding-jdbc等)
33 MVCC了解过不,它的实现原理是怎样的的
- mvcc 是一系列的 undo log 相连行成的版本视图。它的实现原理主要是依赖数据记录中
DB_TRX_ID、DB_ROLL_PTR、DB_ROW_ID 3个隐式字段,undo log ,read View 来实现的 - MVCC其好处是读不加锁,读写不冲突,并发性能好
- 详解可以看看这篇文章:数据库篇:mysql事务原理之MVCC视图+锁
34 limit 100000 很慢怎么办
- 如果id是连续的,筛选大于上次查询的最大记录ID,再往下limit
- 限制翻页页数
- order by 索引列。再做限制
35 百万级别或以上的数据,你是如何删除的
- 我们想要删除百万数据的时候可以先删除索引
- 然后批量删除其中无用数据
- 删除完成后重新创建索引
以上是关于mysql面试题整理的主要内容,如果未能解决你的问题,请参考以下文章