数据库面经,续更
1.ACID事务四大特性
数据库事务:事务是并发控制的基本单位。
所谓的事务,它是一个操作序列,由一条或多条sql语句组成。
原子性,隔离性,一致性,持久性
原子性:事务开始后的所有操作,要么全部做完,要么全部不做,不可能停留在中间环节,当执行过程中出错,事务会回滚到事务开始前的阶段,所有的操作就像没发生过一样,也就是说,事务是一个不可分割的整体。
一致性:事务操作前和操作后,数据满足完整性约束,数据库保持一致性状态。
隔离性:在并发环境下,事务之间是相互隔离的,一个事务的执行不能被其他事务干扰,每个事务都有自己的完整空间。
持久性:事务处理结束后,对数据的修改是永久的,即使系统故障也不会丢失。
2.InnoDB引擎是怎么保证事务的四个特性的?
-
持久性是通过 redo log (重做日志)来保证的;
-
原子性是通过 undo log(回滚日志) 来保证的;
-
隔离性是通过 MVCC(多版本并发控制) 或锁机制来保证的;
-
一致性则是通过持久性+原子性+隔离性来保证;
3.数据库的三大范式
-
第一范式1NF:数据库表字段不能在被分割,满足原子性。
-
第二范式2NF:满足第一范式,非主键必须完全依赖于主键列,不能只依赖于主键的一部分。
-
第三范式3NF:满足第二范式,非主键必须直接依赖于主键,不能存在间接依赖。
4.并发事务会引发什么问题?
1.脏读
一个事务读到未提交事务修改过的数据,就意味着发生了脏读现象。事务A读取了事务B更新的数据,然后B回滚操作,那么事务A读到的就是B的脏数据。
2.不可重复读
在一个事务内多次读取同一个数据,前后读到的数据是不一致的,这种现象就称为不可重复读。事务A多次读取同一数据,事务B在事务A读取的过程中,对数据做了更新并提交,导致事务A多次读取同一数据时,结果不一致。
3.幻读
事务A已经更改了表中的所有数据,但事务B又在表中插入一条数据,事务A发现还有一条数据没有更改过来,就跟发生了幻觉了,称为幻读。进行模糊查询,得到结果个数不一致。
5.事务的隔离等级
1.读未提交
一个事务还没提交,他做的变更就能被其他事务看到。
2.读提交
一个事务提交之后,他的变更才能被其他事务看到。
在InnoDB存储引擎中它是通过MVCC实现的,在每次查询前都会重新生成一个Read View。
3.可重复读
一个事务执行过程中看到的数据,跟事务刚启动时看到的数据时一致的,MySQL InnoDB引擎默认的隔离级别。在InnoDB存储引擎中它也是通过MVCC实现的,是在执行第一个查询语句前生成一个Read View,此后事务都一直用这个Read View。
4.串行化
会给记录加上读写锁,当多个事务对同一条记录进行读写操作时,如果发生了读写冲突,后访问的事务必须等前一个事务执行完成,才能继续执行。
6.数据库有哪些锁?
数据库InnoDB引擎主要有三种锁
Record lock(记录锁):单个行记录上的锁
Gap lock(间隙锁):间隙锁,锁定一个范围,不包括记录本身
next-key lock(record+gap):record+gap锁定一个范围,包括记录本身
全局锁
执行后,整个数据库就处于只读的状态了,这时对数据的增删改操作和表的结构的更改操作,都会被阻塞。
全局锁一般用于全库逻辑备份。
表级锁
分为共享锁和排他锁,
当给表加上共享锁时,本线程和其他线程都只能对该表进行查询操作,其他操作都会阻塞;
当给表加上排他锁时,本线程可以对该表进行增删改操作,其他线程的所有操作都会被阻塞;
一般只有MyISAM引擎会用表锁,InnoDB引擎有更细粒度的行。
意向锁
意向共享锁和意向排他锁都是表级锁,不会和行级的共享锁和排他锁冲突,彼此之间也不会冲突,只会跟表级别的共享锁和排他锁冲突;
如果没有加意向锁,那么在加表锁时,就需要遍历表中所有记录,去查看是否有记录已经存在独占锁,这样遍历效率比较慢,所以意向锁的目的就是为了快速判断表里是否有记录被加锁了。
7.数据库中char和varchar的区别
char的长度是不可变的,而varchar的长度是可变的
取数据的时候,char类型的要用trim()去掉多余的空格,而varchar是不需要的。
插入数据超过规定长度,都会返回错误。
char的存取速度还是要比varchar要快得多,因为其长度固定,方便程序的存储与查找;
但是char也为此付出的是空间的代价,因为其长度固定,所以难免会有多余的空格占位符
占据空间,可谓是以空间换取时间效率,而varchar是以空间效率为首位的。
8.一条mysql更新语句是如何执行的
- 客户端与MySQL的Server层建立连接后,建立连接后Server层会获得用户的权限,之后客户端将语句发给Server层 ;
- 解析器根据sql进行词法分析,语法分析,判断sql语句的语句是否正确,然后构建语法树。
- 预处理器检查表或字段是否存在;
- 优化器决定使用哪个索引,如果有多表连接,还会决定各个表的连接顺序,选择最优的执行计划
- 执行器在开始执行语句前,会对用户进行权限检验,判断用户是否有权限对该表进行操作,通过
后,执行器调用存储引擎的接口,根据执行计划执行获得相应的数据,如果存储发现该数据本来就
在buffer pool中,就直接返回给执行器更新,如果记录不在buffer pool中,就将数据页从磁盘读
入到buffer pool中,之后返回记录给执行器。 - 执行器得到数据后,会判断更新前的数据跟更新后的数据是否一样,如果一样就不进行后续更新操
作,如果不一样则就把更新前的数据和更新后的数据都当作参数传给InnoDB,让InnoDB执行更新
记录的操作; - 开启事务,InnoDB更新数据前,首先要记录这条数据的undo log,将undo log写入buffer pool的
Undo页面中,(这条undo log写入操作,也要记录对应的redo log) 8. InnoDB开始更新数据,更新数据的同时记录redo log,将redo log写入到redo log buffer,此时redo log是prepare状态,然后告诉执行器,执行完成了,随时可以提交。 - 执行器收到通知后就生成这个操作的bin log,保存到binlog cache中。
- 执行器调用InnoDB的提交事务接口,InnoDB将redo log的状态改成commit。
9.redo log,undo log, bin log各自的作用
redo log
为了防止数据库发生故障导致数据丢失,当进行增删改操作的时候,InnoDB引擎会将这条记录具体是在哪个数据页、多少偏移量、做了哪些修改记录在redo log后,这样就能保证数据库在重启时能够恢复到故障前的最新状态。
undo log
- 实现事务回滚,保证了事务的原子性,事务处理的过程中,如果出现错误或者我们主动执行了
ROLLBACK语句,MySQL就可以利用undo log中的历史数据将数据恢复到事务开始之前的状态。 - 跟Read View配合实现MVCC(多版本并发控制),MVCC就是通过Read View和undo log的版本链来来实现的,当MySQL执行快照读的时候,可以根据Read View的信息,顺着undo log的版本链找到可见的数据。
bin log
binlog文件是记录了所有数据库表结构和表数据修改的日志,它主要是用于复制,在主从复制中,从库
利用主库上的bin log进行重播,实现主从同步,它还相当于数据库的备份,如果我们不小心将数据库的数据删除了,可以用binlog文件恢复数据.
10.乐观锁与悲观锁
悲观锁
悲观锁就是每次都假设是最坏的情况,每次查询数据时都认为会有其他事务来修改数据,所以每次查询记录时都会利用锁机制给数据加上排他锁,这样其他事务想要操作这条数据就会阻塞等待当前事务的提交。
乐观锁
乐观锁就是每次都假设不会发生并发冲突,不会给数据加锁,只在更新的时候会判断在此期间有没有其他事务更新了这条数据,可以使用版本号机制或者CAS机制实现,乐观锁适用于读多写少的情况。
11.MVCC
MVCC是多版本并发控制,通过数据行的版本链来实现数据库的并发控制,主要是为了解决读写冲突,而且这个读是快照读(注意跟当前读区分开),MVCC是通过undo log和Read View实现的。
首先Read View会有四个字段,分别是
creator_trx_id:它表示的是创建这个Read View的事务ID
trx_ids:它表示的是生成Read View时当前数据库活跃的事务(就是那些未提交的事务)列表
up_limit_id,活跃的事务中最小的事务ID
low_limit_id,当前数据库最大的事务ID+1
然后每个聚簇索引记录都有两个隐藏字段,一个是trx_id,就是记录了当前操作这条记录的事务ID,还有一个roll_pointer,一个指向了它的旧版本的指针,所以每条记录都会有一条版本链,这些旧版本都写在undo log里面。
然后就会有四种情况
-
如果查询的数据当前版本的trx_id等于当前自身事务的trx_id,就说明这条数据被自身事务更新过,所以一定可见;
-
那如果trx_id小于Read View的up_limit_id,表示这个版本的记录是在创建Read View前就已经提交的事务生成的,所以这个版本的记录对当前事务可见;
-
如果trx_id大于等于Read View的low_limit_id,表明这个版本的记录是在创建Read View后生成的,所以这个版本的记录对当前事务不可见;
-
如果trx_id在up_limit_id和low_limit_id之间,就需要判断trx_id是否在trx_ids中
-
如果记录的trx_id在列表中,表明生成该版本记录的事务依旧活跃着,也就是还没提交,所以该版本的记录对当前事务不可见。
-
如果记录的trx_id不在列表中,表明生成该版本记录的事务已经被提交了,所以该版本的记录对当前事务可见。
-
如果隔离级别是读提交的话,每次执行快照读前都会重新生成一个Read View。
如果隔离级别是可重复读的话,在第一次执行快照读前会生成一个Read View,之后整个事务都在用这个Read View。