四、数据库面试题总结
4.1 MySQL
4.1.1 什么是MySQL?
MySQL是一种关系型数据库,在Java企业级开发中非常常用,因为MySQL是开源免费的,并且方便拓展。MySQL的默认端口号是3306。
4.1.2 MyISAM和InnoDB的区别
MyISAM是MySQL的默认数据库引擎(5.5版本之前)。虽然性能极佳,而且提供的大量的特性。但是MyISAM不支持事务和行级锁,而且最大的缺陷就是崩溃后无法安全恢复。
InnoDB(5.5版本后引入,事务性数据库引擎),5.5版本后默认存储引擎为InnoDB。
二者的对比:
是否支持行级锁: MyISAM只有表级锁,而InnoDB支持行级锁和表级锁,默认为行级锁。
是否支持事务和崩溃后的安全恢复: MyISAM强调的是性能,每次查询具有原子性,其执行速度快,但是不提供事务支持。InnoDB提供事务支持,外键等高级数据库功能。具有事务、回滚和崩溃修复能力的事务安全型表。
**是否支持外键: ** MyISAM不支持,InnoDB支持。
是否支持MVCC: 仅InnoDB支持。应对高并发事务,MVCC比单纯的加锁更高效;MVCC只在READ COMMITTED
和REPEATABLE READ
两个隔离级别下工作;MVCC可以使用乐观锁和悲观锁来实现。
不要轻信“MyISAM比InnoDB快”之类的经验之谈,这个结论往往不是绝对的。在很多我们已知场景中,InnoDB的速度都可以比MyISAM快,尤其是用到了聚簇索引,或者需要访问的数据可以放进内存的应用。
4.1.3 索引
MySQL的索引主要有BTree索引和哈希索引。对于哈希索引来说,底层的数据结构就是哈希表,因此在绝大多数需求为单条记录查询的时候,可以选择哈希索引,查询性能最快;其他场景大部分使用BTree索引。
MySQL的BTree索引使用的是B树中的B+Tree,两种引擎对其实现并不相同。
MyISAM: B+Tree叶节点的data域存放的是数据记录的地址。首先按照B+Tree搜索算法搜索索引,如果指定的Key存在,则取出其data域的值,再根据data域的值去查找对应的数据记录。这被称为”非聚簇索引“。
InnoDB: 其数据文件本身就是索引文件。InnoDB表数据文件本身就是主索引。这被称为“聚簇索引”。而其余的索引都作为辅助索引。在根据主索引搜索时,直接找到key所在的结点即可取出数据;在根据辅助索引查找时,则需要先取出主键的值,在走一遍主索引。
1.为什么要给表加上主键?
主键的作用就是把表的数据格式转换成索引(平衡树)的格式存放。
2.为什么加上主键后表查询的数据变快了?
因为加上主键后,表的数据格式转换成B+Tree的结构,整个表变成了一个索引,通过索引可以快速的找到值,但是更新数据时速度将会降低。
3.什么情况下要同时在两个字段上建立索引?
需要通过主键ID值的查找数据行的真实所在, 直接取得叶节点中user_name的值返回即可。 通过这种覆盖索引直接查找的方式, 可以省略不使用覆盖索引查找的后面两个步骤, 大大的提高了查询性能
4.1.4 什么是事务?
事务是逻辑上的一组操作,要么都执行,要么都不执行。
4.1.5 事务的四大特性(ACID)
原子性:事务要么全部完成,要么全部都不完成。
一致性:执行事务前,数据保持一致,多个事务对同一个数据读取的结果是相同的。
隔离性:并发访问数据库时,一个用户的事务不被其他事务所干扰。
持久性:一个事务被提交后,它对数据库中的数据的改变是持久的,即时数据库发生故障也不应该对其有任何影响。
4.1.6 并发事务会带来哪些问题?
读未提交(脏读):当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问到了这个数据,然后使用了这个数据。因为这个数据是还未提交的,那么另一个事务读到的数据就是“脏数据”。
丢失更新:指一个事务读取一个数据时,另一个事务也读取了这个数据,那么在第一个事务中修改该数据,第二个事务也修改了这个数据,这样第一个事务内的修改结果就被丢失,因此称为丢失更新。
不可重复读:指在一个事务内多次读同一个数据,在这个事务还没有结束时,另一个事务也访问了该数据,并对该数据进行了修改,那么在第一个事务的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。
幻读:幻读与不可重复度类似。它发生在一个事务读取了几行数据,接着另一个并发事务插入了一些数据。在随后的查询中,第一个事务就会发现多了一些原来不存在的数据。
4.1.7 事务隔离级别有哪些?MySQL的默认隔离级别是?
SQL标准中定义了四个隔离级别:
读未提交(READ-UNCOMMITTED): 最低的隔离级别,允许读取未提交的数据,可能会导致脏读、不可重复读、幻读。
读已提交(READ-COMMITTED):允许事务读取已提交的数据,可以阻止脏读,但是幻读,不可重复度仍与可能发生。
可重复读(REPEATABLE-READ): 对同一字段的多次读取结果都是一致的,除非数据是被事务本身自己修改,可以防止脏读,不可重复读,但是还是不能防止幻读。
可串行化(SERIALIZABLE): 最高的隔离级别,所有的事务依次执行,这样事务之间就完全不可能产生干扰,可以防止脏读,不可重复读,幻读。
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交 | ✔️ | ✔️ | :✔️ |
读已提交 | ❌ | ✔️ | ✔️ |
可重复读 | ❌ | ❌ | ✔️ |
可串行化 | ❌ | ❌ | ❌ |
MySQL中InnoDB引擎默认支持的隔离级别是REPEATABLE-READ(可重复读),不同的是InnoDB在REPEATABLE-READ级别才用到是Next-Key Lock锁算法,因此可以避免幻读的产生,达到了SERIALIZABLE隔离程度,因为隔离事务级别越低,事务请求的锁越少所以大部分数据库采用COMMITTED-READ,但是InnoDB引擎默认使用的REPEATABLE-READ并不会有任何性能损失。
4.1.8 锁机制和InnoDB锁算法
MyISAM和InnoDB存储引擎使用的锁:
- MyISAM使用的是表级锁
- InnoDB支持行级锁和表级锁,默认使用行级锁
表级锁和行级锁对比:
- 表级锁:MySQL中锁定粒度最大的一种锁,对当前操作的整张表加锁,实现简单,资源消耗也少,加锁快,不会出现死锁。其锁定粒度最大,触发锁冲突的概念最高,并发度最低。
- 行级锁:MySQL中锁定粒度最小的一种锁,只针对当前操作的行进行加锁。行级锁能大大降低锁冲突的概率,并发度高,但是加锁的开销也最大,加锁慢,会出现死锁。
4.1.9 大表优化
-
限定数据范围
查询数据时添加查询范围
-
读/写分离
主库负责写,从库负责读
-
垂直分区
把一张列比较多的表分成多张表
-
水平分区
保持数据表结构不变,通过某种策略存储数据分片。这样每一片数据分散到不同的表或者库中,达到了分布式的目的。
4.1.10 解释一下什么是池化思想。什么是数据库连接池?为什么需要数据库连接池?
池化思想:这种设计会初始预设资源,解决的问题就是抵消每次获取资源的消耗,如创建线程的开销,获取远程连接的开销等。
数据库连接池:数据库连接池可以看做是维护数据库连接的缓存,以便于将来需要对数据库请求时可以常用这些连接。
为什么需要:在连接池中创建连接后,将其放置在池中,并再次使用它,因此不必创建新的连接,如果使用了所有连接,则会创建一个新连接并将其添加到池中。
4.1.11 分库分表之后,主键如何处理
生成全局唯一ID:
- UUID: 不适合当主键,太长,无序不可读,查询效率低,适合生成唯一的名字表示。
- 数据库自增id: 两台数据库使用不同的步长,生成不重复的ID。这种方式生成的ID有序,但是需要独立部署数据库实例,成本高,还会有性能瓶颈。
- 利用redis生成id: 性能比较好,不依赖于数据库。但是引入了新的组件造成系统更加复杂,可用性降低,编码更复杂,增加了系统成本。
- Twitter的snowflake算法
- 美团的Leaf分布式ID生成系统
Redis生成唯一主键:Redis Incr 命令将 key 中储存的数字值增一。如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCR 操作。如果值包含错误的类型,或字符串类型的值不能表示为数字,那么返回一个错误。本操作的值限制在 64 位(bit)有符号数字表示之内。