数据库中的事务
数据库中的事务提交默认开启,可通过命令查询状态,关闭或者开启:
查询命令
show variables like 'autocommit'
关闭命令
set autocommit=off;
开启命令
set autocommit=on;
事务的管理
开启事务:beigin
提交事务:commit
回滚事务:rollback
尤其需要注意的是,增删改默认开启数据库事务,select不涉及任何事务。
在微服务中,当涉及到多步增删改的操作步骤时,一般我们使用dubbo调用其他模块数据库操作时会用到事务,以保证所有的操作要么一起成功,要么一起失败。此时就会为整个业务实现方法增加事务的注解。
数据库中的死锁
死锁在数据访问安全时时有发生,数据库也是数据访问的一种,虽然数据库提供了一些锁机制来保证线程安全,但,有人的地方就有江湖,数据库也无可避免的就会出现死锁的现象。
下面,我们举个例子来说明数据库中的死锁:
用户表:user1
id | name | age |
1 | 张三 | 20 |
2 | 李四 | 30 |
|
|
|
用户表:user2
id | name | age |
1 | 王五 | 15 |
2 | 周六 | 18 |
我们可以想象一下,这俩操作会怎么样,上面我们说过,增删改都会增加排他锁,所以第一步操作都可以执行完成,且添加了排他锁,那么在第二步操作的时候,由于排他锁的存在,需要等待排他锁的释放,会导致事务A和事务B相互等待,无法继续执行,死锁产生。
那么出现死锁之后,数据库是怎么解决的呢?
在实际操作用会出现两种情况,一种是提示死锁,另一种是提示锁等待超时,这是因为数据库版本不一致的问题。
在mariadb中,对死锁是这么处理的:当检测到死锁后,让一端的事务回滚,并提示死锁DeadLock,接着让另一端的事务执行成功,一般是让后执行的事务进行回滚,回滚之后的事务执行失败,并且不会再次主动发起自旋执行。
数据库中的视图
什么是视图?
数据库中的视图是一张虚拟表,用于展示结果集,它并不保存数据,而是从已经存在的表中调取数据,后期如果要执行相同的sql,可以直接调用视图名称。听起来有点像我们对方法的封装,这里是对数据库sql语句的封装,只不过数据库没有方法的概念。
使用场景:我们要多次展示数据库中某张表或几张表中相同的数据,一样的sql语句需要多次使用,这时候,就可以为这个sql创建视图,在需要的地方直接调用视图,从视图中获取数据,简化sql语句。
操作视图:
创建视图:
create view view_name as select xx from xxxx_tb where xxxxxx;
也可以进行连表操作。
调用视图:
select xxx from view_name;
删除视图:
drop view view_name;
你会发现视图的操作和数据库的操作是一样的。
视图的注意事项
视图是对sql语句的封装,不是对查询结果集的封装,视图的存在并不存提高任何查询的效率,只是简化了sql语句;
视图一般只用于查询,不对数据进行写操作,所以不应该对视图进行写操作,但是数据库允许这么做。
视图来源于单表
insert操作 成功
delete操作 成功
update操作 成功
视图来源于多表连查
insert操作 失败
delete操作 失败
update操作
修改一张表中数据 成功
修改多张表中数据 失败
视图中不保存真是的数据,而是来源于真实的表中,所以,当真是表中的数据发生变化,视图数据也会发生变化。
事务隔离级别
事务的隔离级别有四种:
read uncommitted 读未提交
read committed 读已提交
repeatable read 可重复读
serializable 可串行化
读未提交
事务的结束表现为两种情况,一种是事务提交,另一种是事务的回滚,读未提交即表现在事务可以读取这两种状态下的数据,从而产生脏读。
什么是脏读?
脏读就是事务读取到了其他事务未提交或未回滚之后的内容,导致最终读取到的数据不存在,这就叫脏读。
我们想想这种情况怎么发生?
时刻 | 事务A | 事务B |
t1 | begin | begin |
t2 | 查询user.name='张三' | |
t3 | 修改user.name='李四' | |
t4 | 查询到user.name='李四' | |
t5 |
由于某些原因导致事务B失败,rollback | |
t6 | commit,此时表中user.name实际为'张三',读到的为'李四' |
|
... |
很明显,脏读这种情况不应该出现,所以读未提交这种隔离级别不应该被使用,事实上,也没有数据库使用读未提交的隔离级别。要解决这种情况,产生了另一种隔离级别:读已提交。
读已提交
读已提交的出现就是为了解决读未提交的情况,事务职能读取到其他事务提交后或回滚后的内容,解决了脏读问题,这里应该有掌声👏👏。
不过问题来了,这又产生了新的问题:不可重复读!!!
不可重复读是什么?看下表
时刻 | 事务A | 事务B |
t1 | begin | begin |
t2 | 查询user.name='张三' | |
t3 | 修改user.name='李四' | |
t4 | commit | |
t5 |
再次查询user.name='李四' | |
t6 | commit |
|
... |
两次查询发现查询结果不一样?那么是不是有问题,可能我们看着觉得没错,假设两次分别查询的是银行卡余额,第一次100w,第二次0,我只是查了两次,什么都没干,钱没了,那么你觉得问题大不大? 要不要报警?
在Java中,这就是并发线程的安全问题,所以我们也要避免事务访问期间,其他事务对我们访问的数据进行修改。
根据我们前面学到的内容,我们可以给这条数据加上排他锁,这就解决了这个问题,相信大家已经学会了,此时将产生新的隔离级别:可重复读!
可重复读
可重复读这个隔离级别完美吗?看到这里显然没有结束,所以它一定是不完美的,没错,新的问题又产生了:幻读!
幻读是什么?我们用下面这张表来解读下:
时刻 | 事务A | 事务B |
t1 | begin | begin |
t2 | 查询user表所有数据=2条 | |
t3 | 添加一个新用户 | |
t4 | commit | |
t5 |
再次查询useruser表中的数据=3条 | |
t6 | commit |
|
... |
还以查询银行卡为例,第一次查100w,第二次查0,这是不正常的,因为事务A没有做任何处理。这和不可重复读很相似,但两者也有一定的区别,不可重复读是对数据进行修改,幻读是对数据库进行添加操作,两者都是前后查询到的结果不一致导致的。
我们考虑下幻读应不应该存在? 这种情况可以存在,我认为合法,但是我就需要两次读取到的数据保持一致,该怎么办?给表加排他锁!此时,两次查询结果一致。幻读问题解决!
加了锁,新的隔离级别也就产生了:可串行化!
可串行化
可串行化解决了幻读的问题,但是由于需要给整张表加锁,这样,数据的访问效率就大大降低,现在做开发,谁不使用并发的?所以实际开发中可串行化并不常用,只有在执行一些安全性要求极高的操作时才会使用这一等级。
用一张表来表示他们之间的关系:
隔离等级 | 产生脏读 | 产生不可重复读 | 产生幻读 |
读未提交 | true | true | true |
读已提交 | false | true | true |
可重复读 | false | false | true |
可串行化 | false | false | false |
隔离等级的优先级
和我上面给出的顺序是一致的,优先级由低到高分别是:读未提交-->读已提交-->可重复读-->可串行化。就像道生一,一生二,二生三,三生万物。
数据库使用的隔离级别
oracle和sql server 默认的隔离级别为读已提交。
mysql的 默认隔离级别为可重复读。
MVCC
MVCC是多版本并发控制,是用来实现可重复读的。它解决了并发安全的问题,大大提高了并发执行效率,优于加锁。
MVCC的实现分为三部分:
undolog ,用于记录一些信息,我们下面会说到;
mysql中的每张表里隐藏的三个字段
row_id,Innodb引擎提供的隐藏主键,表中没有主键时自动创建,从1开始,自增;
DB_trx_id,表示最后操作这条数据的事务的id;
DB_roll_ptr,表示回滚指针,当前事务操作变更数据时,如果失败,需要回滚到之前的那条数据,这里保存的就是操作前数据的地址。
ReadView