数据库事务(ACID特征(原子性、一致性、隔离性、持久性)、丢失数据修改,脏数据,不可重复读,幻读,死锁,活锁)讲解
数据库事务
一、什么是事务
事务就是用户定义的操作序列。这些操作共同构成一个完整的工作单元,一个事务内的操作要么全部执行,要么全部不执行。
简单来说就是这样
举例:熟悉的支付操作有三个步骤
操作1:用户发起支付
操作2:扣除用户余额
操作3:增加商家余额
很明显:这三个操作要么都不执行、要么全部执行。不能出现用户扣钱了,商家没收到钱的情况。事务就是为了避免这种情况造成异常。
二、事务的特征
事务有四大特征:简称ACID特征
- 原子性:保证事务是一个不可分割的整体
- 例:支付操作必须全部完成,不能只完成一部分
- 一致性:使数据库从一个一致性状态转移到另一个一致性状态
- 例:支付操作要么支付成功,要么支付失败。如果只成功一部分那么就要回滚(rollback)至未支付的状态
- 隔离性:不同事务之间不能互相干扰
- 例:你支付时候别人也可以支付。你们两个支付行为不会互相影响
- 持久性:事务一旦提交,对于数据库中数据的改变是永久的
- 例:支付成功后,不能出现你拿着商品走了,商家的钱又跑到你账户上,让商家过把眼瘾的事情
三、并发问题
数据库一个明显的特点就是多用户共享数据库资源。
例:卖车票,同时多个窗口卖同一辆车的票。操作的同一个车票数据
既然并发,那么肯定会带来问题。有四大问题
1、丢失数据修改
A事务B事务读取同一数据之后,A提交数据,B也提交数据,B的提交将A提交的数据覆盖,导致A提交的数据丢失。
例:总共有10张票。A售票员与B售票员读取票数。之后A卖出3张,将剩余7张票数写入数据库。这时候B也卖出5张票,将剩余10-5=5张票写入数据库,将A写入的7张票覆盖。A写入的结果丢了,导致错误发生。
2、读脏数据
事务A读取数据之后写回数据,这时事务B读取A写回的数据,但是由于某种原因A进行了rallback操作,将写操作取消了,数据又恢复了,这时候B拿到的数据就是错误的数据。
例:A售票员读取10张票卖出3张,将结果写回数据库。之后B读取剩余7张票。但是有人要退票,A就回滚了之前写入的结果,数据库中又有10张票,此时B手里的7张票就是错误数据。
3、不可重复读
事务A读取数据后,B修改了数据。之后A再次读取数据,发现两次数据不一样。
例:饭店菜单系统A员工读出红烧肉是20元一份,B老板修改红烧肉的价格为100元一份,此时A又读一次红烧肉的价格为100元。A就懵了,到底是多少钱。A以为只要不重复读取红烧肉的价格就好,于是这种错误就被称为不可重复读。
4、幻读
事务A读取数据后,B修改了数据。之后A再次读取数据,发现两次数据不一样。与不可重复读类似,但表述的方向不同。不可重复读指读取的数据内容,幻读则指读取数据的数量
例:饭店菜单系统A员工读出饭店总共有20种菜品,B老板觉得红烧肉销量不好,就删掉了红烧肉这个菜品,此时A又读一次菜品数量发现只有19种。A就懵了,到底是多少种。A以为自己眼花了出现了幻觉,于是这种错误就叫幻读。
四、并发控制
1、两种基本锁
解决并发问题的办法最主要的方式就是加锁。
基本的锁类型有两种:
- 排它锁:同一时刻只能有一个事务对数据进行读写操作,也称X锁
- 例:去厕所,一个坑。别人用了你不能用你也不能看(变态当我没说),只有人家出来了你才能用
- 共享锁:同一时刻只能有一个事务对数据库进行写操作,但是可以有多个事务进行读操作,也称S锁
- 例:开会签到,一张纸同一时刻只能有一个人签字,但是别的人也可以看看签到表。总不能你霸道的捂住整张表不让人看吧
根据以上特性就可以知道:
- 一个对象可以同时被多个事务加S锁
- 一个对象加X锁,并且还不允许别的事务加任何的锁
2、死锁和活锁
活锁
事务A封锁了数据R,事务B也请求封锁R,B只能等A先释放锁。这是事务C也请求封锁R,C也进入等待状态。当A释放了锁之后,系统又批准了C的锁请求。B就只能继续等待。然后事务D也请求封锁R,系统再次同意了D的请求,B就只能继续等待…forever。这就是活锁
例:AB在食堂排队买饭,A买完走了,C要来插队。B太懦弱就让位了,说要不是看你没吃饭我揍死你。C卖完走了,D又来插队。B无语,只能让开。D走了,B心想:这次到我了吧。此时,E走过来了…forever
死锁
事务A封锁了数据R1,事务B封锁了数据R2。这时A向B索要R2的锁,B向A索要R1的锁,这时就会出现A等B,B等A的状态。形成死锁。
例:A吃中餐,B吃西餐。A拿着B的刀叉,B拿着A的筷子。A:“给我筷子,我就还你刀叉”。B:“你先给我刀叉我再给你筷子”。两人互不相让