刘强东(刘强东:简历个人资料)

君心似我心 2023-11-13 11:44:19 网络

突发换帅,京东巨变,刘强东面临10个艰难抉择

2018年6月,刘强东在牛津大学赛德商学院演讲,分享了京东创业路上三次大的抉择:把线下店全关掉,只做线上;扩充品类,从只做电子产品到做全品类的大平台;在几乎所有投资人都反对的时候,自建物流。

那是京东巨变的前夜。在那之后的五年里,京东陷入了一个又一个危急关头,抉择时刻只多不少。刘强东,京东唯一的创始人、绝对的控制者,一度隐身、放权,但我们梳理公开信息后发现,在每一个关键路口,他都会现身一线,强势地给出方向。

文 | 徐田艺

编辑 | 辛野

运营 | 刘璇

徐雷,还是许冉?

5月11日,京东发布了一则管理层变动公告:现任CFO许冉将接替徐雷,担任京东集团CEO兼执行董事。

落款是京东集团董事会主席刘强东。让CFO成为CEO,是他和京东的最新抉择。

徐雷,热爱摇滚乐、个性张扬的型互联网高管,此前被外界看作京东的“二号人物”。2022年11月,刘强东在内部会议上提到,“京东最顶头的领导,一个是我,一个是徐雷”。再往前,他还曾经在一场酒局上,语气强硬地对高管们宣布:“谁不服徐雷,就是不服我。”

一切来得太过突然,徐雷在京东集团CEO这个位置上刚刚待满一年。现年49岁的他和刘强东同岁,远未到退休年纪,却在这个初夏宣布因个人原因退休。交接匆忙,流程将立即开始,“以确保留有充足时间”,在6月份完成平稳过渡。

至于徐雷过往的成绩,刘强东也在公告里给予了评价,“曾担任多个高级职务,为公司的发展作出重大贡献”。

与徐雷相比,他的继任者在京东内外都要低调得多。

公开资料里,京东是许冉供职的第二家公司,第一家是普华永道,一待就是近20年,做到审计合伙人。2018年加入京东后,她从未担任过业务线一号位,而是深耕财务,从副总监一路升任集团CFO。

刘强东在公告里称赞许冉,“在京东投融史上发挥了重要作用”。达达、德邦等多起并购案,京东科技业务重组,以及京东赴港二次上市等资本动作,都由她带领团队完成。

徐雷则在他作为CEO出席的最后一次财报会上补充,许冉专业度强、有战斗力、能传承京东的价值观,“过往五年(我)和Sandy(许冉)非常紧密地合作”。

▲京东集团CFO许冉升任CEO。图 / 京东公众号

能拼杀的,还是会算账的?

徐雷和许冉年龄相仿,却是在不同时期加入京东。

2009年,在为京东做了两年营销顾问后,徐雷接受刘强东的邀请,正式成为京东商城营销副总裁。上任没多久,他就交出第一份亮眼的答卷——将定位模糊的“红六月”促销,改名为“618”,自此,京东有了可以与双十一抗衡的购物节,影响延续至今。

但徐雷真正被推向台前,是在2018年。京东唯一的创始人身陷负面、退至幕后时,徐雷以“救火队长”的姿态,推动京东内部大调整。

彼时,摆在京东面前的困境是营收增速下滑,追不上阿里不说,还被后来者拼多多步步紧逼。刘强东环视众高管,痛斥“混日子的人太多了”,他需要像徐雷这样,能打仗、会拼杀的“兄弟”。

“有战斗力”的许冉也在那时加入京东。她最突出的能力标签之一是“会算账”,打的第一场重要战役便是和徐雷配合。据晚点LatePost报道,2018年下半年,徐雷花了四个多月的时间,分析京东商城存在的问题,正是许冉用外部视角帮助分析了财务状况,得出结论:如果再不调整运营、组织模式,公司的经营风险可能会很大。

此后,徐雷开始了多轮组织、业务调整,通过搭建前、中、后台等动作,将京东拽回增长轨道——2019至2021年,京东连续三年保持净收入同比增速26%以上,活跃用户数也从3.05亿增长至5.8亿。2021年9月,徐雷履新京东集团总裁,这是京东20多年历史上第一次出现的职位,他也成为刘强东之外,京东最有权力的高管。

可惜,增长的甜蜜过于短暂。进入2022年后,受疫情叠加影响,打仗、扩张成为过去式,省钱、保利润成为包括京东在内,头部互联网公司的核心任务。

在降本增效的大趋势下,“会算账”的能力,前所未有地被看重。有京东零售中层人士接受36氪采访时表示,“互联网现在越来越关注生意的本质,没有谁会比CFO更懂得赚钱。少亏钱是赚钱,节流也是赚钱。”除了许冉,原京东零售CFO李帅和原京东健康CFO曹冬,在始自去年11月的这场变革中,走到了京东集团业务的一号位上。

这一定程度上也能够解释,为什么京东、华为、TikTok,开始让许冉、孟晚舟、周受资这些CFO走上CEO的位置。

▲ 京东集团候任CEO许冉。图 / 京东公众号

敲打高管,还是照拂“兄弟”?

此次京东集团CEO换人之前,各板块的核心高管,已经在刘强东的意志下开启了一洗牌。

起因是,他对不少高管的表现非常不满意。2022年11月20日,身在香港的刘强东,先是通过一场长达三小时的视频会议,隔空严肃批评高管们,尤其是零售业务的负责人们“谈花里胡哨的故事太多,但谈成本、效率、体验太少”,他认为,如果对如此核心的战略都把握不够,那将很难带领团队长远走下去。

刘强东还直指,许多高管一团和气,不讲核心问题,甚至拿PPT骗他。“整个京东零售,只有1.5个人在会上说真话、提问题。0.5是缪钦,1是姚彦中。”

会上被骂,还没有结束。不到48小时后,高管们又收到一封全员信,核心决定有四条,前三条都是给“兄弟”加福利,包括给德邦十几万员工缴五险一金、为工作满五年的基层员工设立“住房保障基金”,以及刘强东自掏腰包一个亿,加上集团各事业群、事业部拿出的现金,扩充“员工子女救助基金”的规模。

最后一条,京东全部高级管理人员,现金薪酬全部降低10%-20%不等,职位越高降得越多。

管培生,还是职业经理人?

在高管会和全员信后,刘强东又掀起一场人事风暴,首当其冲受到影响的是京东零售,各个事业群的一号位几乎都经历了一番调整。

“说真话的1.5个人”,迎来了变化——姚彦中由3C家电事业群总裁出任大商超全渠道事业群总裁,缪钦继续留任生活服务事业群负责人,但所管辖的房产业务线被拆分出去,更多是为了业务优化。

其中最引人注目的是两个年轻的面孔——电脑通讯事业群负责人吴双喜,和时尚美妆事业群负责人孔祥莹。他们都是京东2012届的管培生,在公司内部走出了陡峭的晋升曲线,尤其是吴双喜,短短一年多时间,职级变了三次,从业务总监到副总裁再到总裁。

什么时候选择什么样的人来管理公司,一直是考验一号人物的难题。

刘强东一直愿意给他信任的管培生,打造畅通的上升通道。此前直线向他汇报的高管里,2019年上任的CHO余睿和后来接替他的张雱,都是京东早期的管培生。

但在公司待得久,并不是绝对的护身符。据新行情援引内部人士观察,这次调整有三大特点:空谈派下课、实干派上台;一些曾被刘强东扶持起来的人“留在老功劳簿里,没有新功劳”,于是被拿下或换岗;刘强东在管培生、“老京东人”和职业经理人之间寻求微妙的平衡。

上一次如此规模的高管汰换,还要追溯到2016年。2014年上市之前,京东处于从草莽向成熟转变的关键时期,刘强东热衷于从外部引入“新鲜血液”,激活整个组织,不少履历光鲜的外企高管空降京东。但短短两三年,这些人又都纷纷离职。

一度有人调侃:京东高管比京东的库存周转更快。

▲京东集团总部大厦。图 / 视觉中国

个人意志,还是集体决策?

在去年11月那场高管会上,刘强东还提到,“我这几年一直在尝试各种放权”。

比如,2020年6月18日,京东回港上市当天,用鼓槌敲响钟声的是徐雷,刘强东并未现身。京东集团战略执行委员会(Strategy Executive Committee,SEC )的十余人团队,也在那一天首次亮相。

▲2020年6月18日,京东正式登陆港交所的仪式在京东总部举行,京东零售CEO徐雷等高管出现在现场参与“云敲钟”仪式。图 / 视觉中国

官方表示,SEC是京东战略管理的最高执行机构,由各业务板块和职能部门的负责人组成,核心工作包括战略规划,监督和保障集团战略级业务和项目的有效落地。

看上去,这是刘强东减弱个人意志,逐渐放权到集体决策的标志之一。

然而,此刻被放出去的权力,一年前刚刚收束过。2019年2月,刘强东提出要在当年年末汰换10%副总裁以上的高管。一并推出的还有“核心高管轮值”计划,超过一定年限,核心管理岗上的高管就会换。

放权,在京东历史上并不新鲜,只是每一次“放”,都没那么彻底。

2013年,刘强东前往美国,但依然远程监控公司的运转。2014年,京东上市后,时任京东商城COO的沈皓瑜出任京东商城CEO,刘强东也说要逐步退出日常运营和管理。但随着阿里进攻加剧,京东自身业务增速放缓,他在2016年又重新执掌一线。

第三次便是2018年,京东商城宣布实行轮值CEO制度,由集团CMO徐雷出任第一任轮值CEO,负责京东商城日常工作。之后,徐雷一路晋升,2021年出任京东零售CEO,2022年更执掌起整个京东集团,直到2023年5月11日,宣告退休。

放权、收权、放权……京东版本的权力的游戏里,不少人的角色都变了,只有刘强东和他接近80%投票权带来的绝对控制权,岿然不动。

品质电商,还是低价战略?

在京东,一号人物每一次权力的收放,都有明确的指向。

眼下回到一线,刘强东的目标也非常清晰:将未来三年最重要的“低价战略” ,贯彻到京东零售的每一个角落。

刘强东认定,“今天京东在品牌方、零售行业地位的下降,本质是因为其他渠道的快速增长”。

在他看来,京东如今营收和GMV增长缓慢的数据,或许很刺眼。财报数据显示,京东2022全年实现营收10462亿元,同比增长9.9%;GMV为34820亿元,同比增长5.6%。此前四年,京东的这两项指标都保持着同比25%以上的高速增长。

这位创始人在不同场合,向公司高管和管培生们传递共识。徐雷还未宣布退休之前,也在内部强调说:“我们坚定了要做低价,大家意志统一了,以后的改变特别多、特别难,但没办法,(低价)这条路,我们必须走下去。”

这是一次动作巨大的转身。徐雷开始执掌京东零售的2019年,京东的选择是“向上走”“实现有质量的增长”。“品质电商”也通过强大的供应链和物流,在消费者心中烙下深深的印记。“多快好省”的slogan里,最深入人心、也为京东带来增长的是“快”和“好”。

然而,在新一轮的零售战争中,京东决定拿起“低价武器”,把货品不够多和买东西不够省的短板恶补一气,将失去的下沉市场找回来。

为此,京东甚至不惜冒着“拾人牙慧”的质疑,直接在今年3月8日“复制”了拼多多的“百亿补贴”。

▲ 图 / 视觉中国

补贴自营,还是扶持第三方?

执行“低价战略”的第一个难题:百亿补贴到底向谁倾斜?

此前在京东,平台流量倾斜的对象一直是利润高、品质好的自营商品,但消费者也形成了“自营商品偏贵”的印象。现在,奉行“低价为王”的京东要学会,将广大下沉市场的POP(第三方卖家)笼进大盘,让更多卖家出现在平台上卖货。

4月,京东零售的组织变革中,全面打通自营与POP成为最受关注的举措之一。3月8日上线的“百亿补贴”,也试图覆盖全品类,自营与POP商家都会参与其中。刘强东多次强调,“谁能做到低价就给谁流量”。

这一倾斜并非全然没有风险。允许POP商家选择其它物流服务,或者放开“个人商家开店”的限制,都有可能发生物流不及时、正品率无法保证的问题,进而影响京东一直以来苦心经营的企业形象。

没有起色的新业务,投入还是砍掉?

京东不是没有过下沉的心思。

早在2014年,京东就尝试过社交电商的玩法,2019年又组建了京喜事业群。社区团购的风吹过互联网,京东一度犹豫不决,最终在2021年1月还是决定成立京喜拼拼下场拼杀,刘强东亲自带队。

但这项新业务,入场还是太晚了。仅仅一年后,20多个省份的业务被砍到个位数,社区团购熄火的同时,京东也铩羽而归。

京喜拼拼的收缩,为京东2022年的降本拉开序幕。

据晚点LatePost报道,京喜业务的持续亏损没有为京东换来想象中的规模,零售自营业务的优势品类通电、日用品的增长也开始放缓,此后,京东先后对零售、物流、科技、国际等几乎所有业务线进行了数次裁员。

在零售赛道之外,京东也曾在美团“腹地”本地生活伺机出手,想要试水外卖和同城购。契机或许在于,美团触碰到了京东的核心业务——3C消费电子。

近几年,越来越多的品牌如小米、华为、苹果、vivo 等与美团闪购建立合作,一些消费电子产品通过美团,最快可以30分钟送达用户。据报道,2021年美团闪购在这一块的总交易额(GTV)高达600至700亿元。

虽然京东二十多年来倾力培养起来的消费习惯,短时间内还未必会被撼动,但考虑到美团在外卖行业建立的即时履约能力和用户量级,已经足够形成威胁,这是无法接受的。

只是同城这条赛道已经非常拥挤,京东想要发力也并不轻松。已经在直播带货领先一大截的抖音,也显示出对本地生活的野心——据媒体报道,2022年,抖音生活服务超预期完成了各项目标,最终完成了大约770亿的GMV;2023年更是定下了翻倍的目标,1500亿。

如今的京东,对新业务的扶持也受到挑战。虽然京东零售CEO辛利军曾声明,京东的百亿补贴“在预算层面不设上限”,但当时还是CFO的许冉在提到2023年规划时却坦承,“京东内部没有大幅度提升本年度营销预算的计划”。

对于京东而言,能不能狠下心来烧钱,也将决定“低价战略”推行效果的成色。

▲广东东莞:华南首家京东MALL开业在即。图 / 视觉中国

要收入,还是要利润?

一系列动作的背后,京东的经营目标也已经发生变化。

2022年三季报电话会上,被问及如何驱动利润增长时,徐雷的答案是:提升高毛利商品占比、提升服务性收入,以及几乎所有大公司都在谈的,降本增效。他说,2022年主要靠降本,2023年会将更多的精力放在“增效”上,“在我看来,整个组织还有太多的效率可以提升”。

只是伴随着CEO的人事变动,“增效”的重任落到了许冉身上。

几天前的2023一季报电话会上,许冉强调:京东的战略方向不会发生变化,在相当长的时间也会保持一致。

具体到考核指标,京东各个业务线,不再盯着客单价,而是开始考核与低价相关的指标。有知情人士告诉虎嗅,财务背景的许冉,可能会在执行刘强东“低价”策略的同时,带领集团恰到好处地走在“收支平衡红线”边缘。

追求“性感”,还是接受“平庸”?

京东要成为一家怎样的公司?

刘强东说:“我讨厌去做一家平庸的公司,如果我们是一家平庸的公司,我宁愿把它关掉。”

徐雷也曾在2019年接受采访时提到,舆论场中的京东已经没有那么性感了,“因为有一家你们觉得更性感的公司”。这家公司,或许是拼多多,在阿里和京东防守薄弱的下沉市场攻城略地;又或者是抖音,在相当短的时间内,通过短视频海量获客,并且对直播电商、本地生活虎视眈眈。

但无论是平庸还是性感,都是极为主观的词,千人千面。只有数据不会撒谎。

成立超过二十年后,京东如何找到继续增长的曲线?或许这已经不是选择题,而是没有参考答案、刘强东和京东也无法避开的问答题。

▲图 / 视觉中国

参考资料:

[1]京东徐雷谈竞争:京东零售是古典电商 不用担心它今天不性感 晚点LatePost

[2]徐雷卸任京东集团 CEO,CFO 许冉接替,刘强东继续掌舵 晚点LatePost

[3]刘强东详解京东低价战略:一个共识、两条公式和三大痛点 晚点LatePost

[4]三小时内部发言,刘强东批评了哪些人哪些事 晚点LatePost

[5]毕竟,刘强东从未离开过 虎嗅

[6]京东零售开启5年来最大组织变革:取消事业群制,全面打通自营与POP 36氪

[7]京东是刘强东的京东 品玩

文章为每日人物原创,侵权必究。

MySQL事务--万文长字探究隔离性实现原理

1 隔离性简介

事务具有原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)四个特性,简称 ACID,缺一不可。这篇文章旨在讲清楚隔离性的产生背景、有何作用及实现原理等。

1.1 产生背景

并发环境下保证共享资源的线程安全性。

数据库表中的一行行数据就是共享资源,链接至数据库服务端的众多客户端就好比是多线程环境。

1.2 作用

保证数据库表数据,在每个事务间是读写安全,写写安全的。

2 MySQL事务间的读写冲突 & 解决方案

事务间的读写冲突,分为三种情况:脏读、不可重复读、幻读。

2.1 脏读

脏读指当前事务能够读到其他事务未提交的数据,未提交意味着这些数据可能会回滚,也就是可能最终不会存到数据库中。读到了不一定最终存在的数据,这就是脏读。

2.2 不可重复读

不可重复读指在同一事务内,不同时刻读到的同一批数据,结果显示可能不一样。原因是这批数据可能会受到其他事务的影响,比如其他事务改了这批数据并提交了。

2.3 幻读

MySQL 官方给出了幻读的定义:在同一事务中,相同的 SELECT 语句,得到的结果不一致(又造成了不可重复读,破坏了可重复读的隔离级别),并且第二次 select 的 raws 只会比第一次 select 的 raws 要多,这些多出的行,被称为“幻行”(即幻读只会发生在新增数据的场景下)。

if a SELECT is executed twice, but returns a row the second time that was not returned the first time, the row is a“phantom”row.

关于幻读,还有一道很经典的面试题:InnoDB RR 隔离级别下,是否解决了幻读?

这里先给出结论:快照读场景下,利用MVCC规避了大部分幻读场景,当前读场景下,利用间隙锁解决了幻读。事实上,快照读场景下,MVCC无法规避的幻读场景,在实际应用中也很少出现,因为一般没人那样写SQL,因此可以理解为MVCC + 间隙锁完全解决了幻读问题。

因为幻读的产生及解决方式涉及到 MVCC、快照读、当前读及间隙锁等背景知识,所以这部分的详细内容在下面“再探幻读”一节还会阐述。

SQL 标准定义了四种隔离级别用于解决上述这些读写冲突,MySQL 全都支持,下面一一分析。(注:从上往下,隔离强度逐渐增强,性能逐渐变差。采用哪种隔离级别要根据系统需求权衡决定,其中,可重复读是 MySQL 的默认级别。)

2.4 读未提交

没有解决上述任何一种读写冲突,即 MySQL 面对事务间的读写冲突没有做任何事情,性能最好,也最不实用。

2.5 读已提交

解决了脏读的问题,即当前事务只能读到其他事务已经提交过的数据。

读已提交是大多数流行数据库的默认隔离级别,比如 Oracle,但不是 MySQL 的默认隔离级别。

2.6 可重复读

解决了脏读,不可重复读的问题,即当前事务不会读到其他事务对同一批数据的修改,即使其他事务已提交。但是,对于其他事务新插入的数据是可以读到的,这也就引发了幻读问题。

2.7 串行化

解决了脏读,不可重复读,幻读的问题,它将事务的执行变为顺序执行,后一个事务的执行必须等待前一个事务结束,虽然隔离效果最好,但性能最差。

2.8 总结

隔离级别

脏读

不可重复读

幻读

读未提交

可能

可能

可能

读已提交

不可能

可能

可能

可重复读

不可能

不可能

可能(MVCC无法规避的某些场景)

串行化

不可能

不可能

不可能

3 MVCC-MySQL解决读写冲突的实现原理

我们平时在做开发,若在多线程环境下想要保证线程安全,大多数的解决方式都是加锁--同步锁、读写锁等。MySQL 在解决读写冲突时也用到了锁,但又不仅仅只用到了锁,需要分情况讨论。

对于同一条数据库记录来说:

读未提交,性能最好,因为它压根儿就不加锁,所以可以理解为事务间毫无隔离性串行化,性能最差,可以理解为事务运行时使用了排它锁,多个事务间调度必须串行执行

这两种隔离级别在实际中几乎没人使用,读未提交的线程安全性毫无保证,串行化则性能太差。

同时,MySQL 能够支持众多不同业务下的高并发场景需求,因此如果使用锁机制实现事务隔离性,则读写性能可能还是会差强人意。因此,为了提⾼数据库的并发性能,⽤更好的⽅式去处理读-写冲突,做到即使有读写冲突,也不会加锁,MVCC(Multi-Version Concurrency Control) 即多版本并发控制诞生了。

MVCC 除了提高数据库的读写性能外,还解决了脏读、不可重复读、幻读等问题。但是不能解决写写冲突下的更新丢失问题。

MVCC 的实现原理主要依赖记录中的隐式字段,undo 日志,readview 等,接下来会一一介绍。不过在此之前,先要了解一下什么是当前读和快照读:

当前读:select lock in share mode(共享锁)、select for update、update、insert、delete这些操作都是一种当前读,它会读取记录的最新版本,并对读取的记录进行加锁,保证其他并发事务不能修改当前记录快照读:select操作就是快照读,即不加锁的非阻塞读,快照读的前提是隔离级别不是串行级别,串行级别下的快照读会退化成当前读;快照读可能读到的并不一定是数据的最新版本,而是之前的历史版本

实际上 MVCC 只是一个抽象概念,即:维持一个数据的多个版本,使得读写操作没有冲突,并非实现。快照读相当于是 MySQL 对 MVCC 模型中非阻塞读功能的实现。相对而言,当前读则是悲观锁的具体功能实现。

3.1 隐式字段

数据表中的每条记录,除了我们自定义的字段外,还有数据库隐式定义的 DB_TRX_ID, DB_ROLL_PTR, DB_RAW_ID 等3个字段。

DB_TRX_ID:最近修改(更新、插入)事务ID:记录创建这条记录/最后一次修改该记录的事务IDDB_ROLL_PTR:回滚指针,配合 undo 日志,指向这条记录的上一个版本DB_RAW_ID:隐藏主键(自增id),如果数据表没有主键,则 InnoDB 会自动以 DB_ROW_ID 产生一个聚簇索引

例,person 表的某条记录:

3.2 undo log

undo log 实质分两种:

insert undo log:事务在 insert 记录时产生的 undo log,只在回滚时需要,并在事务提交后立即丢弃update undo log:事务在 update 或 delete 记录时产生的 undo log,不仅事务回滚时需要,快照读时也需要,不能随便删除,只有在当前读或事务回滚不涉及该日志时,才会被 purge 线程统一清理

对 MVCC 有实质帮助的是 update undo log。

3.3 undo log的生成流程

举个例子:

1.有个事务向 person 表插入了一条新纪录,隐式主键是1,回滚指针和事务ID假设是 NULL,如下:

2.事务1将该记录的 name 改为 tom:

事务1修改该行(记录)数据时,数据库会先对该行加排他锁然后把该行数据拷贝到 undo log 中作为旧记录,即在 undo log 中有当前行的拷贝副本拷贝完毕后,修改该行 name 为 Tom,并且修改隐藏字段的事务 ID 为当前事务1的 ID(事务 ID 默认从1开始递增),回滚指针指向拷贝到 undo log 的副本记录,即表示该行数据的上一个版本就是它事务提交后,释放锁

修改成功后,该行数据的组织形式如下:

3.事务2继续修改该行记录,将 age 修改为 30 岁:

同样,数据库也先为该行加锁然后把该行数据拷贝到 undo log 中作为旧记录,发现该行记录已经有 undo log 了,那么最新的旧数据将作为链表的表头,插在该行记录的 undo log 的最前面修改该行 age 为 30 岁,并且修改隐藏字段的事务 ID 为当前事务2的 ID,也就是 2,回滚指针指向刚刚拷贝到 undo log 的副本记录事务提交,释放锁

最终该行数据的组织形式如下:

如上述过程,不同事务或相同事务对同一记录的修改,会导致该记录的undo log成为一条记录版本的链表,undo log 的链首就是最新的旧记录,链尾就是最早的旧记录。

3.4 readview读视图

Read View 就是事务进行快照读时生产的读视图(Read View),即数据库系统当前的一个快照,读视图会记录并维护系统当前活跃事务的 ID(当每个事务开启时,都会被分配一个递增 ID,所以越新的事务,ID 值越大)。

Read View 主要用来做可见性判断,即当某个事务执行快照读的时候,会对该记录创建一个读视图,把它比作条件来判断当前事务能够看到该行记录哪个版本的数据,即可能是最新的数据,也有可能是 undo log 里某个版本的数据。

Read View 有三个全局属性:

trx_list:一个数值列表,维护生成 Read View 时系统正活跃的事务 ID 列表low_limit_id:trx_list 列表中最小的事务 IDup_limit_id:ReadView 生成时系统尚未分配的下一个事务 ID,即已出现过事务 ID 的最大值 + 1

Read View 的可见性算法:将当前的事务 ID、产生快照读的记录的最新事务 ID(DB_TRX_ID)及 readview 维护的事务 ID 取出来,进行比较,如果DB_TRX_ID不符合可见性,则通过DB_ROLL_PTR回滚指针取出Undo Log中上一版本的DB_TRX_ID再比较,即遍历链表的DB_TRX_ID,直到找到满足特定条件的DB_TRX_ID,那么这条记录就是当前事务所能看见的版本。

可见性算法的规则如下:

比较产生快照读的记录的最新事务 ID(DB_TRX_ID)与 low_limit_id, 如果 DB_TRX_ID < low_limit_id,说明此版本的记录对应的事务已提交,则当前事务能看到DB_TRX_ID对应的版本记录,如果大于等于则进入下一个判断如果 DB_TRX_ID >= up_limit_id,则代表DB_TRX_ID所在的记录是在 Read View 生成后才出现的,因此对当前事务不可见,如果小于则进入下一个判断如果DB_TRX_ID >= low_limit_id && DB_TRX_ID < up_limit_id,则判断trx_listntains (DB_TRX_ID)(即 DB_TRX_ID 是否在活跃事务之中)。如果在,则代表 Read View 生成时,该记录版本对应的事务还在活跃,没有 Commit,因此此记录版本当前事务不可见;如果不在,则说明,此版本记录对应的事务在 Read View 生成之前就已经 Commit 了,因此当前事务可见如果最新记录版本不满足事务可见性,则遍历 undo 日志链表,取更早的记录版本,重复上述判断规则,直到找到可见的版本记录为止

从可见性算法的实现规则来看,MVCC 解决了脏读的问题,即实现了读已提交。

3.5 整体流程

在了解了 MVCC 的基本实现原理后,我们举个例子,再来模拟一下 MVCC 的整体流程。

1.事务 2 对某行数据执行了快照读,数据库为该行数据生成一个 Read View 读视图,假设当前事务 ID 为 2,同时还有事务1和事务3在活跃中,事务4在事务2快照读前一刻提交了更新。Read View 记录了系统当前活跃的事务ID——1,3,维护在 trx_list 上。

事务1

事务2

事务3

事务4

事务开始

事务开始

事务开始

事务开始

...

...

...

修改且已提交

进行中

快照读

进行中

...

...

...

2.Read View 除了维护 trx_list 列表,还有两个属性:low_limit_id、up_limit_id。在这个例子中,low_limit_id 就是 1,up_limit_id 就是 4 + 1 = 5,trx_list 集合的值是 1、3。Read View 如下图:

3.在此例子中,只有事务4修改过该行记录,并在事务2执行快照读前就提交了事务,所以当前该行数据的undo log如下图所示;事务2在快照读的时候,会拿该行记录的 DB_TRX_ID 去跟 up_limit_id,low_limit_id 和 trx_list 比较,以此判断事务2能看到该记录的版本是哪个

4.判断过程如下:

先拿该记录最新的 DB_TRX_ID(即4)跟 Read View 的 low_limit_id 比较,是否满足DB_TRX_ID(4) < low_limit_id(1),不符合条件,继续下一步判断判断DB_TRX_ID(4) >= low_limit_id(5),也不符合条件,继续下一步判断最后判断trx_listntains(4),发现事务4并不在当前活跃事务列表中,即 Read View 生成时事务4已提交,符合可见性条件,因此事务2能读到事务4所提交的记录版本

MVCC 可见性算法的整体流程如下:

3.6 RR隔离级别的实现

事务A

事务B

开启事务

开启事务

快照读查询金额为500

快照读查询金额为500

更新金额为400

提交事务

快照读金额为?

select lock in share mode当前读金额为400

如上图,在 RC 隔离级别下,事务 B 进行快照读的结果为 400,在 RR 隔离级别下,结果为 500。

Read View 的使用方式,造成 RC、RR 级别下快照读的结果不同。

RC 级别:每次快照读都会新生成一个 Read View,因此可能导致同一事务中前后读取同一条数据的结果并不相同RR 级别:在整个事务中,只会记录产生的第一个 Read View,此后对数据进行快照读的时候,使用的是同一个 Read View,因此对 Read View 生成后的修改不可见3.7 再探幻读3.7.1 幻读的再解释

为了加深大家对 2.3 小结中幻读定义的印象,我举下面几个例子:

例子一

如上图,步骤 6 读到了另一个事务新增的数据,这个不是幻读,幻读是违背了可重复读的定义(同一个 select 语句,当前读的 select 与快照读的 select 还是不一样的),即如果步骤 5 所读到的数据与步骤 2 不一致,才叫幻读。

例子二

如上图,上图描述有误,RR 隔离级别下,根据 MVCC 读视图的生成规则,由于事务 A 中的第二个 Select 语句使用的 Read View 与第一个 Select 语句是一样的,因此事务 B 插入的数据不符合可见性原则,因此也不会发生幻读。

例子三

如上图,id 列是主键,由于事务 B 已经插入了id=30的数据,因此即使事务 A 两次快照读都没有读到id=30的数据,步骤 6 还是会插入失败。这个也不是幻读,为了满足 RR 隔离级别,这个现象是正常的,符合 SQL 标准。

3.7.2 MVCC无法规避的幻读场景

3.7.1小节中同时说明了那些本应该出现,但 MVCC 已经规避了的幻读场景,下面看两个 MVCC 无法规避的幻读场景。

可以看到,有两个事务 session1 与 session2。session1 在 session2 插入数据前后的两次快照读,读到的结果都为空(即使 session2 提交事务的时机放在 session1 第二次快照读之前,结果也依然为空),符合 RR 隔离级别。但是在 session1 进行了update t1 set a = 3之后,再次快照读,发现多出了一条数据(破坏了 RR)。这条数据,就是幻行(session 1 本身没有新增数据,因此 RR 隔离级别下,session1 中相同的快照读语句,前后读到的结果本应该是一致的)。

相同的例子,还有如下:

以上述这个例子说明这部分MVCC无法规避的幻读场景产生的原因:

事务 1 先 select,事务 2 insert 并 commit事务 1 再次 select,根据 RR 隔离级别的原理,查询的结果与第一次 select 一样接着事务 1 不加条件地 update,这个 update 会作用在所有行上,包括事务 2 新加的(新加的行的事务 id 被改变为 1,符合可见性原则)事务 1 再次 select 就会出现事务 2 中的新行,出现幻读

快照读场景下,幻读发生的本质是由于当前事务更新了新插入数据的事务id,导致新插入数据的可见性发生变化,破坏了RR隔离级别。

不过也正如上述场景所述,在实际应用中,我们很少这样去写 SQL,更别说像例子二一样去写全表更新这样的 SQL,因此可以简单的认为,MVCC 已经解决了快照读场景下的幻读问题。

3.7.3 间隙锁-当前读下幻读的规避方案

由于当前读会给记录加锁,阻止了其他事务在当前读期间,并发修改记录的情况发生,因此当前读天然的不会出现脏读及不可重复读的问题,但是幻读仍然会发生。

例子如下:

sql复制代码CREATE TABLE `author` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(255) DEFAULT NULL, `age` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8;INSERT into author VALUES (1,'g1',20),(5,'g5',20),(15,'g15',30),(20,'g20',30);

时间

session1

session2

begin;

T1

select * from author where age = 20 for update;

T2

INSERT into author VALUES (25,'g25',20);

T3

select * from author where age = 20 for update;

T4

commit;

session1在 T1 时刻读取到的结果:(1,'g1',20), (5,'g5',20),若没有间隙锁,session1在 T3 时刻会出现幻读,即读取到的结果:(1,'g1',20), (5,'g5',20), (25,'g25',20),若引入了间隙锁,则 session2 在 T2 时刻的插入语句会被阻塞,直到 session1 commit 为止,这样就防止了幻读的发生。

那么间隙锁的实现原理是什么呢?看下面这个例子。

sql复制代码-- 建表CREATE TABLE `userinfo` ( `id` int(11) NOT NULL COMMENT '主键', `name` varchar(255) DEFAULT NULL COMMENT '姓名', `age` int(11) DEFAULT NULL COMMENT '年龄,普通索引列', `phone` varchar(255) DEFAULT NULL COMMENT '手机,唯一索引列', `remark` varchar(255) DEFAULT NULL COMMENT '备注', PRIMARY KEY (`id`), UNIQUE KEY `idx_userinfo_phone` (`phone`) USING BTREE COMMENT '手机号码,唯一索引', KEY `idx_user_info_age` (`age`) USING BTREE COMMENT '年龄,普通索引') ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;-- 初始化数据INSERT INTO `userinfo`(`id`, `name`, `age`, `phone`, `remark`) VALUES (0, 'mayun', 20, '0000', '马云');INSERT INTO `userinfo`(`id`, `name`, `age`, `phone`, `remark`) VALUES (5, 'liuqiangdong', 23, '5555', '刘强东');INSERT INTO `userinfo`(`id`, `name`, `age`, `phone`, `remark`) VALUES (10, 'mahuateng', 18, '1010', '马化腾');INSERT INTO `userinfo`(`id`, `name`, `age`, `phone`, `remark`) VALUES (15, 'liyanhong', 27, '1515', '李彦宏');INSERT INTO `userinfo`(`id`, `name`, `age`, `phone`, `remark`) VALUES (20, 'wangxing', 23, '2020', '王兴');INSERT INTO `userinfo`(`id`, `name`, `age`, `phone`, `remark`) VALUES (25, 'zhangyiming', 38, '2525', '张一鸣');

首先明确一点:行锁及间隙锁不是加在记录上的,而是加在索引上的!

如上表,userinfo中目前有 3 种索引,分别是主键索引、唯一索引、普通索引。唯一索引这里用处不大,可以先忽略。

间隙锁,顾名思义,锁的是索引之间的间隙,因此对于此表数据,间隙锁的作用范围如下(以主键为例,另外两个索引同理):

具体间隙锁是如何规避幻读的,例子如下:

sql复制代码select * from userinfo where age = 23 for update;

如上述 SQL,由于普通索引并不唯一,因此可能有并发事务会新增一条 age 为 23 的数据,这时如果没有间隙锁,就会发生幻读。那么在此例中,为了防止出现幻读,只要给(20,23), (23, 27)这两个区间,即(20, 27)加上间隙锁即可。

对上图再做一个详细的解释(上图涉及到 B+Tree 的结构,不清楚的可以先了解一下 B+Tree):

此时的间隙锁为(20, 27),不包含左右两边的边界值 20 和 27在 age=23 的普通索引上有两把行锁,同时在 id=5 和 id=20 的主键索引上也有两把行锁只要当 age 的取值范围为 (20, 27) 时,任何并发事务都不能插入成功,因为间隙锁阻止了数据的插入,当然也阻止了修改与删除只要当 age 的值不属于 (20, 27),且不等于 20 也不等于 27 时,任何并发事务都能插入成功,前提是待插入的数据行中的 id 值符合主键唯一性和 phone 值符合唯一索引唯一性当 age=20 的时候,只有对应的主键值小于 0 才能插入。举例说明:当待插入的数据为age=20, id=1时,还需要为 age 这个非唯一索引下面存上主键索引的值:1,此时的 1 比表中已经存在的主键值 0 大,所以它会放在 0 的后面,此时的age=20, id=1依然会占用被锁住的索引间隙,所以此时不能插入成功。能插入成功的数据为age=20, id=-1或age=20, id=-2这样的组合当 age=27 的时候,同理,只有对应的主键值大于 15 才能插入。因为主键值如果小于 15,如上图,若插入age=27, id=14,数据会被放在age=27, id=15的前面,这时依然会占用被锁住的索引间隙,所以不能插入成功上面红色箭头的区域都是有间隙锁的区域,在此区域内新增数据不能插入成功

由于间隙锁锁的是索引之间的位置,因此会发生上述第 5、6 点的情况,但个人觉得其实并没有必要, MySQL 应该是可以准确计算出间隙锁作用的区间是开区间还是闭区间。在本例中,当 age 等于 20 或 27 时,即使 id 取任意值插入,也不会出现幻读,但 MySQL 并没有这样实现。这个东西也不必深究,只需明白间隙锁的原理即可。如下是对 5、6 点情况的验证:

4 MySQL事务间的写写冲突 & 解决方案4.1 何为写写冲突

不过多赘述,学习过并发编程的应该都明白如果没有互斥条件,并发写可能出现的问题就是数据丢失(数据覆盖)等,并发事务同理。

这里主要关注 MySQL 对并发事务之间的写冲突如何处理,首先来了解一下 MySQL 中的各种锁。

4.2 共享锁

可以被不同事务共享的锁就是共享锁(Shared Locks),简称 S 锁、读锁。共享锁与排它锁互斥,共享锁之间不互斥。

如下 SQL 会加共享锁:

sql复制代码SELECT ... LOCK IN SHARE MODE;4.3 排它锁

排它锁,也称独占锁(Exclusive Locks),简称 X 锁、写锁。排它锁之间互斥,排它锁与共享锁互斥,因此一个时刻,一行数据,只能被一个事务加 X 锁。

如下 SQL 会加排它锁:

sql复制代码SELECT ... FOR UPDATE;INSERT ... ;UPDATE ... ;DELETE ... ;4.4 表锁

MySQL 中粒度最大的一种锁,对整张表加锁,资源开销比行锁少,不会出现死锁,但发生锁冲突的概率很大。被大部分 MySQL 引擎支持,如 MyISAM 和 InnoDB。MyISAM 只支持表锁,InnoDB 默认是行锁,只有全表扫描的时候才会升级为表锁。因此我们主要围绕 MyISAM 来讲述一下表锁。

MySQL的表级锁有两种模式:

表级共享锁:不会阻塞其它线程对同一表的读请求,但会阻塞对同一表的写请求。只有当表级共享锁释放后,才会执行其它线程的写操作表级排他锁:会阻塞其它线程对同一表的读和写操作,只有当表级排他锁释放后,才会执行其它线程的读写操作。MyISAM 下写锁默认比读锁的优先级要高,即使读请求先到达锁请求队列,也会放在写请求的后面执行

因此 MyISAM 不适合做写多读少场景下的存储引擎。想要了解更多表锁在 MyISAM 中的技术细节,可以阅读此文章:MySQL中的锁(表锁、行锁)。

4.5 页锁

MySQL 中数据在内存及磁盘上存储的基本单位并不是记录(行),而是页,可以简单理解为多个行会组成一个页,页锁即是给页上加的锁。

BDB 存储引擎中实现了页锁,而 InnoDB 以及 MyISAM 存储引擎都没有页锁,因此知道有这个概念就行,有兴趣的同学可以自行了解。

4.6 行锁

行锁大家都比较熟悉,这里不再赘述。

4.7 意向锁

意向锁是一种表锁,也分为意向共享锁(IS)和意向排它锁(IX)。由于 InnoDB 既支持表锁又支持行锁,因此意向锁的作用是用来辅助表锁和行锁的冲突判断。在取得行级共享锁和行级排它锁之前,会先取得对应的意向共享锁和意向排它锁(此步骤数据库系统自动完成,不需要人工干预)。

若没有意向锁,则判断表锁和行锁冲突的时候,需要对索引进行遍历,判断表上是否有行锁。有了意向锁,只要判断表是否有意向锁,就知道表上是否有行锁。表锁与意向锁的兼容性如下:

X

IX

S

IS

X

Conflict

Conflict

Conflict

Conflict

IX

Conflict

Compatible

Conflict

Compatible

S

Conflict

Conflict

Compatible

Compatible

IS

Conflict

Compatible

Compatible

Compatible

注:从意向锁的作用及加锁时机可知,意向锁不与行锁冲突,因此表格中的 S、X 代表的是表级共享锁与表级排它锁。

4.8 悲观锁 & 乐观锁

悲观锁 & 乐观锁都是抽象概念,并不是真实存在的锁。大部分情况下,悲观锁及乐观锁的使用需要我们自己编写业务逻辑进行实现。

悲观锁 & 乐观锁产生的背景:由于在并发场景下,共享资源也不可能时时都处于竞争状态,基于此种条件,提出了悲观锁及乐观锁。

基于产生背景,悲观锁顾名思义,即并发场景下对共享资源的操作比较悲观,认为共享资源可能时刻处于竞争冲突中,因此操作数据的时候,直接加排他锁,保证本次操作的原子性、互斥性、可见性。像 Java 中 synchronized 的使用就是悲观锁的实现场景。

而乐观锁则相反,即并发场景下对共享资源的操作比较乐观,默认每次对共享资源的操作并没有引起竞争冲突,即每次操作天然线程安全,不用加锁,只有发现确实出现了竞争冲突,才会对共享资源真正的上锁。比较有代表性的实现方案就是 CAS 与版本号方案。

乐观锁的使用场景这里不再赘述,有兴趣的可以阅读此文章-【BAT面试题系列】面试官:你了解乐观锁和悲观锁吗?,写的比较好。

这里重点提一下面试官喜欢问的两个问题(上文已有详细的描述,这里总结一下):

1.CAS的ABA问题本质是什么?怎么解决?

当业务场景需要关注共享资源作的中间过程,却没有进行记录,就容易引发 ABA 问题。解决方案就是引入版本号或时间戳,即记录共享资源被修改的次数或其他信息。

详情可阅读:真实业务场景展现CAS原理的ABA问题及解决方案。

2.乐观锁有加锁吗?

乐观锁本身不加锁,只是在更新时判断一下数据是否被其他线程更新了,AtomicInteger 便是一个例子。有时乐观锁可能会与加锁操作合作,例如 update test set id = 1 where version = 1 这条 SQL,使用版本号机制实现乐观锁,但 update 时 MySQL 默认还会加排它锁,但这只是乐观锁与加锁操作合作的例子,不能改变 “乐观锁本身不加锁” 这一事实。

4.9 Next-key锁

Next-key 锁也不是什么新鲜玩意,事实上,前面所讲的行锁与间隙锁的组合就并称为 Next-key 锁,之所以有这么个叫法,是因为 MySQL 中加锁的基本粒度是 Next-key 锁,即默认行锁 + 间隙锁的组合,不过不同情况下,Next-key 锁可能会退化成单独的行锁或间隙锁。

以 3.7.3 节的例子再为例,Next-key 锁的锁范围如下:

注:Next-key 锁是左开右闭的区间。

4.10 加解锁时机

MySQL中使用两阶段锁协议进行加解锁,详情链接:详解MySQL两阶段加锁协议。

这里做个总结:

在事务中,在对记录进行更新或者(select for update、lock in share model)时,就会对记录加锁,但只有提交(commit)或者回滚(rollback)时才会解锁依据 2PL 的性能优化:SQL 编写时,越热点的记录离事务的终点越近,这样可以显著提高吞吐量4.11 加锁情况详细分析

上面基本已经介绍完了 MySQL 中常见的所有锁,接下来,是本篇节最重要的部分,各种加锁情况分析(来将所学的知识融会贯通吧)~

不同的 MySQL 版本,相同的 SQL 加锁情况可能不尽相同,下面的结论只是根据前述所讲所得出的理论,并不代表实际加锁情况,况且不同的 MySQL 版本中 Next-key 的加锁范围还有一定的 bug,这里不关注这些旁枝末节。

如果想要查看 MySQL 实际的加锁情况,可使用 select * from performance_schema.data_locks 命令进行查看。此命令执行后,有几个重要关键字简单解析一下:

LOCK_TYPE

LOCK_MODE

INDEX_NAME

LOCK_DATA

锁范围

TABLE

IX

NULL

NULL

表级锁,意向排他锁

RECORD

X,REC_NOT_GAP

PRIMARY

15

主键值为15的数据加了行锁

RECORD

X,GAP

uniq_a

115, 15

唯一索引列,值为115之前的间隙,不包括115加了间隙锁

RECORD

X

uniq_a

115, 15

唯一索引列,值为115之前的间隙加了 Next-key 锁,前开后闭区间,包括115

0.环境准备

MySQL 不同的版本加锁策略可能不同,下面以 MySQL 8.0.25 版本为例,进行多角度验证 next-key lock 的加锁范围。

MySQL 版本:8.0.25、隔离级别:可重复读(RR)、存储引擎:InnoDB。

sql复制代码CREATE TABLE `t` ( `id` int NOT NULL COMMENT '主键', `a` int DEFAULT NULL COMMENT '唯一索引', `c` int DEFAULT NULL COMMENT '普通索引', `d` int DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uniq_a` (`a`), KEY `idx_c` (`c`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;-- 初始化数据INSERT INTO `t`(`id`, `a`, `c`, `d`) VALUES (0, 10, 20, 30);INSERT INTO `t`(`id`, `a`, `c`, `d`) VALUES (5, 15, 25, 35);INSERT INTO `t`(`id`, `a`, `c`, `d`) VALUES (10, 110, 210, 310);INSERT INTO `t`(`id`, `a`, `c`, `d`) VALUES (15, 115, 215, 315);INSERT INTO `t`(`id`, `a`, `c`, `d`) VALUES (20, 120, 220, 320);

1.主键索引等值查询-值存在

sql复制代码mysql> begin; select * from t where id = 10 for update;

直接说结论,根据前文所述,首先会给表加上意向排它锁,虽然加锁的基本单位是 Next-key,但这里主键存在,只要锁住 id=10 的主键索引就行,因此 Next-key 会退化为行锁。

2.主键索引等值查询-值不存在

sql复制代码mysql> select * from t where id = 11 for update;

主键索引结构如下:

根据前文所述,首先会加表级意向排它锁,然后为防止幻读,Next-key会退化为间隙锁,间隙锁范围是(10, 15)。

3.主键索引范围查询

sql复制代码mysql> begin; select * from t where id > 10 and id <= 15 for update;

根据索引结构以及前文所述,首先会给表加意向排它锁,然后会给(10, 15]区间加 Next-key 锁,不会退化。

4.唯一索引等值查询-值存在

sql复制代码begin; select * from t where a = 110 for update;

唯一索引等值查询也不需要间隙锁,原因与主键索引等值查询一样。不同的是索引上的加锁方式,由于涉及到回表,因此 X 锁不仅会加到唯一索引上,同时也会加到主键索引上。

这里牵扯到两个问题:

X锁只给唯一索引上加行不行不行,如果此时有一个并发SQL:delete from t where id = 10;,而select for update语句没有将主键索引上的记录加锁,那么并发的 delete 就会感知不到前面 select 语句的存在,违背了同一记录上排他锁之间互斥的原则X锁只给主键索引上加行不行可以,由于唯一索引与主键索引是一一映射的关系,并且写操作最终会回归到主键索引上,因此是可以只给主键索引加锁的,但 MySQL 并没有这样做,我想原因大概是为了提高性能,毕竟唯一索引上只要加了锁,就有可能避免一次无效的回表操作

综上,不管是select for share语句还是不涉及回表的select id from t where a = 110 for update;语句,最终都会给主键索引加上相应的锁。

5.唯一索引等值查询-值不存在

sql复制代码begin; select * from t where a = 111 for update;

首先会给表加意向排他锁,然后会给唯一索引的 (110,115) 加上间隙锁(Next-key锁退化),由于已经解决了幻读问题,因此主键索引上不会加锁。

6.唯一索引范围查询

sql复制代码mysql> begin; select * from t where a >= 110 and a < 115 for update;

首先会给表加意向排他锁,然后 Next-key 锁会分别退化为唯一索引 110 的行锁,(110, 115)的间隙锁,由于 110 有对应的主键,因此也需要对相应的主键索引加上行锁。

6.普通索引等值查询-值存在

sql复制代码begin; select * from t where c = 210 for update;

首先加意向排它锁,由于是普通索引,值可能重复,因此 Next-key 锁不会退化,同时给 210 对应的主键索引也加上行锁。

7.普通索引等值查询-值不存在

sql复制代码begin; select * from t where c = 211 for update;

首先加意向排它锁,然后给 215 的前置区间加 Next-key 锁,由于 (210, 215) 的锁区间已经能够防止幻读产生,因此 Next-key 锁退化成间隙锁。

8.普通索引范围查询

sql复制代码mysql> begin; select * from t where c >= 210 and a < 215 for update;

首先加意向排它锁,由于是普通索引,索引值可能重复,因此第一个 Next-key 锁并不会退化,锁区间为(25,210],并给 210 对应的主键索引加上行锁,第二个 Next-key 锁会退化为间隙锁,锁范围为(210, 215)。

9.无索引查询

当锁定读查询语句select for share,select for update没有使用索引列作为查询条件,或者查询语句没有走索引查询,将导致全表扫描,同时锁全表(全表的每一个索引都会加 Next-key 锁,相当于把整个表锁住了,并不是加了表锁),这时如果其他事务对该表进行写操作,都会被阻塞。

根据两阶段锁协议,以及 MySQL 加锁的基本单位是 Next-key 锁,最终全表的加锁情况将如下所示:

因此,在线上执行 update、delete、select for update 等具有加锁性质的语句时,一定要检查语句是否走了索引,如果发生了全表扫描,那将会引发很严重的问题!

作者:_dhengyi链接:https://juejin/post/7238921098807803961

免责声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,请发送邮件举报,一经查实,本站将立刻删除。

扫一扫在手机阅读、分享本文