Java后端高频面试问题:MySQL索引和事务
1.索引
MySQL索引使⽤的数据结构主要有B+树索引 和 哈希索引 。对于哈希索引来说,底层的数据结构就是哈希表,因此在绝⼤多数需求为单条记录查询的时候,可以选择哈希索引,查询性能最快;其余⼤部分场景,建议选择B+树索引。MySQL的BTree索引使⽤的是B树中的B+Tree,但对于主要的两种存储引擎的实现⽅式是不同的。
MyISAM: B+树叶节点的data域存放的是数据记录的地址。在索引检索的时候,⾸先按照B+树搜索算法搜索索引,如果指定的Key存在,则取出其 data 域的值,然后以 data 域的值为地址读取相应的数据记录。这被称为“⾮聚簇索引”。
InnoDB:其数据⽂件本身就是索引⽂件。相⽐MyISAM,索引⽂件和数据⽂件是分离的,其表数据⽂件本身就是按B+Tree组织的⼀个索引结构,树的叶节点data域保存了完整的数据记录。这个索引的key是数据表的主键,因此InnoDB表数据⽂件本身就是主索引。这被称为“聚簇索引(或聚集索引)”。⽽其余的索引都作为辅助索引,辅助索引的data域存储相应记录主键的值⽽不是地址,这也是和MyISAM不同的地⽅。在根据主索引搜索时,直接找到key所在的节点即可取出数据;在根据辅助索引查找时,则需要先取出主键的值,再⾛⼀遍主索引。 因此,在设计表的时候,不建议使⽤过⻓的字段作为主键,也不建议使⽤⾮单调的字段作为主键,这样会造成主索引频繁分裂。
2.B树、B+树、红黑树
B树:
B树是一棵多路平衡查找树,B树的每个节点都存储索引和数据(key和data)
对于一个m阶的B树:
1.每个节点最多有m-1个关键字(可以存有的键值对),根节点最少可以只有1个关键字,非根节点最少有m/2个关键字
关键字范围:根节点[1,m-1] 非根节点[m/2,m-1]
2.每个节点中的关键字都按照从小到大的顺序排列
3.所有叶子节点都位于同一层,或者说根节点到每个叶子节点的长度都相同。
4.每个节点都存有索引和数据,也就是对应的key和value。
B+树:
只有叶子节点存储data,叶子节点包含了这棵树的所有数据,所有的叶子节点使用链表相连,便于区间查找和遍历,所有的非叶子节点起到了索引作用。
B+树也是一棵多路平衡查找树
对于一个m阶的B+树:
1.关键字范围:根节点[1,m-1] 非根节点[m/2,m-1]
2.B+树中非叶子节点不存储数据,只存储索引,数据都存储在叶子节点中。
3.对于非叶子节点中key都按照从小到大的顺序排列, 非叶子节点中的每一个key,都会出现在子节点中,是子节点中最大或最小元素。
叶子节点中的记录也按照key从小到大排列。
4.叶子节点依据关键字的大小从小到大顺序链接,形成一个有序链表。
5.每个结点至多有m个子女;除根结点外,每个结点至少有[m/2]个子女,根结点至少有两个子女;有k个子女的结点必有k个关键字。
6.所有叶子节点都位于同一层,或者说根节点到每个叶子节点的长度都相同。
红黑树:
3.B树与B+树的区别?
1.B树的每个节点都存储key和data,B+树只有叶子节点存储data,叶子节点包含了这棵树的所有数据,这样一个叶子节点可以存储更多的key,可以使树更矮,所以 IO操作的次数更少。
2.B+树中所有的叶子节点构成一个有序链表,可以按照关键子码排序的次序遍历全部记录,由于数据顺序排列并相连,所以编译区间查找和搜索。
而B树则需要进行每一层的遍历,相邻的元素可能在内存中并不相邻。
3.在B树中,当要查找的值恰好在一个非叶子节点时,查找到该节点就会成功并结束查询,而B+树由于非叶子节点只是索引部分,这些节点中只含有其子树中最大或最小关键字,当非终端节点的关键字等于给定值时,查找并不终止,而是继续向下查找直到叶子节点。因此,在B+树中,无论是否查找成功,都是走了一条从根节点到叶子节点的路径。
4.MySQL为什么使用B+树作为索引?
① B+树的磁盘读写代价更低:B+树的内部节点并没有指向关键字具体信息的指针,因此其内部节点相对B树更小,如果把所有同一内部节点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多,一次性读入内存的需要查找的关键字也就越多,相对IO读写次数就降低了。
②B+树的查询效率更加稳定:由于非终结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。
③B+树更便于遍历:由于B+树的数据都存储在叶子结点中,分支结点均为索引,方便扫库,只需要扫一遍叶子结点即可,但是B树因为其分支结点同样存储着数据,我们要找到具体的数据,需要进行一次中序遍历按序来扫,所以B+树更加适合在区间查询的情况,所以通常B+树用于数据库索引。
B+叶子节点依据关键字的大小从小到大顺序链接,形成一个有序链表。
B+树更适合基于范围的查询:B树在提高了IO性能的同时并没有解决元素遍历的我效率低下的问题,正是为了解决这个问题,B+树应用而生。B+树只需要去遍历叶子节点就可以实现整棵树的遍历。而且在数据库中基于范围的查询是非常频繁的,而B树不支持这样的操作或者说效率太低。
5.Hash比B+树更快,为啥mysql还用B+树来存索引?
和具体的业务场景有关,如果只选一个数据,那确实hash更快。但是数据库中经常会选择多条,这时B+树索引就更快了。
而且数据库中索引一般放在磁盘中,数据量大的情况,无法一次装入内存,B+数的设计可以允许数据分批加载,同时树的高度较低,提高查找效率。
6.MySQL的Hash索引和B树索引有什么区别?
Hash索引是将索引键通过Hash运算之后,将 Hash运算结果的Hash值和所对应的行指针信息存放于一个Hash表中,由于不同索引键存在相同Hash值,所以即使取满足某个Hash键值的数据的记录条数,也无法从Hash索引中直接完成查询,还是要通过访问表中的实际数据进行相应的比较,并得到相应的结果。
7.MySQL怎么判断要不要加索引?
8.所有的字段都适合建索引吗?
9.如何评估一个索引创建的是否合理?
10.索引在哪些情况下会失效?
11.如何避免索引失效?
12.如何判断数据库的索引有没有生效?
13.聚簇索引和非聚簇索引的区别?
14.什么是联合索引?
15.MySQL索引有什么优缺点?
16.什么是事务?
17.事务的四大特性(ACID)
①原子性(Atomicity):事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用。
②一致性(Consistency):事务将数据库从一种状态转变为下一种一致性状态。在事务开始之前和事务结束之后,数据库的完整性约束没有被破坏。
③隔离性(Isolation):并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的。
④持久性(Durability):一个事务被提交之后。它对数据库中数据的改变是永久性的,即使数据库发生故障也不应该对其又任何影响。
18.并发事务带来哪些问题?
在典型的应用程序中,多个事务并发运行,经常会操作相同的数据来完成各⾃的任务(多个⽤户对同⼀数据进⾏操作)。并发虽然是必须的,但可能会导致以下的问题。
①脏读(Dirty read):当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问了这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外⼀个事务读到的这个数据是“脏数据”,依据“脏数据”所做的操作可能是不正确的。
②不可重复读(Unrepeatableread): 指在⼀个事务内多次读同⼀数据。在这个事务还没有结束时,另⼀个事务也访问该数据。那么,在第⼀个事务中的两次读数据之间,由于第⼆个事务的修改导致第⼀个事务两次读取的数据可能不太⼀样。这就发⽣了在⼀个事务内两次读到的数据是不⼀样的情况,因此称为不可重复读。
③幻读(Phantom read):幻读与不可重复读类似。它发⽣在⼀个事务(T1)读取了⼏⾏数据,接着另⼀个并发事务(T2)插⼊了⼀些数据时。在随后的查询中,第⼀个事务(T1)就会发现多了⼀些原本不存在的记录,就好像发⽣了幻觉⼀样,所以称为幻读。
不可重复读和幻读区别:
不可重复读的重点是修改⽐如多次读取⼀条记录发现其中某些列的值被修改,幻读的重点在于新增或者删除⽐如多次读取⼀条记录发现记录增多或减少了。
19.事务的隔离级别有哪些?
①READ-UNCOMMITTED(读取未提交):最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。
②READ-COMMITTED(读取已提交):允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
③REPEATABLE-READ(可重复读):(默认)对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
④SERIALIZABLE(可串行化):最高的隔离级别,完全服从ACID的隔离级别。所有的事务依此逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。
MySQL InnoDB 存储引擎的默认⽀持的隔离级别是 REPEATABLE-READ(可重读)。
我们可以通过SELECT @@tx_isolation;命令来查看
这⾥需要注意的是:与 SQL 标准不同的地⽅在于InnoDB 存储引擎在 REPEATABLE-READ(可重读) 事务隔离级别下使⽤的是Next-Key Lock 锁算法,因此可以避免幻读的产⽣,这与其他数据库系统(如SQL Server) 是不同的。所以说InnoDB 存储引擎的默认⽀持的隔离级别是 REPEATABLE-READ(可重读) 已经可以完全保证事务的隔离性要求,即达到了 SQL标准的 SERIALIZABLE(可串⾏化) 隔离级别。因为隔离级别越低,事务请求的锁越少,所以⼤部分数据库系统的隔离级别都是 READ-COMMITTED(读取提交内容) ,但是你要知道的是InnoDB 存储引擎默认使⽤
REPEATABLE-READ(可重读) 并不会有任何性能损失。InnoDB 存储引擎在 分布式事务 的情况下⼀般会⽤到 SERIALIZABLE(可串⾏化) 隔离级别。
20.在可重复读(RR)下解决幻读问题
InnoDB 存储引擎在 REPEATABLE-READ(可重读) 事务隔离级别下使⽤的是Next-Key Lock 锁算法,因此可以避免幻读的产⽣。
方法:是通过next-key lock在当前读事务开启时。
1.给涉及到的行加写锁(行锁),防止写操作;
2.给涉及到的行两端加间隙锁(Gap Lock),防止行新增插入;
从而解决了幻读问题。