数据库面试题

文章目录

  • 数据库并发的场景
  • 事务未提交和提交
  • 怎么解决读-写和写-写问题?
  • 事务的4大特征
  • 事务的隔离级别
  • 并发事务的问题
  • MVVC
    • undo log 和 redo log
  • mysql的锁
    • 全局锁(对数据库加锁)
    • 表锁(对表加锁)
    • 行锁

参考文章: 【MySQL笔记】正确的理解MySQL的MVCC及实现原理 - @作者:SnailMann

数据库并发的场景

读-读:不存在任何问题,也不需要并发控制。
读-写:有线程安全问题,可能会造成事务隔离性问题,可能遇到脏读,幻读,不可重复读
写-写:有线程安全问题,可能会存在更新丢失问题,比如第一类更新丢失,第二类更新丢失

事务未提交和提交

事务未提交时数据存在于数据库系统的缓存中,而在事务提交后,数据才会被写入到磁盘上的数据库文件中。

怎么解决读-写和写-写问题?

简而言之,MVCC 就是因为大佬们,不满意只让数据库采用悲观锁这样性能不佳的形式去解决读-写冲突问题,而提出的解决方案,所以在数据库中,因为有了 MVCC,所以我们可以形成两个组合:
MVCC + 悲观锁
MVCC解决读写冲突,悲观锁解决写写冲突

MVCC + 乐观锁
MVCC 解决读写冲突,乐观锁解决写写冲突

这种组合的方式就可以最大程度的提高数据库并发性能,并解决读写冲突,和写写冲突导致的问题

事务的4大特征

事务的四大特征
原子性:事务不可分割,要么全部成功,要么全部失败
一致性:事务的一致性要求数据库在任何时候都保持一致的状态,不会出现数据的矛盾或不完整。(数据总额不能变)
隔离性:并发执行的事务之间互不干扰。
持久性:事务一旦提交, 其对数据库的修改是永久性的

事务的隔离级别

MySQL支持四种隔离级别,分别有:

读 未提交(read uncommitted)可以读到未提交的数据。会造成脏读,不可重复读和幻读。

读 已提交(read committed)只会读已提交的数据。它能解决脏读的问题的,但是解决不了不可重复读和幻读。

可重复 读(repeatable read)它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。它能解决脏读和不可重复读,但是解决不了幻读,这个也是mysql默认的隔离级别。

第四个是串行化(serializable)这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。

编者按:事务顺序执行,没有并行,完全杜绝幻读。

并发事务的问题

面试官:并发事务带来哪些问题?

候选人

脏读,读到其他事务还未提交的数据。

不可重复读:一个事物先后读取两次同一条记录。在第一次读取后,其他事务修改了该数据,导致第二次读到的数据和第一次不同。

幻读(Phantom read):

select的时候没有发现某条记录,插入的时候又发现记录已经存在,好像自己出现了幻觉一样。

幻读错误的理解

说幻读是 事务A 执行两次 select 操作得到不同的数据集,即 select 1 得到 10 条记录,select 2 得到 15 条记录。这其实并不是幻读,既然第一次和第二次读取的不一致,那不还是不可重复读吗,所以这是不可重复读的一种。
正确的理解

幻读,并不是说两次读取获取的结果集不同,幻读侧重的方面是某一次的 select 操作得到的结果所表征的数据状态无法支撑后续的业务操作。更为具体一些:select 某记录是否存在,不存在,准备插入此记录,但执行 insert 时发现此记录已存在,无法插入,此时就发生了幻读。

MVVC

mvcc即多版本并发控制。指维护一个数据的多个版本,使得读-写操作没有冲突
MVCC的实现靠 记录的3个隐式字段、undo log,Read View来实现。

3个记录的隐式字段:DB_ROW_ID、DB_TRX_ID、DB_ROLL_PTR
DB_ROW_ID(隐式主键):当数据表没有主键时,InnoDB 会以该字段构建b+树。
DB_TRX_ID(事务ID):记录最后一次修改该记录的事务的id。
DB_ROLL_PTR(回滚指针):指向这条记录的上一个版本,配合undo log形成版本链。
数据库面试题_第1张图片

undo log,会通过回滚指针,在内部会形成一个记录的版本链。

read View
read View包含四个核心字段,当前活跃的事务ID的集合、最小的活跃的事务ID、最大的事务ID+1、创建当前Read View的事务ID。
在可重复读的隔离级别下,事务第一次select快照读时才会生成读视图,后续复用该读视图。生成read View后,根据read View中的信息按照一定规则从记录的版本链中读取记录。
大致规则如下:
依次用版本链中的事务ID去和Read View中的信息比较,判断哪条记录能被当前事务访问。
1.事务ID小于最小的活跃的事务ID,那肯定能被当前事务看到。
2.事务ID大于等于最大的事务ID+1,那肯定不能被当前事务看到。
3.判断完上述两条,事务ID肯定在最小和最大之间,然后判断事务ID如果在当前活跃的事务ID的集合中,如果在,说明readView生成之前,你这个事务还在执行,还没提交,那对肯定不能被当前事务看到,不然就是读到未提交的数据了。
如果不在说明在当前事务生成Read View之前,这条记录已经提交了,肯定能被当前事务看到。

示例:
在以下案例中用到的是数据库的默认隔离级别,可重复读。
事务5在第一次快照读时生成read View。根据read View从版本链中读取记录时,事务3对事务5是不可见的,会读到事务2。
在第二次快照读时复用上一次的read View。
根据read View进行第二次读取时,事务4提交的age=10对事务5是不可见的,事务3也是不可见的。还是会读到事务2。
这样就解决了不可重复读问题。
数据库面试题_第2张图片
数据库面试题_第3张图片

undo log 和 redo log

Undo log(回滚日志) 记录了事务对数据所做的修改操作的逆操作,即撤销操作。当事务进行修改时,会先将修改前的数据记录到 undo log 中,以便在事务回滚或者发生异常时能够恢复到事务开始之前的状态。

保证事务的原子性和一致性。

redo log(重做日志)
在执行update操作后,会先去缓冲池中修改对应的数据,然后提交事务后把缓冲池中的数据保存到磁盘文件中。但是这个保存过程可能会发生问题,比如数据库宕机了,内存中的数据没有了,数据就无法保存到磁盘文件中了。这时就需要用到redo log日志,redo log日志会记录所做的修改操作,在数据库宕机恢复后重新执行一遍。

mysql的锁

mysql的锁按粒度分为:全局锁、表锁、行锁。
按功能分为:(共享)读锁、写锁。
获取读锁的事务只能读,不能写(会被阻塞),其他事务也是只能读,不能写。
获取写锁的事务既能读又能写,其他事务既不能读,也不能写(会被阻塞)。
一个获取了读锁之后,其他事务也能获取这个读锁,但不能获取写锁
一个事务获取了写锁之后,其他事务不能获取读锁,也不能获取写锁

全局锁(对数据库加锁)

1.备份数据库时,最好在备份之前先加一个全局读锁。
如果不加全局锁,就会出现不一致的情况。(如备份库存表时,库存还没减去备份完库存表,事务对库存表进行了修改,同时生成了订单,就会出现备份的数据库中库存没减去,但有订单的情况)

1、加全局读锁:flush tables with read lock;
2//在windows的命令窗口输入这行
mysqldump -u root -p 123 > 123备份1.sql
3//接下来输入密码
4、解锁 unlock tables;

在这里插入图片描述
在这里插入图片描述

表锁(对表加锁)

对表加锁 :lock tables table_name read/write;
对表解锁:unlock tables;

行锁

行锁是通过对索引上的索引项加锁而实现的。
select。。。不加锁
select。。。lock in share mode(加锁用分享模式) 手动加读锁
select。。。for update(为了修改加锁)手动加写锁
insert 。。。 自动加读锁
update。。。自动加读锁
delete。。。自动加读锁

你可能感兴趣的:(java数据库面试,数据库)