|
| 1 | +--- |
| 2 | +title: Mysql-面试常见的问题 |
| 3 | +author: DreamCat |
| 4 | +id: 1 |
| 5 | +date: 2019-11-24 12:37:38 |
| 6 | +tags: Sql |
| 7 | +categories: Sql |
| 8 | +--- |
| 9 | +## 引言 |
| 10 | + |
| 11 | +> Mysql,那可是老生常谈了,对于后端同学,那是必须要掌握的呀。 |
| 12 | +
|
| 13 | +<!-- more --> |
| 14 | + |
| 15 | +## 常见问题 |
| 16 | + |
| 17 | +### 数据库引擎innodb与myisam的区别? |
| 18 | + |
| 19 | +[InnoDB和MyISAM比较](http://dreamcat.ink/2019/11/15/sql-notes/1/) |
| 20 | + |
| 21 | +### 4大特性 |
| 22 | + |
| 23 | +[Mysql的ACID原理](http://dreamcat.ink/2019/11/20/sql-notes/1/) |
| 24 | + |
| 25 | +### 并发事务带来的问题 |
| 26 | + |
| 27 | +#### 脏读 |
| 28 | + |
| 29 | + |
| 30 | + |
| 31 | +#### 丢弃修改 |
| 32 | + |
| 33 | +T1 和 T2 两个事务都对一个数据进行修改,T1 先修改,T2 随后修改,T2 的修改覆盖了 T1 的修改。例如:事务1读取某表中的数据A=20,事务2也读取A=20,事务1修改A=A-1,事务2也修改A=A-1,最 终结果A=19,事务1的修改被丢失。 |
| 34 | + |
| 35 | + |
| 36 | + |
| 37 | +### 不可重复读 |
| 38 | + |
| 39 | +T2 读取一个数据,T1 对该数据做了修改。如果 T2 再次读取这个数据,此时读取的结果和第一次读取的结果不同。 |
| 40 | + |
| 41 | + |
| 42 | + |
| 43 | +#### 幻读 |
| 44 | + |
| 45 | +T1 读取某个范围的数据,T2 在这个范围内插入新的数据,T1 再次读取这个范围的数据,此时读取的结果和和第一次读取的结果不同。 |
| 46 | + |
| 47 | + |
| 48 | + |
| 49 | +#### 不可重复度和幻读区别: |
| 50 | + |
| 51 | +不可重复读的重点是修改,幻读的重点在于新增或者删除。 |
| 52 | + |
| 53 | +例1(同样的条件, 你读取过的数据, 再次读取出来发现值不一样了 ):事务1中的A先生读取自己的工资为 1000的操 作还没完成,事务2中的B先生就修改了A的工资为2000,导 致A再读自己的工资时工资变为 2000;这就是不可重复读。 |
| 54 | + |
| 55 | +例2(同样的条件, 第1次和第2次读出来的记录数不一样 ):假某工资单表中工资大于3000的有4人,事务1读取了所 有工资大于3000的人,共查到4条记录,这时事务2 又插入了一条工资大于3000的记录,事务1再次读取时查到的记 录就变为了5条,这样就导致了幻读。 |
| 56 | + |
| 57 | +### 数据库的隔离级别 |
| 58 | + |
| 59 | +1. 未提交读,事务中发生了修改,即使没有提交,其他事务也是可见的,比如对于一个数A原来50修改为100,但是我还没有提交修改,另一个事务看到这个修改,而这个时候原事务发生了回滚,这时候A还是50,但是另一个事务看到的A是100.**可能会导致脏读、幻读或不可重复读** |
| 60 | +2. 提交读,对于一个事务从开始直到提交之前,所做的任何修改是其他事务不可见的,举例就是对于一个数A原来是50,然后提交修改成100,这个时候另一个事务在A提交修改之前,读取的A是50,刚读取完,A就被修改成100,这个时候另一个事务再进行读取发现A就突然变成100了;**可以阻止脏读,但是幻读或不可重复读仍有可能发生** |
| 61 | +3. 可重复读,就是对一个记录读取多次的记录是相同的,比如对于一个数A读取的话一直是A,前后两次读取的A是一致的;**可以阻止脏读和不可重复读,但幻读仍有可能发生。** |
| 62 | +4. 可串行化读,在并发情况下,和串行化的读取的结果是一致的,没有什么不同,比如不会发生脏读和幻读;**该级别可以防止脏读、不可重复读以及幻读。** |
| 63 | + |
| 64 | +| 隔离级别 | 脏读 | 不可重复读 | 幻影读 | |
| 65 | +| ---------------- | ---- | ---------- | ------ | |
| 66 | +| READ-UNCOMMITTED | √ | √ | √ | |
| 67 | +| READ-COMMITTED | × | √ | √ | |
| 68 | +| REPEATABLE-READ | × | × | √ | |
| 69 | +| SERIALIZABLE | × | × | × | |
| 70 | + |
| 71 | +MySQL InnoDB 存储引擎的默认支持的隔离级别是 REPEATABLE-READ(可重读)。我们可以通过 |
| 72 | + |
| 73 | +命令来查看。我们可以通过`SELECT@@tx_isolation;` |
| 74 | + |
| 75 | +这里需要注意的是:与 SQL 标准不同的地方在于InnoDB 存储引擎在 REPEATABLE-READ(可重读)事务隔离级别 下使用的是Next-Key Lock 锁算法,因此可以避免幻读的产生,这与其他数据库系统(如 SQL Server)是不同的。所以 说InnoDB 存储引擎的默认支持的隔离级别是 REPEATABLE-READ(可重读) 已经可以完全保证事务的隔离性要 求,即达到了 SQL标准的SERIALIZABLE(可串行化)隔离级别。 |
| 76 | + |
| 77 | +因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是READ-COMMITTED(读取提交内 容):,但是你要知道的是InnoDB 存储引擎默认使用 REPEATABLE-READ(可重读)并不会有任何性能损失。 |
| 78 | + |
| 79 | +InnoDB 存储引擎在 分布式事务 的情况下一般会用到SERIALIZABLE(可串行化)隔离级别。 |
| 80 | + |
| 81 | +### 为什么使用索引? |
| 82 | + |
| 83 | +- 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。 |
| 84 | +- 可以大大加快数据的检索速度,这也是创建索引的最主要的原因。 |
| 85 | +- 帮助服务器避免排序和临时表 |
| 86 | +- 将随机IO变为顺序IO |
| 87 | +- 可以加速表和表之间的连接,特别是在实现数据的参考完整性方面特别有意义。 |
| 88 | + |
| 89 | +### 索引这么多优点,为什么不对表中的每一个列创建一个索引呢? |
| 90 | + |
| 91 | +- 当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,这样就降低了数据的维护速度。 |
| 92 | +- 索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立簇索引,那么需要的空间就会更大。 |
| 93 | +- 创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加 |
| 94 | + |
| 95 | +### 索引如如何提高查询速度的? |
| 96 | + |
| 97 | +- 将无序的数据变成相对有序的数据(就像查目一样) |
| 98 | + |
| 99 | +### 使用索引的注意事项? |
| 100 | + |
| 101 | +- 在经常需要搜索的列上,可以加快搜索的速度; |
| 102 | +- 在经常使用在where子句中的列上面创建索引,加快条件的判断速度。 |
| 103 | +- 在经常需要排序的列上创建索引,因为索引已经排序,这样查询可以利用索引的排序,加快排序查询时间 |
| 104 | +- 在中到大型表索引都是非常有效的,但是特大型表的维护开销会很大,不适合建索引 |
| 105 | +- 在经常用到连续的列上,这些列主要是由一些外键,可以加快连接的速度 |
| 106 | +- 避免where子句中对字段施加函数,这会造成无法命中索引 |
| 107 | +- 在使用InnoDB时使用与业务无关的自增主键作为主键,即使用逻辑主键,而不要使用业务主键。 |
| 108 | +- **将打算加索引的列设置为NOT NULL,否则将导致引擎放弃使用索引而进行全表扫描** |
| 109 | +- 删除长期未使用的索引,不用的索引的存在会造成不必要的性能损耗 |
| 110 | +- 在使用limit offset查询缓存时,可以借助索引来提高性能。 |
| 111 | + |
| 112 | +### Mysql索引主要使用的两种数据结构 |
| 113 | + |
| 114 | +- 哈希索引,对于哈希索引来说,底层的数据结构肯定是哈希表,因此在绝大多数需求为单条记录查询的时候,可以选择哈希索引,查询性能最快;其余大部分场景,建议选择BTree索引 |
| 115 | +- BTree索引,Mysql的BTree索引使用的是B树中的B+Tree但对于主要的两种存储引擎(MyISAM和InnoDB)的实现方式是不同的。 |
| 116 | + |
| 117 | +### MyISAM和InnoDB实现BTree索引方式的区别 |
| 118 | + |
| 119 | +- MyISAM,B+Tree叶节点的data域存放的是数据记录的地址,在索引检索的时候,首先按照B+Tree搜索算法搜索索引,如果指定的key存在,则取出其data域的值,然后以data域的值为地址读区相应的数据记录,这被称为“非聚簇索引” |
| 120 | +- InnoDB,其数据文件本身就是索引文件,相比MyISAM,索引文件和数据文件是分离的,其表数据文件本身就是按B+Tree组织的一个索引结构,树的节点data域保存了完整的数据记录,这个索引的key是数据表的主键,因此InnoDB表数据文件本身就是主索引。这被称为“聚簇索引”或者聚集索引,而其余的索引都作为辅助索引,辅助索引的data域存储相应记录主键的值而不是地址,这也是和MyISAM不同的地方,在根据主索引搜索时,直接找到key所在的节点即可取出数据;在根据辅助索引查找时,则需要先取出主键的值,在走一遍主索引。因此,在设计表的时候,不建议使用过长的字段为主键,也不建议使用非单调的字段作为主键,这样会造成主索引频繁分裂。 |
| 121 | + |
| 122 | +### 数据库结构优化 |
| 123 | + |
| 124 | +- 范式优化: 比如消除冗余(节省空间。。) |
| 125 | +- 反范式优化:比如适当加冗余等(减少join) |
| 126 | +- 限定数据的范围: 务必禁止不带任何限制数据范围条件的查询语句。比如:我们当用户在查询订单历史的时 候,我们可以控制在一个月的范围内。 |
| 127 | +- 读/写分离: 经典的数据库拆分方案,主库负责写,从库负责读; |
| 128 | +- 拆分表:分区将数据在物理上分隔开,不同分区的数据可以制定保存在处于不同磁盘上的数据文件里。这样,当对这个表进行查询时,只需要在表分区中进行扫描,而不必进行全表扫描,明显缩短了查询时间,另外处于不同磁盘的分区也将对这个表的数据传输分散在不同的磁盘I/O,一个精心设置的分区可以将数据传输对磁盘I/O竞争均匀地分散开。对数据量大的时时表可采取此方法。可按月自动建表分区。 |
| 129 | +- 拆分其实又分垂直拆分和水平拆分: |
| 130 | + - 案例: 简单购物系统暂设涉及如下表: |
| 131 | + - 1.产品表(数据量10w,稳定) |
| 132 | + - 2.订单表(数据量200w,且有增长趋势) |
| 133 | + - 3.用户表 (数据量100w,且有增长趋势) |
| 134 | + - 以mysql为例讲述下水平拆分和垂直拆分,mysql能容忍的数量级在百万静态数据可以到千万 |
| 135 | + - **垂直拆分:** |
| 136 | + - 解决问题:表与表之间的io竞争 |
| 137 | + - 不解决问题:单表中数据量增长出现的压力 |
| 138 | + - 方案: 把产品表和用户表放到一个server上 订单表单独放到一个server上 |
| 139 | + - **水平拆分:** |
| 140 | + - 解决问题:单表中数据量增长出现的压力 |
| 141 | + - 不解决问题:表与表之间的io争夺 |
| 142 | + - 方案:**用户表** 通过性别拆分为男用户表和女用户表,**订单表** 通过已完成和完成中拆分为已完成订单和未完成订单,**产品表** 未完成订单放一个server上,已完成订单表盒男用户表放一个server上,女用户表放一个server上(女的爱购物 哈哈)。 |
| 143 | + |
| 144 | +### 主键 超键 候选键 外键是什么 |
| 145 | + |
| 146 | +#### 定义 |
| 147 | + |
| 148 | +超键:在关系中能唯一标识元组的属性集称为关系模式的超键 |
| 149 | + |
| 150 | +候选键:不含有多余属性的超键称为候选键。也就是在候选键中,若再删除属性,就不是键了! |
| 151 | + |
| 152 | +主键:用户选作元组标识的一个候选键程序主键 |
| 153 | + |
| 154 | +外键:如果关系模式R中属性K是其它模式的主键,那么k在模式R中称为外键。 |
| 155 | + |
| 156 | +#### 举例 |
| 157 | + |
| 158 | +| 学号 | 姓名 | 性别 | 年龄 | 系别 | 专业 | |
| 159 | +| -------- | ------ | ---- | ---- | ------ | -------- | |
| 160 | +| 20020612 | 李辉 | 男 | 20 | 计算机 | 软件开发 | |
| 161 | +| 20060613 | 张明 | 男 | 18 | 计算机 | 软件开发 | |
| 162 | +| 20060614 | 王小玉 | 女 | 19 | 物理 | 力学 | |
| 163 | +| 20060615 | 李淑华 | 女 | 17 | 生物 | 动物学 | |
| 164 | +| 20060616 | 赵静 | 男 | 21 | 化学 | 食品化学 | |
| 165 | +| 20060617 | 赵静 | 女 | 20 | 生物 | 植物学 | |
| 166 | + |
| 167 | +1. 超键:于是我们从例子中可以发现 学号是标识学生实体的唯一标识。那么该元组的超键就为学号。除此之外我们还可以把它跟其他属性组合起来,比如:(`学号`,`性别`),(`学号`,`年龄`) |
| 168 | +2. 候选键:根据例子可知,学号是一个可以唯一标识元组的唯一标识,因此学号是一个候选键,实际上,候选键是超键的子集,比如 (学号,年龄)是超键,但是它不是候选键。因为它还有了额外的属性。 |
| 169 | +3. 主键:简单的说,例子中的元组的候选键为学号,但是我们选定他作为该元组的唯一标识,那么学号就为主键。 |
| 170 | +4. 外键是相对于主键的,比如在学生记录里,主键为学号,在成绩单表中也有学号字段,因此学号为成绩单表的外键,为学生表的主键。 |
| 171 | + |
| 172 | +#### 总结 |
| 173 | + |
| 174 | +**主键为候选键的子集,候选键为超键的子集,而外键的确定是相对于主键的。** |
| 175 | + |
| 176 | +### drop,delete与truncate的区别 |
| 177 | + |
| 178 | +drop直接删掉表;truncate删除表中数据,再插入时自增长id又从1开始 ;delete删除表中数据,可以加where字句。 |
| 179 | + |
| 180 | +1. DELETE语句执行删除的过程是每次从表中删除一行,并且同时将该行的删除操作作为事务记录在日志中保存以便进行进行回滚操作。TRUNCATE TABLE 则一次性地从表中删除所有的数据并不把单独的删除操作记录记入日志保存,删除行是不能恢复的。并且在删除的过程中不会激活与表有关的删除触发器。执行速度快。 |
| 181 | +2. 表和索引所占空间。当表被TRUNCATE 后,这个表和索引所占用的空间会恢复到初始大小,而DELETE操作不会减少表或索引所占用的空间。drop语句将表所占用的空间全释放掉。 |
| 182 | +3. 一般而言,drop > truncate > delete |
| 183 | +4. 应用范围。TRUNCATE 只能对TABLE;DELETE可以是table和view |
| 184 | +5. TRUNCATE 和DELETE只删除数据,而DROP则删除整个表(结构和数据)。 |
| 185 | +6. truncate与不带where的delete :只删除数据,而不删除表的结构(定义)drop语句将删除表的结构被依赖的约束(constrain),触发器(trigger)索引(index);依赖于该表的存储过程/函数将被保留,但其状态会变为:invalid。 |
| 186 | +7. delete语句为DML(Data Manipulation Language),这个操作会被放到 rollback segment中,事务提交后才生效。如果有相应的 tigger,执行的时候将被触发。 |
| 187 | +8. truncate、drop是DDL(Data Define Language),操作立即生效,原数据不放到 rollback segment中,不能回滚 |
| 188 | +9. 在没有备份情况下,谨慎使用 drop 与 truncate。要删除部分数据行采用delete且注意结合where来约束影响范围。回滚段要足够大。要删除表用drop;若想保留表而将表中数据删除,如果于事务无关,用truncate即可实现。如果和事务有关,或老是想触发trigger,还是用delete。 |
| 189 | +10. Truncate table 表名 速度快,而且效率高,因为: truncate table 在功能上与不带 WHERE 子句的 DELETE 语句相同:二者均删除表中的全部行。但 TRUNCATE TABLE 比 DELETE 速度快,且使用的系统和事务日志资源少。DELETE 语句每次删除一行,并在事务日志中为所删除的每行记录一项。TRUNCATE TABLE 通过释放存储表数据所用的数据页来删除数据,并且只在事务日志中记录页的释放。 |
| 190 | +11. TRUNCATE TABLE 删除表中的所有行,但表结构及其列、约束、索引等保持不变。新行标识所用的计数值重置为该列的种子。如果想保留标识计数值,请改用 DELETE。如果要删除表定义及其数据,请使用 DROP TABLE 语句。 |
| 191 | +12. 对于由 FOREIGN KEY 约束引用的表,不能使用 TRUNCATE TABLE,而应使用不带 WHERE 子句的 DELETE 语句。由于 TRUNCATE TABLE 不记录在日志中,所以它不能激活触发器。 |
| 192 | + |
| 193 | +### 视图的作用,视图可以更改么? |
| 194 | + |
| 195 | +视图是虚拟的表,与包含数据的表不一样,视图只包含使用时动态检索数据的查询;不包含任何列或数据。使用视图可以简化复杂的sql操作,隐藏具体的细节,保护数据;视图创建后,可以使用与表相同的方式利用它们。 |
| 196 | + |
| 197 | +视图不能被索引,也不能有关联的触发器或默认值,如果视图本身内有order by 则对视图再次order by将被覆盖。 |
| 198 | + |
| 199 | +创建视图:`create view xxx as xxxx` |
| 200 | + |
| 201 | +对于某些视图比如未使用联结子查询分组聚集函数Distinct Union等,是可以对其更新的,对视图的更新将对基表进行更新;但是视图主要用于简化检索,保护数据,并不用于更新,而且大部分视图都不可以更新。 |
| 202 | + |
| 203 | +### 数据库范式 |
| 204 | + |
| 205 | +#### 第一范式 |
| 206 | + |
| 207 | +在任何一个关系数据库中,第一范式(1NF)是对关系模式的基本要求,不满足第一范式(1NF)的数据库就不是关系数据库。 所谓第一范式(1NF)是指数据库表的每一列都是不可分割的基本数据项,同一列中不能有多个值,即实体中的某个属性不能有多个值或者不能有重复的属性。如果出现重复的属性,就可能需要定义一个新的实体,新的实体由重复的属性构成,新实体与原实体之间为一对多关系。在第一范式(1NF)中表的每一行只包含一个实例的信息。简而言之,**第一范式就是无重复的列**。 |
| 208 | + |
| 209 | +#### 第二范式 |
| 210 | + |
| 211 | +第二范式(2NF)是在第一范式(1NF)的基础上建立起来的,即满足第二范式(2NF)必须先满足第一范式(1NF)。第二范式(2NF)要求数据库表中的每个实例或行必须可以被惟一地区分。为实现区分通常需要为表加上一个列,以存储各个实例的惟一标识。这个惟一属性列被称为主关键字或主键、主码。 第二范式(2NF)要求实体的属性完全依赖于主关键字。所谓完全依赖是指不能存在仅依赖主关键字一部分的属性,如果存在,那么这个属性和主关键字的这一部分应该分离出来形成一个新的实体,新实体与原实体之间是一对多的关系。为实现区分通常需要为表加上一个列,以存储各个实例的惟一标识。简而言之,**第二范式就是非主属性非部分依赖于主关键字**。 |
| 212 | + |
| 213 | +#### 第三范式 |
| 214 | + |
| 215 | +满足第三范式(3NF)必须先满足第二范式(2NF)。简而言之,第三范式(3NF)要求一个数据库表中不包含已在其它表中已包含的非主关键字信息。例如,存在一个部门信息表,其中每个部门有部门编号(dept_id)、部门名称、部门简介等信息。那么在员工信息表中列出部门编号后就不能再将部门名称、部门简介等与部门有关的信息再加入员工信息表中。如果不存在部门信息表,则根据第三范式(3NF)也应该构建它,否则就会有大量的数据冗余。简而言之,第三范式就是属性不依赖于其它非主属性。(我的理解是消除冗余) |
| 216 | + |
| 217 | +### 什么是覆盖索引? |
| 218 | + |
| 219 | +如果一个索引包含(或者说覆盖)所有需要查询的字段的值,我们就称 之为“覆盖索引”。我们知道在InnoDB存储引 擎中,如果不是主键索引,叶子节点存储的是主键+列值。最终还是要“回表”,也就是要通过主键再查找一次,这样就 会比较慢。覆盖索引就是把要查询出的列和索引是对应的,不做回表操作! |
0 commit comments