mysql面试通关宝典,你看你知道多少

  • 1 存储引擎

1.1)MYISAM:不支持事务、外键,表锁,写锁优先级大于读锁,MyISAM表不太适合于有大量更新操作和查询操作

1.2)InnoDB:支持事务和外键,行锁,带来了脏读,不可重复读,幻读

  • 2 数据类型
  • 3 索引(InnoDB使用的是聚簇索引,MyISM使用的是非聚簇索引)

索引

3.1)聚簇索引:顺序结构存储,索引和数据在一起,找到了索引,就找到了数据

3.2)非聚簇索引

3.3)二级索引:

3.3.1)唯一索引:索引值唯一,不能为空,查找到一条就停止

3.3.2)普通索引:一直查找直到没有满足的

3.3.3)前缀索引:给BLOB,TEXT,或者长varchar建立索引,具体长度创建的时候指定

3.4)B+树索引

3.4.1)B+树,B树的区别

B树每个节点都存储数据,而B+树只有叶子节点才存储数据,同样数据量下,B树高度高,在大数据下IO次数比较多

B树三层可以存放两千万条数据

3.4.2)最左前缀原则

3.5)哈希索引

3.5.1)Hash索引底层是哈希表,适合等值查询,

3.5.2)相对于B+树来说不适合范围查询

3.5.3)不支持多列联合索引的最左匹配规则

3.5.4)如果有大量重复Key,会出现hash冲突

3.5.5)应用场景:

3.5.5.1)哈希索引只包含hash值和行指针,不能作为覆盖索引,不能排序,不支持最左前缀原则。

3.5.5.2)只支持等值查询(=,in),不支持范围查询

3.5.5.3)如果出现hash冲突,需要遍历链表中的所有行指针进行一一比较

3.5.5.4)hash冲突越高,维护索引的代价越高

  • 4 事务

4.1)隔离级别(ACID):

4.1.1)读取未提交:脏读,不可重复读,幻读

4.1.2)读取已提交:不可重复读,幻读

4.1.3)可重复读(默认):幻读

4.1.4)可串行化

  • 5 锁

5.1)粒度锁

5.1.1)行锁:InnoDB是通过给索引上的索引项加锁来实现的,如果没有通过索引条件加锁,InnoDb使用表锁

5.1.2)表锁

5.1.2.1)如果当前事务更新大部分或者全部数据,使用行锁性能差,考虑使用表锁

5.1.2.2)事务涉及多张表,可能造成死锁,引起大量回滚,考虑一次性锁住所有表

5.1.3)页锁: 引擎 BDB

5.2)行锁分类

5.2.1)记录锁:为某行的记录加锁,id为1的记录行会被锁住,id列必须为唯一索引或者主键索引,否则下面语句加的锁会变成临键锁

5.2.2)间隙锁:基于非唯一索引,它锁住的是一段范围内的索引记录。范围条件查询,而不是等值查询,innodb会给已有的数据记录索引项加锁,对于在这个范围内但不存在的记录叫做间隙

给这部分 间隙 加锁 叫做间隙锁。锁定一个范围,但不包括记录本身 RR隔离界别下

间隙锁条件:

1.普通索引锁定

2.使用多列唯一索引

3.使用唯一索引锁住多行数据

5.2.3)临键锁:记录锁+间隙锁,左开右闭合,特殊的间隙锁,通过临键锁可以解决幻读的问题,每个数据行的非唯一索引,都会存一把临键锁,在唯一索引上不存在临键锁 锁定一个范围,并且锁定记录本身

当innodb扫描索引记录时,首先对选中的索引记录加上记录锁,然后对索引记录两遍间隙加上间隙锁

临键锁在以下两个条件时会降级成为间隙锁或者记录锁:

当查询未命中任务记录时,会降级为间隙锁。

当使用主键或者唯一索引命中了一条记录时,会降级为记录锁。

test表中有5条记录,主键值分别为:1,5,8,10,20。那么就会有如下六个间隙:(-∞,1),(1,5),(5,8),(8,10),(10,20),(20,+∞)

select * from test where id>=2 and id<6 for update;

临键锁中锁住的是最后一个命中记录的 key 和其下一个左开右闭的区间那么上面的例子中其实锁住了(1,5]和(5,8]这两个区间。

如果我们执行的查询刚好是id>=2且id<=5,那么就算只锁住了(1,5]

行级锁并不是直接锁记录,而是锁索引,如果一条SQL语句用到了主键索引,mysql会锁住主键索引;

如果一条语句操作了非主键索引,mysql会先锁住非主键索引,再锁定主键索引。

对于快照读来说,幻读的解决是依赖mvcc解决。而对于当前读则依赖于临键锁解决。

ps:

事务A UPDATE table SET name = Vladimir WHERE age = 24;

事务B INSERT INTO table VALUES(100, 26, 'Ezreal');

事务 A 在对 age 为 24 的列进行 UPDATE 操作的同时,也获取了 (24, 32] 这个区间内的临键锁,事务B会陷入阻塞状态。

5.3)读写分类

5.3.1)共享锁

多个事务对于同一数据可以共享一把锁,都能访问到数据,但是只能读不能修改

5.3.2)排它锁

如一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁,包括共享锁和排他锁,但是获取排他锁的事务是可以对数据就行读取和修改。

5.4)状态分类

5.4.1)意向共享锁

事务在获取共享锁前,现获取意向共享锁

5.4.2)意向排它锁

事务在获取排它锁前,现获取意向排它锁

意向锁的存在,是为了协调表锁和行锁的关系,支持多粒度的锁并存。

PS:事务A去修改user表 X行的数据时,首先会给记录X加上一个行排它锁,然后给表加上意向排它锁,这时候如果事务B去给user表加表锁,会被阻塞。避免事务B对整个索引每个节点扫描是否加锁,

  • 6 MVCC

MVCC

同一份数据保存多个版本的方式,实现并发控制,实现了快照隔离

当前读与快照读:加锁的读(insert,update,delete,select for update)为当前读,不加锁的读为快照读

MVCC作用:读写冲突不加锁,解决了脏读,快照读下的幻读,不可重复读

实现原理:三个隐式字段,undo log,ReadView;

隐式字段:事务ID,回滚指针(指向当前记录的上个版本,配合undo log使用),隐藏的自增ID

undo日志:

insert undo log:事务回滚时使用

update undo log:不仅事务回滚时使用,快照读也使用

快照读:事务进行快照读时生成的一个快照,记录并维护系统当前正活跃的事务ID

实现原理:活跃事务列表list,list最小的事务ID min_id,list最大的事务ID+1,max_id(当前快照生成时,尚未分配的事务ID),

当前数据最新事务ID db_tx_id;

1)如果db_tx_id<min_id,说明当前快照能看到这个事务的数据

2)如果db_tx_id>=max_id,说明当前数据最新改动是在快照生成后进行的,所以看不到

3)如果db_tx_id在(min_id.max_id)之间,判断db_tx_id是否在list中,如果在说明事务还没有提交,所以不能看到;

如果不在,说明生成ReadView之前就commit事务了,所以能看到

RC,RR级别下的InnoDB快照读

RC:每个快照读都会生成并获取最新的Read View,所以会看到其他事务提交的数据

RR:同一个事务下,第一个快照读才会生成Read View,以后的快照读会使用同一个Read View

存在的问题:

当前读的幻读依然存在,可通过Next-Key Lock解决

如何解决幻读

1)如何解决幻读

2)MVCC+next-key locks:next-key locks由record locks(索引加锁) 和 gap locks(间隙锁,每次锁住的不光是需要使用的数据,还会锁住这些数据附近的数据)

  • 7 sql优化流程

7.1)建表使用哪种数据引擎

建表字段定义什么类型,长度多少,一般要求有默认值,是否需要加索引

如果添加联合索引,考虑下使用场景,按照联合索引的顺序写sql,尽量符合最左前缀原则

覆盖索引可以避免回表查询,所以尽量避免select *

给字符串加索引可以考虑下前缀索引,

对于前几位区分度不高的字段,可以考虑倒序存储,

[email protected]或者长字符串可以使用hash字段

7.2)sql:

避免select *用到啥就查啥

使用 join替换子查询

join,order by,group by后面的字段加索引

where 语句后面避免使用函数、表达式,!=

要想使用or,又想让索引生效,只能将or条件中的每个列都加上索引

索引失效:

1、where后面使用函数、表达式,or,!=

2、on 后面的字段类型不一样

3、复合索引不满足最左原则

4、某个列重复类型太多,0/1

5、字段进行NULL值判断

6、列类型与where条件类型不一样

7、查询数据太多,in也可能不走索引

8 其他

8.1)redolog:确保事务的持久性。防止在发生故障的时间点,尚有脏页未写入磁盘,

在重启mysql服务的时候,根据redo log进行重做,从而达到事务的持久性这一特性,循环写入。

undo log:保存了数据的上一个版本,可用于回滚,同时提供了多版本并发下的快照读,

bin log:用于复制,在主从复制中,从库利用主库上的binlog进行重播,实现主从同步。

两阶段提交:redo log->prepare,binlog写入磁盘,redo log commit

redolog 刷盘:
innodb_flush_log_at_trx_commit

0、日志放在redolog buffer中,不写入redolog file,每隔一秒,redolog buffer缓存到redo log file中

1、每次提交事务都提交到磁盘

2、先缓存到oscache中,再有系统进行fsync

8.2)change buffer

8.2.1)更新来了,如果当前更新所在的数据页在内存,直接更新,如果不在,缓存到change buffer中,等下次来了,读取到内存中更新

merge 触发条件 >定期刷新 change buffer到ibdata中进行物理保存

对于非聚集索引(非唯一)的插入和更新有效

适用场景:写多查少,因为如果立刻查看会频繁的触发merge

8.2.2)唯一索引,如果数据页不在buffer pool中,需要去磁盘中读取,判断有没有冲突,所以不适用change buffer

8.2.3)普通索引:在buffer pool中直接更新,不使用changebuffer,不在内存中直接将更新记录在change buffer中

8.3)innodb的恢复机制应该加上change buffer 最后为 change buffer+redo log+binlog

8.4)5.6之后索引下推:不需要多个回表 一边遍历 一边判断,在非主键索引上的优化,可以有效减少回表的次数

8.5)更新数据流程

8.5.1)事务开始,从内存或磁盘读取这条数据,返回给server执行器

8.5.2)记录undo log

8.5.3)调用存储api,将结果修改到buffer pool中

8.5.4)记录到redo log,将这条数据记录为prepare

8.5.5)写入bin log

8.5.6)commit提交事务

8.5.7)将redo log事务状态设置为commit

8.5.8)等待工作线程刷新

查询过程

1.客户端发送一条查询给服务器

2.服务期先检查是否命中缓存,命中缓存,则立即返回缓存结果,否则执行下一步

3.服务器进行sql解析,预处理,再由优化器生成对应的执行计划

4.MySQL根据执行计划,调用存储api执行查询,结果返回客户端

8.6)ACID保证

8.6.1)原子性:undo log

8.6.2)一致性:通过两阶段提交,保证redo log,binlog一致性

8.6.3)隔离性:锁(MVCC)

8.6.4)持久性:redo log(数据写入buffer pool,没有同步到磁盘中,mysql挂了),bin log

8.7)autoincrement关键字 高并发

8.7.1)AUTO-INC:执行插入的时候,表级别加一个AUTO-INC锁,当前语句插入完成后释放锁

8.7.2)轻量级锁,在为插入语句生成AUTO_INCREMENT修饰的列分配递增的值时获取该锁,在数值分配完成后就释放该锁,使用这种方式,必须清楚插入语句具体的插入条数。采用轻量级锁,可能会造成不能事务,插入的数据有交叉

8.8)mysql主从不一致

8.8.1)单个库读写分离,一主多从,主写从读,分散压力。这样从库压力比主库高,保护主库

8.8.2)直接禁用slave端的binlog

8.9)为什么建议不要用mysql的Text和Blob类型

8.9.1)在遇到临时表的时候无法使用内存临时表,只能在磁盘上创建临时表

8.9.2)分配页的时候,存储空间有一定的浪费。

8.9.3)行溢出数据禁用了自适应哈希索引,如果作为 where 条件时必须完整的比较整个列

8.9.4)排序只能使用部分前缀索引

8.9.5)数据量太大,会导致 InnoDB 每个数据页中存放的行数减少,从而影响对页面的缓存

8.10)SQL优化方法

8.10.1)善用explain查询SQL执行计划

8.10.2)select语句指定字段名称

8.10.3)如果排序字段没有用到索引,就尽量少排序

8.10.4)如果限制条件中其他字段没有索引,尽量少用or

8.10.5)尽量用union all代替union

8.10.6)in用exists

8.10.7)使用合理的分页方式以提高分页的效率(id> 替换limit)

8.10.8)数据量大可以分段查询

8.10.9)where不用null判断,like,表达式

8.10.10)合理使用索引,组合索引,满足最左前缀原则

8.11)mysql 主从原理

8.11.1)主库有一个log dump线程,给从库传递binlog

8.11.2)从库有两个线程,一个是IO线程,一个是SQL线程。IO线程是为了接收主库binlog,写到本地relaylog中,SQL线程读取relaylog,并解析成sql逐个执行,在本地重放,使得数据节点和主节点保持一致。

8.11.3)IO线程,SQL线程进入睡眠状态,等待下一次被唤醒。

8.12)Mysql优化思路

8.12.1)explain

查看sql执行计划,用到了哪些索引,哪些索引没有用到

8.12.2)索引优化

8.12.2.1)避免范围查询,运算符,方法查询,!=,避免对字段进行null值判断

8.12.2.2)如果查询的数据列在索引中,可以考虑使用覆盖索引,减小回表次数,

8.12.2.3)多列索引(最左前缀原则),前缀索引

8.12.2.4)索引排序

8.12.2.5)删除没必要的索引

8.12.2.6)避免select *,SELECT *增加很多不必要的消耗(cpu、io、内存、网络带宽);增加了使用覆盖索引的可能性;当表结构发生改变时,前断也需要更新。所以要求直接在select后面接上字段名。

8.12.2.7)区分in,exists

区分in和exists主要是造成了驱动顺序的改变(这是性能变化的关键),如果是exists,那么以外层表为驱动表,先被访问,如果是IN,那么先执行子查询。所以IN适合于外表大而内表小的情况;EXISTS适合于外表小而内表大的情况。

8.12.2.8) 在一些用户选择页面中,可能一些用户选择的时间范围过大,造成查询缓慢。主要的原因是扫描行数过多。这个时候可以通过程序,分段进行查询,循环遍历,将结果合并处理进行展示。

8.12.2.9)不建议使用%模糊匹配,对于双%%匹配的,可以将字段添加为全文索引,然后使用match来匹配

8.12.2.10)关于join优化。

尽量使用inner join,避免使用left join,如果一定使用left join,则用小表来驱动大表

inner join是由mysql选择驱动表,但是有些特殊情况需要选择另个表作为驱动表,比如有group by、order by等「Using filesort」、「Using temporary」时,可以使用STRAIGHT_JOIN来强制连接顺序,在STRAIGHT_JOIN左边的表名就是驱动表,右边则是被驱动表

8.12.3)特定查询优化

8.12.3.1)count()查询,尽量使用count(*)来查询行数,count(字段) 查询的字段列非空的条数

8.12.3.2)优化limit分页

SELECT id FROM t WHERE id > 10000 LIMIT 10;

8.12.3.3)减少使用union,union all,尽量用union all代替union

8.12.3.4)order by 排序优化

加大max_length_for_sort_data系统变量:当我们返回的所有数据最大长度小于这个值的时候,MySQL会选择改进后的排序算法,而不是默认算法

去掉不必要的返回字段

增大sort_buffer_size的设置,增大 sort_buffer_size 并不是为了让 MySQL 可以选择改进版的排序算法,而是为了让 MySQL 可以尽量减少在排序过程中对需要排序的数据进行分段,因为这样会造成 MySQL 不得不使用临时表 来进行交换排序

8.12.3.5)group by优化

尽可能让 MySQL 可以利用索引来完成 GROUP BY 操作,当然最好是松散索引扫描的方式最佳

当无法使用索引完成 GROUP BY 的时候,由于要使用到临时表且需要 filesort,所以我们必须 要有足够的 sort_buffer_size 来供 MySQL 排序的时候使用,而且尽量不要进行大结果集的 GROUP BY 操作,因为如果超出系统设置的临时表大小的时候会出现将临时表数据 copy 到磁盘上面再进行 操作,这时候的排序分组操作性能将是成数量级的下降

8.12.4)表设计优化

8.12.4.1)表字段非空

8.12.4.2)采用合适的数据类型对整数类型指定宽度

通过选用更“小”的数据类型减少存储空间,使查询相同数据需要的 IO 资源降低

通过合适的数据类型加速数据的比较

8.12.4.3)表列不要太多

如果太多,可以考虑拆表

8.12.4.4)垂直拆分,水平拆分,

8.12.4.5)增加冗余列,增加统计列

8.12.5)读写分离

8.12.6)Innodb表锁优化

尽可能让所有的数据检索都通过索引来完成,从而避免 Innodb 因为无法通过索引键加锁而升级 为表级锁定

合理设计索引,让 Innodb 在索引键上面加锁的时候尽可能准确,尽可能的缩小锁定范围,避免 造成不必要的锁定而影响其他 Query 的执行

尽可能减少基于范围的数据检索过滤条件,避免因为间隙锁带来的负面影响而锁定了不该锁定 的记录

尽量控制事务的大小,减少锁定的资源量和锁定时间长度

在业务环境允许的情况下,尽量使用较低级别的事务隔离,以减少 MySQL 因为实现事务隔离级 别所带来的附加成本

8.12.7)高效的模型设计

适度冗余 - 让 Query 尽两减少 Join

大字段垂直分拆 - summary 表优化

大表水平分拆 - 基于类型的分拆优化

统计表 - 准实时优化

8.12.8)EXPLAIN解析SQL执行计划

首先看下 type 这列的结果,如果有类型是 ALL 时,表示预计会进行全表扫描(full table scan)。通常全表扫描的代价是比较大的,建议创建适当的索引,通过索引检索避免全表扫描。此外,全索引扫描(full index scan)的代价有时候是比全表扫描还要高的,除非是基于InnoDB表的主键索引扫描

看下 Extra 列

Using temporary,表示需要创建临时表以满足需求,通常是因为GROUP BY的列没有索引,或者GROUP BY和ORDER BY的列不一样,也需要创建临时表,建议添加适当的索引。

Using filesort,表示无法利用索引完成排序,也有可能是因为多表连接时,排序字段不是驱动表中的字段,因此也没办法利用索引完成排序,建议添加适当的索引。

Using where,通常是因为全表扫描或全索引扫描时(type 列显示为ALL 或 index),又加上了WHERE条件,建议添加适当的索引。

8.13)MySQL主键递增

MySQL 表使用主键递增,每次插入的时候,都会插入索引的后续位置,当一页写满,就会开辟新的一页,这样就形成一个紧凑的数据结构,近似填满,每次插入的时候,不需要移动现有的数据结构,因此效率高,也不会在维护索引上增加很多开销

8.14)Mysql数据磁盘不足,不停机扩容方案

添加一个磁盘容量大的节点设置为从节点,进行同步主库上的数据,形成数据库一主双从集群。

在关闭主节点,切换大容量从库2为主节点之前,先把应用写的数据插入到rabbitmq,读的数据读原始从库1

切换大容量从库2为主库,修改应用配置读写都走该主库(大容量从库2),修原始从库1配置同步主库(大容量从库2)的数据

将消息队列rabbitmq中的数据插入到现在的主库中

8.15)大表加字段不锁表方案

Mysql在5.6版本之前,直接修改表结构的过程中会锁表,具体的操作步骤如下:

(1)首先创建新的临时表,复制原表结构,并新增新字段

(2)然后把原表中数据导入到临时表

(3)删除原表

(4)最后重命名临时表为原表名

ORDER BY 排序算法

最佳实践

利用索引实现数据排序的方法是 MySQL 中实现结果集排序的最佳做法,可以完全避免因为排序 计算所带来的资源消耗。

扩展:

默认一直有的排序算法。取出满足过滤条件的用于排序条件的字段以及可以直接定位到行数据的行指针信息,在 Sort Buffer 中进行实际的排序操作,然后利用排好序之后的数据根据行指针信息返回表中取得客户端请 求的其他字段的数据,再返回给客户端

MySQL4.1 版本才开始增加的改进版排序算法。根据过滤条件一次取出排序字段以及客户端请求的所有其他字段的数据,并将不需要排序的字 段存放在一块内存区域中,然后在 Sort Buffer 中将排序字段和行指针信息进行排序,最后再利用 排序后的行指针与存放在内存区域中和其他字段一起的行指针信息进行匹配合并结果集,再按照顺 序返回给客户端

扩展的两种算法优先使用哪种呢,是通过max_length_for_sort_data参数控制

感谢阅读,更多的java课程学习路线,笔记,面试等架构资料,需要的同学可以私信我(资料)即可免费获取!

猜你喜欢

转载自blog.csdn.net/hahazz233/article/details/125490532
今日推荐