InnoDB的事务与锁

事务

构成单一逻辑工作单元的操作集合称作事务。例如淘宝买东西,从你账户中扣款到淘宝账户,这就是一个事务,涉及两个读写操作。要么都成功,要么都不成功。
事务具有4大特性:

  • 原子性(atomicity):事务所有操作在数据库中要么全部正确反应,要么完全不反应。
  • 一致性(consistency):隔离执行事务时,保持数据库前后数据的一致性。这是事务程序员的责任。
  • 隔离性(isolation):尽管多个事务并发执行,系统也得确保其中一个事务正常执行而不被来自并发执行的数据库语句干扰。
  • 持久性(durability):事务完成后,对数据库的改变必须是永久的,即使系统出现故障。

简称为事务的ACID特性,下面举个事务的简单模型阐述上述四大特性。

InnoDB默认支持3种隔离级别。介绍等等,写写写写 多写写写。

这里写图片描述

事务隔离级别:

为了保证多个事务对同一个表访问的并发执行,MySQL提供了四个事务隔离性级别,事务调用策略将根据隔离级别配置实现不同的事务执行调度尽量保证并发性。级别从低到高依次是

  • 未提交读(read uncommitted):就是一个事务可以读取另一个未提交事务的数据
  • 已提交的读(read committed):就是一个事务要等另一个事务提交后才能读取数据。
  • 可重复读(repeatabl read):就是在某个事务开始读取数据(事务开启)时,另外事务不再允许修改(update数据)操作。
  • 可串行化(serializable):保证事务的以串行化顺序执行。效率最低的一种事务执行方式,导致事务没有并发的可能性。

隔离级别越低,事务请求的锁越少或持有锁的时间就越短,并发执行效率也就越高,相应的也可能出现脏读、不可重复读、幻读等问题。

未提交的读

Read uncommitted:就是一个事务可以读取另一个未提交事务的数据。

事例:老板要给程序员发工资,程序员的工资是3.6万/月。但是发工资时老板不小心按错了数字,按成3.9万/月,该钱已经打到程序员的户口,但是事务还没有提交,就在这时,程序员去查看自己这个月的工资,发现比往常多了3千元,以为涨工资了非常高兴。但是老板及时发现了不对,马上回滚差点就提交了的事务,将数字改成3.6万再提交。

分析:实际程序员这个月的工资还是3.6万,但是程序员看到的是3.9万。他看到的是老板还没提交事务时的数据。这就是脏读—也就是一个事务读取到了另外一个事务中未提交的数据,显然违背了数据库的隔离性。那怎么解决脏读呢?Read committed!读提交,能解决脏读问题。

已提交的读

Read committed,读提交,只允许读取已提交的数据,这样可以避免脏读,但是可能会造成不可重复读。

事例:singo拿着工资卡去消费,系统读取到卡里确实有2000元,而此时她的老婆也正好在网上转账,把singo工资卡的2000元转到另一账户,并在singo之前提交了事务,当singo扣款时,系统检查到singo的工资卡已经没有钱(第二次检测金额当然要等待老婆转出金额事务提交完),扣款失败,singo十分纳闷,明明卡里有钱,为何……

分析:读提交,若有事务对数据进行更新(UPDATE)操作时,读操作事务要等待这个更新操作事务提交后才能读取数据,可以解决脏读问题。但是出现了我们所说的不可重复读,两个并发的事务,“事务A:singo消费”、“事务B:singo的老婆网上转账”,事务A事先读取了数据,事务B紧接了更新了数据,并提交了事务,而事务A再次读取该数据时,数据已经发生了改变。

不可重复读是指在一个事务内多次读取同一数据集合。在这个事务还没有结束时,另外一个事务也访问该同一数据集合,并做了一些update、更新操作。因此,在第一个事务中的两次读数据之间,由于第二个事务的更新,那么第一个事务两次读到的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的情况,这种情况称为不可重复读。不可重复读和脏读的区别是:脏读是读到未提交的数据,而不可重复读读到的却是已经提交的数据,但是其违反了数据库事务一致性的要求。
一般来说,不可重复读的问题是可以接受的,因为其读到的是已经提交的数据,本
身并不会带来很大的问题。所以大多数数据库的默认级别就是Read committed,比如Sql Server , Oracle。

那怎么解决可能的不可重复读问题?Repeatable read

可重复读

Repeatable read,重复读,就是在开始读取数据(事务开启)时使用行锁,不再允许修改(update)此数据的操作。

事例:程序员拿着信用卡去享受生活(卡里当然是只有3.6万),当他埋单时(事务开启,不允许其他事务的UPDATE修改操作),收费系统事先检测到他的卡里有3.6万。这个时候他的妻子不能转出金额了。接下来收费系统就可以扣款了。

分析:重复读可以解决不可重复读问题。写到这里,应该明白的一点就是,不可重复读对应的是修改,即UPDATE操作。但是可能还会有幻读问题。例如幻读是指事务A读取与搜索条件相匹配的若干行。事务B以插入或删除行等方式来修改事务A的结果集,然后再提交。此时事务A再执行相同的操作,则会出现幻读,这个和不可重复读很相似,都是在一个事务中多次读取到不同的数据。但是不可重复读是对同几个数据,而幻读是检索符合条件的数据时发送。所以幻读问题对应的是插入INSERT操作,而不可重复读对应的是UPDATE操作。

什么时候会出现幻读?
事例:程序员某一天去消费,花了2千元,然后他的妻子去查看他今天的消费记录(全表扫描FTS,妻子事务开启),看到确实是花了2千元,就在这个时候,程序员花了1万买了一部电脑,即新增INSERT了一条消费记录,并提交。当妻子打印程序员的消费记录清单时(妻子事务提交),发现花了1.2万元,似乎出现了幻觉,这就是幻读—-是指在同一事务下,连续执行两次同样的SOL语句可能导致不同的结果,第二次的SOL语句可能会返回之前不存在的行(insert 操作)(如上述老婆先查第一次和第二次查消费记录出现了幻读)。

表t由1、2、5这三个值组成,若这时事务T1执行如下的SQL语句:

SELECT * FROM t WHERE a> 2 FOR UPDATE;

注意这时事务T1并没有进行提交操作,上述应该返回 5 这个结果。若与此同时,
另一个事务 T2插入了4这个值,并且数据库允许该操作,那么事务T1再次执行上述
SQL语句会得到结果4和5。这与第一次得到的结果不同,违反了事务的隔离性,即当前事务能够看到其他事务的结果。这就是幻读,解决的方案就是直接串行化执行即可。

那怎么解决幻读问题?Serializable!

可串行化

Serializable 序列化,Serializable 是最高的事务隔离级别,在该级别下,事务串行化顺序执行,可以避免脏读、不可重复读与幻读。但是这种事务隔离级别效率低下,比较耗数据库性能,一般不使用。

总结表格

√: 可能出现 ×: 不会出现

隔离级别脏读不可重复读幻读
Read uncommitted
Read committed×
Repeatable read××
Serializable×××

解决丢失更新问题

简单地说来,出现下面的情况时,就会发生丢失更新问题:
1)事务T1查询一行数据,放人本地内存,并显示给一个终端用户User1。
2)事务T2也查询该行数据,并将取得的数据显示给终端用户User2 。
3) User1修改这行记录,更新数据库并提交。
4)User2修改这行记录,更新数据库并提交。
数据库任何隔离级别都可以保证,在User1提交之前,User2进行提交会被阻塞。但是这种错误的操作逻辑任然会导致User1的修改被覆盖。

显然,这个过程中用户User1的修改更新操作”丢失”了。

设想银行发生丢失更新现象,例如一个用户账号中有10000元人币。他用两个网上银行的客户端分别进行转账操作。第一次转账9000人民币,因为网络和数据的关系,这时需要等待。但是这时用户操作另一个网上银行客户端,转账1元(同时查询),如果最终两笔操作都成功了,用户的账号余款是9999人民币,第一次转的9000人民币并没有得到更新,但是在转账的另一个账号却会收到这9000元,这导致的结果就是钱变多,而账不平。当网银是绑定USB Key的,不会发生这种情况。但是更重要的是在数据库层解决这个问题,避免任何可能发生丢失更新的情况。
要避免丢失更新发生,需要让事务在这种情况下的操作变成串行化,而不是并行的操作。即在上述四个步骤的 1 )中,对用户读取的记录加上一个排他 X 锁。同样,在步骤 2) 的操作过程中,用户同样也需要加一个排他 X 锁。通过这种方式,步骤 2) 就必须等待一步骤 1 )和步骤 3) 完成,最后完成步骤,从而避免这种逻辑上丢失更新问题的产生,取消并行化操作,直接使得操作串行处理。

上述中如果直接通过UPDATE不读取,则可以解决丢失更新的问题,但是通常应用需要先读写,在进行操作处理,这是就一定得同步,使得事务串行化处理,避免不必要的竞争。
程序员可能在了解如何使用SELECT、INSERT、UPDATE 、DELETE语句后就开始编写应用程序。因此,丢失更新是程序员最容易犯的错误,也是最不易发现的一个错误,因为这种现象只是随机的、零星出现的,不过其可能造成的后果却十分严重。

InnoDB存储引擎的隔离级别实例

脏读

这里写图片描述

不可重复读

这里写图片描述

幻读

这里写图片描述

没有问题

这里写图片描述

特例:InnoDB存储引擎默认支持的隔离级别是REPEATABLE READ,InnoDB存储引擎在REPEATABLE READ事务隔离级别下,使用 Next-Key Lock锁的算法,避免幻读的产生,所以说,InnoDB存储引擎在默认的REPEATABLE READ的事务隔离级别下已经能完全保证事务的隔离性要求,即达到SQL标准的SERIALIZABLE 隔离级别,满足了ACID四大特性。

InnoDB之锁简介

InnoDB通过锁解决了隔离性的问题,通过选择不同的事务隔离级别,可以最大限度的提高事务的并发性。InnoDB存储引擎支持多粒度锁定,也就是允许事务运行在数据行级别的锁和表级别的锁同时存在。

表级别的锁有两种

  • 表级别共享锁:允许事务同时读取多个行。
  • 表级别排他锁:仅仅允许一个事务删除或更新多个行。

行级别的锁有两种

  • 共享锁:允许事务同时读取多个行。
  • 排他锁:仅仅允许一个事务删除或更新多个行。
    如果一个事务T1已经获得了行r的共享锁,那么另外的事务T2可以立即获得行r的共享锁,因为读取并没有改变行r的数据,称这种情况为锁兼容 (Lock Compatible) 。
    但若有其他的事务T3想获得行r的排他锁,则其必须等待事务T1、T2释放行r上的共
    享锁(等待读完再去修改)-这种情况称为锁不兼容。

死锁:死锁是指两个或两个以上的事务在执行过程中,因争夺锁资源而造成的一种互相等待的现象。若无外力作用,事务都将无法推进下去。

InnoDB通过两种机制预防死锁

  • 超时机制:即当两个事务互相等待时,当一个等待时间超过设置的某一阔值时,其中一个事务进行回滚,另一个等待的事务就能继续进行。在InnoDB存储引擎中,参数innod_lock_wait-timeout用来设置超时的时间。缺点是当超时的事务所占权重比较大,如事务操作更新了很多行, 占用了较多的undo log,可能回滚这个事务的时间相对另一个事务所占用的时间可能会很多。
  • 等待图:通过锁的信息链表和等待事务链表构造成一张图,若图中存在回路则代表有死锁。这是一种主动的死锁检测机制。若有回路,则存在死锁,通常来说InnoDB 存储引擎选择回滚undo量最小的事务,以节约时间。深度优先算法。

乐观锁和悲观锁概念

重做日志

事务使用小技巧

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

有时需要偏执狂

请我喝咖啡

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值