数据库,事务以及序列化相关的面试题

1 什么是事务?

事务指对数据库的一组操作,这组操作要么都实现,要么都不实现。

常规的数据库均提供了对事务的支持,即数据库本身提供了保证一组操作要么都实现,要么都不实现的功能。使用者要掌握的是操作数据库提供的事务功能的方式,及事务的相关特性。

2 事务的实现方式?

  • 命令式事务

在数据库客户端下,使用SQL语句来操作事务,SQL语句分别是:

start transaction; -- 开启事务
commit; -- 提交事务
rollback; -- 回滚事务

需要特别注意的是,默认情况下,每条SQL语句独占一个事务。

  • 编程式事务

通过JDBC API实现数据库事务操作,API分别是:

conn.setAutoCommit(false); // 开启事务
conn.commit(); // 提交事务
conn.rollback(); // 回滚事务 
  • 声明式事务

利用Spring框架提供的@Transactional注解来实现。

@Transactional默认仅对RuntimeException及其子类异常回滚,因此我们在设计异常时,要求ServiceException继承RuntimeException,以保证事务正常回滚。

可以通过显式声明rollbackfor属性,来配置该注解对Exception回滚:@Transactional(rollbackFor=Exception.class)

@Transactional也可以添加到类上,代表该类中所有的方法都是在事务下运行的,但是不推荐这么做

3 事务的4大特性

事务具备ACID四种特性,ACID是Atomic(原子性)、Consistency(一致性)、Isolation(隔离性)和Durability(持久性)的英文缩写。

  • 原子性(Atomicity)

事务最基本的操作单元,要么全部成功,要么全部失败,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚到事务开始前的状态,就像这个事务从来没有执行过一样。

  • 一致性(Consistency)
    事务执行前后,数据库的完整性是一致的。

数据库的完整性:数据的完整性、业务的完整性。

业务的完整性例子:转账操作,事务前后,原账户和目标账户的金额总和是一致的。

a 转 1000元 给b
转账前:  a余额5000,b余额 6000   a+b=11000
转账后:  a余额4000,b余额 7000   a+b=11000
  • 隔离性(Isolation)
    并发的事务之间,应该保证彼此隔离,互不干扰。

指的是在并发环境中,当不同的事务同时操纵相同的数据时,每个事务都有各自的完整数据空间。由并发事务所做的修改必须与任何其他并发事务所做的修改隔离。事务查看数据更新时,数据所处的状态要么是另一事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看到中间状态的数据。

  • 持久性(Durability)
    指的是只要事务成功结束,它对数据库所做的更新就必须永久保存下来。事务一旦commit,其操作结果就是持久的,后续再进行多次的rollback,也不会改变之前的结果。

4 事务的隔离级别

数据库事务的隔离级别是保证事务隔离性的解决方案。

加锁可以保证完美的隔离,同一时间内只能有一个人操作数据,但是这样一来数据库就相当于工作在单线程的状态下,同一时间只能有一个事务操作,并发的效率非常低下

而现实生活中,并不是所有的场景下都需要那么严格的事务隔离,在不同的业务场景下对隔离性的要求是不同。

所以数据库的设计者设计了不同的隔离级别,让使用者可以在隔离能力和性能间做一个权衡。

事务的隔离级别有4种,由低到高分别为Read uncommitted、Read committed、Repeatable read、Serializable。

事务的隔离级别越高,事务与事务之间彼此的干扰越少,安全性越高,但是消耗的资源也越多。反之,隔离性越低,消耗资源越少,效率越高,但是安全性也越差。

因此,事务的隔离级别不是越高越好,是在当前业务安全性允许的条件下,选择最低的隔离级别。

Mysql数据库的默认隔离级别是:Repeatable read
Oracle数据库的默认隔离级别时:Read committed

脏读、不可重复读、幻象读概念说明:

脏读:一个事务使用了另一个事务未提交的数据。

不可重复读:指在一个事务内,多次读同一数据,得到的结果不同。

幻读:一个事务读取全表数据时,读取到另一个事务向表中新增、删除操作提交的结果

4种隔离级别具体说明:

  1. Read uncoommitted
    读未提交,一个事务可以读取到另一个事务未提交的操作的结果。该级别不保证任何的隔离性。

该隔离级别可能出现脏读、不可重复读、幻读问题。

举例:

例子前的建表语句:

create table account(
	id int primary key auto_increment,
	name varchar(10),
	money int
);

insert into account values(1,'a',1000),(2,'b',1000);

查询数据库隔离级别的SQL语句:
select @@tx_isolation;

设置数据库的隔离级别的SQL语句:
set [session/global] transaction isolation level xxxxxx;
session:修改当前会话的隔离级别,不影响其他会话。当前客户端关闭,则修改失效。
global:修改数据库的默认隔离级别,对所有后续新建的会话生效,对之前已经存在的会话无效。

案例演示:
a:
	set session transaction isolation level read uncommitted;
	---------------
	a: 1000
	b: 1000
	---------------

b:
	start transaction;
	update account set money = money - 100 where name = 'b';
	update account set money = money + 100 where name = 'a';

a:
	start transaction;
	select * from account;
	---------------
	a: 1100
	b: 900
	---------------
	commit;

b:
	rollback;

a:
	start transaction;
	select * from account;
	---------------
	a: 1000
	b: 1000
	---------------
	commit;
  1. Read committed
    读已提交,一个事务仅能读取到另一个事务已提交的数据。

可以保证部分隔离性,可以防止脏读问题,但是具有不可重复读和幻读问题。

不可重复读不一定是问题,仅在一些特殊的场景下,会成为问题。

例子前的建表语句:

create table account2(
	name varchar(10),
	money1 int, -- 现金
	money2 int, -- 活期存款
	money3 int -- 定期存款
);

insert into account2 values('a',1000,1000,1000);

a:
	set session transaction isolation level read committed;
	
	-------------------------
		a 1000 1000 1000
	-------------------------

b:
	start transaction;
	select money1 from account where name = 'a'; -- 活期存款1000
	select money2 from account where name = 'a'; -- 定期存款1000
	select money3 from account where name = 'a'; -- 固定存款1000
	-------------
a:
	start transaction;
	update account2 set money1 = money1 - 1000 where name = 'a';
	commit;
	-------------
b:	
	select money1 + money2 + money3 from account where name = 'a'; -- 总资产2000
  1. Repeatable read
    可重复读,该级别下,一个事务无法读取另一个后开启的事务已提交的操作结果。

可以防止脏读和不可重复读问题,但是会出现幻读问题。

举例:

set session transaction isolation level Repeatable read;

	-------------------------
		a 1000 
		b 1000
	-------------------------
a:
	start transaction;
	select count(*) from account; -- 2人
	select sum(money) from account; -- 2000元
	--------
	d:
		start transaction;
		insert into account values(d 4000);
		commit;
		-------------------------
			a 1000 
			b 1000
			d 4000
		-------------------------
	--------
	select avg(money) from account; -- 2000元
	commit;
  1. Serializable
    保证完全隔离,可以防止脏读,不可重复读,幻读问题,本质上是靠锁来实现的

5 事务的传播机制

在使用Spring的声明式事务,即@Transactional注解时,可以使用@Transactional(propagation = Propagation.REQUIRED)来配置事务的传播行为。

spring在TransactionDefinition接口中定义了七个事务传播行为,也可以通过Propagation枚举类来调用:

REQUIRED(默认):支持使用当前事务,如果当前事务不存在,创建一个新事务。
SUPPORTS:支持使用当前事务,如果当前事务不存在,则不使用事务。
MANDATORY:中文翻译为强制,支持使用当前事务,如果当前事务不存在,则抛出Exception。
REQUIRES_NEW:创建一个新事务,如果当前事务存在,把当前事务挂起。
NOT_SUPPORTED:无事务执行,如果当前事务存在,把当前事务挂起。
NEVER:无事务执行,如果当前有事务则抛出Exception。
NESTED:嵌套事务,如果当前事务存在,那么在嵌套的事务中执行。如果当前事务不存在,则表现跟REQUIRED一样。

6 数据库表中的外键

foreign key约束: A表中的一个字段关联了B表中的主键字段
foreign key的基本作用:A表中该字段的值,必须是B表中主键字段中已经存在的值
在这里插入图片描述
数据库的外键约束可以自动进行关联数据的校验,有利于保证数据库中数据的一致性。但是,外键约束会较大的影响并发查询时的效率,同时不利于项目的可扩展性。

基于上述原因,在大多数互联网企业中,要求数据库中不能显式声明外键约束,而采用逻辑外键的方式来实现。

逻辑外键:使用编程的方式,在程序中实现外键形式的校验。
具体例子:在向角色菜单表中插入关联数据之前,先验证插入的菜单id在菜单表中都有对应的记录。

序列化小面试题

所有的实体类都需要添加序列化接口吗?

  1. 为什么要实现序列化接口?标识该类可以被序列化,并为该类添加一个序列化ID

  2. 序列化是用来做什么的?将Java对象中的数据以固定的格式写入到一个文件中,可以将该文件发送给需要使用对象数据的程序,再使用反序列化,根据该文件,在内存中创建对应的对象。

  3. Web项目中什么时候需要序列化?

     a: 业务层可能被做成第三方接口,供其他程序调用,包括微服务场景,这些场景下,彼此传递的实体类必须实现序列化接口
    
     b: HttpSession中保存的对象,需要实现序列化接口,因为Session执行机制中,包含自动将数据序列化到本地文件保存的机制。
    

综上,很多企业要求所有的实体类都实现序列化接口,增加项目的可扩展性。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值