2022.06.22文章升级废话这篇文章的名字可以简单的是《事务操作:从入门到放弃》。尝试解决:在MySQL5.5及之后的版本中,使用完整的交易过程和详细的交易记录,而不必面对网上散落的交易笔记。实践-基础首先,在你的空数据库(比如Test保留数据库)上创建一个测试表,它有两个字段:id和text(varchar50)。现在模拟事务顺序查询,占用ID=2/5行,两个事务几乎同时执行的情况:SETAUTOCOMMIT=0atterminalAandterminalB;SETAUTOCOMMIT=0;SELECTtextFROMtestWHEREid=2FORUPDATE;无UPDATEtestSETtext='UioYang'WHEREid=5;SELECTtextFROMtestWHEREid=5FORUPDATE;无无UPDATEtestSETtext='UioSun'WHEREid=2;当A事务执行FORUPDATE查询时,ID2行已经被锁定,B事务也通过UPDATE锁定了ID5行。因此,虽然两个事务都成功执行了第一步,但它们需要的另一半数据行已经被对方占用了。.因此,当任何事务进行下一步时,都会收到下一步的行已被占用,从而导致等待,但由于双方都在等待对方,所以一方必须放弃自己的事务。这种错误状态称为死锁,可以通过解锁相关内容来杀死死锁。MySQL本身也会进行死锁检测(如果开启了innodb_deadlock_detect配置),会在超时后回滚某个事务,以保证另一个事务可以进行。为此,事务可能不得不考虑重试。在传统的微型逻辑中,这种情况很少发生。尽量减少逻辑中涉及的数据,使用自动提交;伪原子业务越复杂,出现死锁的可能性就越高,所以如果有必要,请在业务的某个点对所有后续需要的数据加锁——比如使用Redis预先锁定后续相关的ID。这是事务的基本演示,最后通过ROLLBACK或者COMMIT,就可以完成事务的结束。实践——锁上一部分,你完成了一个事务的基本流程,开始,进行,最后得到结果(可能是意想不到的结果)。至少我在上一部分的结尾,我脑子里有两个问题:我听说过事务锁,它通过锁来实现独占目标,并在完成修改后释放其独占权,但是如何我设置它的水平?锁阻塞时间是多久?我怎样才能检测到它?当然,对于有另一种思路的编程玩家,我也会在本节最后放上目前支持锁的优缺点对比。行级锁、页级锁、表级锁。听到名字就知道是什么意思,比较少见的有:页级锁,锁住一组相邻的数据。不同的MySQL引擎对锁级别的支持不同。以最常用的InnoDB为代表,默认使用行级锁,也支持表级锁,但这是有条件的。只有在对索引SQL进行操作时,才会使用Row-level锁,否则该操作将使用table-level锁。表级锁的锁数量最多,占用内存最多,但是在做内部处理时,其运行速度相当快,几乎不会出现死锁问题,所以在中大型内部处理机制中,表级锁的应用场景比行级锁更大。行级锁分为共享锁和排他锁(exclusivelocks,翻译区别)。允许读的共享锁是默认锁,而独占锁是不允许读写的完全占有——废话。共享锁(S):允许一个事务读取一行,防止其他事务获得对同一数据集的独占锁。独占锁(X):允许获取独占锁的事务更新数据,防止其他事务读写同一数据集。另外,为了让行锁和表锁共存,实现多粒度的锁机制,InnoDB内部还有两种意向锁(IntentionLocks),都是表锁。意向共享锁(IS):事务打算给数据行加行共享锁,事务在给数据行加共享锁之前必须先获取表的IS锁。意向排他锁(IX):事务打算给数据行加排他锁,事务在给数据行加排他锁之前必须先获得表的IX锁。页面级锁(gaplock)@gaoyulong说:gap锁被吃了吗?我为此感到抱歉。间隙锁是一个非常重要的锁。当对非唯一索引进行操作时,会锁定一组索引,以保证区间的可用性。如果是唯一索引,精确匹配,则直接进行行锁。对于键值在条件范围内但不存在的记录,称为“间隙(GAP)”,InnoDB也会锁定这个“间隙”。这种锁定机制就是所谓的间隙锁。InnoDB使用间隙锁的目的一方面是为了防止幻读,另一方面是为了满足相关隔离级别的要求。对具有AutoID100数据的表执行ID102的查询。如果不使用间隙锁,如果其他事务插入IDs大于100的任意一条记录,事务再次执行上述语句就会出现幻读;另一方面是为了满足其恢复和复制的需要。本段内容来自:Gaplock(Next-Keylock)-xiaobluesky在此基础上,如果在查询锁表时对不存在的ID进行Insert操作,会导致等待阻塞。除了DB本身的分类外,额外的锁在框架层面可以分为乐观锁和悲观锁。注意这种锁属于应用程序设计的锁,不是数据库设计的锁。以我最熟悉的Yii2框架为例。简要说明:乐观锁是一个比较序列号,但是在高频并发中存在比较错位的bug;悲观锁是一个严格的可比序列号,并提供解锁功能。事实上,由于使用悲观锁的复杂性,Yii2并没有提供悲观锁。开锁说完锁,肯定需要开锁机制,脑子里突然冒出一个冷笑话:我一个人去买门锁,装上后才发现门只能开从外部。是冷的。没有解锁机制的交易处理系统是只能进入不能退出的交易处理系统。虽然死锁会自动解锁,但是反馈时间是一个非常死板的设置。先说说这个很新鲜的设定吧。如果要修改,可以到my.ini文件的innodb_lock_wait_timeout这一行。默认为50秒的等待时间。应用层的锁可以通过查看流水号来解锁,而MySQL层的锁可以通过information_scheme的PROCESSLIST表来解锁——确认事务无法完成。这里说一下PROCESSLIST表。当禁用自动提交的事务启动时,另一个类似的事务也启动了。双方发生冲突后,这张表中有一个冲突的SQLStatus,可以自己观察。最后:不管解锁机制多么完善,死锁本身就是代码逻辑造成的。在不修改/优化代码逻辑的情况下,简单的解锁机制只是对系统的额外负担。解决方法很简单:自己写一个简单的Log函数,把所有触发解锁机制的情况记录在Log中,自己优化。与锁机制配合的隔离机制就是隔离机制,可以尽可能有效的设置:事务之间的可见性。ReadUncommitted(RU,ReadUncommitted):最低隔离,问题是脏读(没有提交的UPDATEs仍然可以读)。已提交读(RC,ReadCommitted):语句提交后,其他事务可以读取COMMIT执行后的变化。问题:不可重复读(在同一个事务中,前后读取的数据不一致)。可重复读(RR,RepeatableRead):当在同一个事务中连续执行同一个查询语句时,结果是相同的。问题:幻读(并发事务同时处理同一个内容,导致一个内容覆盖另一方,使对方产生幻觉)。序列化(S,Serializable):在这个层次上,所有事务的完整性都得到了保留,这意味着只有当两个事务执行之间没有任务冲突时,所有事务才能被序列化并并发执行。四个级别中,高级别的隔离不会遇到低级别隔离的问题,但是隔离级别越高,并发损失就越大。MySQL默认使用RR级别。题外提到锁,就想到了之前做过的秒杀后端。当时的处理机制很简单,时间戳+事务。时间过得真快,现在回过头来看,突然发现有一些改进,一口气提到:最大秒杀缓存数的比较→服务器端微秒级时间戳+事务/悲观锁插入+插入失败缓存队列和二次插入尝试,已经可以实现解决了很大程度的并发问题。
