文章目录
在上文我们曾小小的提到过,在索引失效的情况下,MySQL会把所有聚集索引记录和间隙都锁上,我们称之为锁表,或叫行锁升表锁.
那么对于 行锁升表锁,有的同学误以为行锁 升级变成了 表锁,但实际上锁的类型并没有发生变化✍️,还是行锁! 只是表的所有聚集索引记录都被加上了行锁, 看起来像表锁, 所以提前澄清一下, 举个例子:
假设,表中有10万多条记录
行锁升表锁10万多条索引记录加行锁, 锁的粒度小, 但开销非常大,示意图如下:
表锁
OK, 相信已经澄清了~ 那么对于行锁升表锁, 我们应该如何避免呢? 如果真被行锁锁表了又该如何分析排查呢? 别着急, 我们一步一步来, 干货满满, 建议先收藏!后面如果有需要了, 直接能找到这里来看.
兵法有云:知己知彼,百战不殆!
所以在说如何避免之前,我们提前说一下哪些场景会造成行锁升表锁,建议还未看过前面两文的小伙伴先了解一下加锁规则:
【MySQL】说透锁机制(一)行锁 加锁规则 之 等值查询
【MySQL】说透锁机制(二)行锁 加锁规则 之 范围查询(你知道会锁表吗?)
那么对于看过前两篇文章的小伙伴,应该已经猜到了,场景肯定和索引有关!
没错, 就是 无索引 或 索引失效!
那么原因呢? 你想过这里的原因吗?

解读:因为InnoDB引擎的 3种行锁算法(Record Lock、Gap Lock、Next-key Lock),都是锁定的索引,当触发X锁(写锁)的where条件无索引 或 索引失效 时, 查找的方式就会变成全表扫描,也就是扫描所有的聚集索引记录,到这我想大家都应该看懂了,但是可能还有个疑问,为什么要把不匹配的记录也加锁呢?
这里是针对于默认的事务隔离级别:可重复读(RR)事务隔离级别来说的, 因为在RR隔离级别下,需要解决不可重复读 和幻读问题, 所以在遍历扫描聚集索引记录时, 为了防止扫描过的索引被其它事务修改(不可重复读问题) 或 间隙被其它事务插入记录(幻读问题), 从而导致数据不一致, 所以MySQL的解决方案就是把所有扫描过的索引记录和间隙都锁上, 这也就 发生了我们看到的锁表!💪💪💪

展开来说:
无索引
例如, 下面这个sql的 remark列 不是索引列, 如果按remark更新就是无索引更新.
update ct set abc = 1
where remark = '阿根廷';
索引失效
索引失效的情况有很多, 我们本文不分析为什么失效, 也不会列举出所有失效的场景, 因为那不是本节的重点(我会考虑单独安排一篇详细讲解)。 这里直接用explain说话:
key不是你期望的索引, 而是PRIMARY;type是index或all如果同时满足上面这两个条件, 那么就说明索引失效了!
对于索引失效列几个常见的场景简单说明一下:
例如,我新建一个复合索引:abc列 和 name列,如下:
ALTER TABLE `lock_test`.`ct`
ADD INDEX `idx_abc_name`(`abc`, `name`);
但更新sql语句未按照最左前缀, 直接按`name=`更新,这样就会**导致索引失效**:
update ct set abc = 1
where name = '阿根廷';
看一下explain的结果:

例如,我新建一个普通索引:name列:
ALTER TABLE `lock_test`.`ct`
ADD INDEX `idx_name`(`name`);
但更新sql语句使用了 like以%开头,这样也会导致索引失效:
update ct set abc = 1
where name like '%阿根廷';
看一下explain的结果:

这是比较特殊的情况. 同样的SQL, 传入的参数不同, explain的结果也不同, 有时会走索引, 但有时索引又失效! 😫
这里的原因:因为根据传入的参数不同 导致 结果集不同, 在正式扫描之前,MySQL会进行成本计算,计算走哪个索引更快!结果一算,发现走索引还不如全表扫描快, 那么这时即使你用的是索引列等值 也不会走索引,会走全表扫描,这也就导致了索引失效!
关于成本计算, 它是先计算不同索引的I/0成本和CPU成本, 然后进行对比, 哪个成本低就采用哪个索引来执行! 当然, 成本计算并不会真实执行, 所以速度非常快, 在上文【范围查询】时曾给过一个小的示例说明,这里不再重复赘述!

当然,索引失效的情况还有很多, 这里只是举几个例子让大家学会用explain分析, 如果不够过瘾,我后面紧接着会更新索引相关文章!记得关注我哦!
此时, 咱们已经清楚的知道了 可能造成 行锁升表锁 的场景,那么应对起来也就更有底气了,我的建议是:
无索引列进行更新/删除聚集索引进行更新/删除非聚集索引 进行更新/删除,需要确认:
这条非常重要!不可重复读 和幻读问题,所以也就不存在 锁表了。
前面两文咱们说的都是基于可重复读(RR)事务隔离级别,因为引入了
间隙锁(Gap Lock),所以情况变的复杂, 而在RC下, 情况变的简单.
咱们只能做到尽可能避免, 根据墨菲定律:只要有可能 就一定会发生!
所以我们必须掌握锁表应该如何分析排查!
InnoDB_row_lock%相关变量show status like 'innodb_row_lock%';

| 字段 | 说明 |
|---|---|
| Innodb_row_lock_current_waits | 当前正在等待锁定的数量 |
| Innodb_row_lock_time | 等待总时长: 从系统启动到现在锁定总时间长度 |
| Innodb_row_lock_time_avg | 等待平均时长: 每次等待所花平均时间 |
| Innodb_row_lock_time_max | 从系统启动到现在等待最长的一次所花时间 |
| Innodb_row_lock_waits | 等待总次数: 系统启动后到现在总共等待的次数 |
从上述值,我们可以看出我们行锁的整体情况,有助于我们分析。
INFORMATION_SCHEMA系统库我们可以通过 INFORMATION_SCHEMA系统库提供的:查看事务、锁、锁等待的 数据表 来分析.
-- 查看事务
select * from INFORMATION_SCHEMA.INNODB_TRX;
-- 查看锁
select * from INFORMATION_SCHEMA.INNODB_LOCKS;
-- 查看锁等待
select * from INFORMATION_SCHEMA.INNODB_LOCK_WAITS;
-- 查看连接情况
select * from INFORMATION_SCHEMA.PROCESSLIST;
阻塞的事务id和锁id-- 查看锁等待
select * from INFORMATION_SCHEMA.INNODB_LOCK_WAITS;
| 字段 | 说明 |
|---|---|
| requesting_trx_id | 请求的事务id |
| requested_lock_id | 请求的锁id |
| blocking_trx_id | 阻塞的事务id |
| blocking_lock_id | 阻塞的锁id |
我这里模拟一个锁等待,然后查询,可以清晰的看到谁阻塞了谁

温馨提示:只有发生锁等待才有数据
上锁的详细信息-- 查看锁
select * from INFORMATION_SCHEMA.INNODB_LOCKS;
这和我们通过show engine innodb status\G;看到的结果类似, 略…, 也是只有发生阻塞才会有数据.
事务的状态、阻塞开始时间、阻塞的sql、线程id等等 -- 查看事务
select * from INFORMATION_SCHEMA.INNODB_TRX;
这个表很关键, 对于我们排查来说必不可少, 一些关键字段说明如下:
| 字段 | 说明 |
|---|---|
| trx_id | 事务id |
| trx_state | 事务状态,LOCK WAIT代表发生了锁等待 |
| trx_started | 事务开始时间 |
| trx_requested_lock_id | 请求锁id, 事务当前正在等待锁的标识,可以join关联INNODB_LOCKS.lock_id |
| trx_wait_started | 事务开始锁等待的时间 |
| trx_weight | 事务的权重 |
| trx_mysql_thread_id | 事务线程 ID,可以join关联PROCESSLIST.ID |
| trx_query | 事务正在执行的 SQL 语句 |
| trx_operation_state | 事务当前操作状态 |
| trx_isolation_level | 当前事务的隔离级别 |
当发生阻塞时,我们来看一下数据:

一目了然,哪个SQL从什么时间开始阻塞,线程id是多少,看的一清二楚.
连接情况-- 查看连接情况
select * from INFORMATION_SCHEMA.PROCESSLIST;
通过这个表,我们可以定位到事务所在的主机.
| 字段 | 说明 |
|---|---|
| ID | 线程ID, 可以JOIN INNODB_TRX.trx_requested_lock_id |
| USER | 连接用户 |
| HOST | 连接主机 ip:port |
| DB | 连接的数据库 |
通过对上面的表进行查询, 当我们发现某个事务阻塞了很多事务, 并且执行时间很长时, 我们可以手动中止它, 只需要找到INNODB_TRX.trx_mysql_thread_id,然后调用kill命令:
kill {INNODB_TRX.trx_mysql_thread_id}
本文主要介绍了:
无索引 或 索引失效读已提交(RC)事务隔离级别INFORMATION_SCHEMA.INNODB_TRX、INFORMATION_SCHEMA.INNODB_LOCK_WAITS,以及手动中止 kill {INNODB_TRX.trx_mysql_thread_id}如果感觉不错,欢迎订阅本专栏,后面还有更详细的MySQL知识陆续放出。
关注我 天罡gg 分享更多干货: https://blog.csdn.net/scm_2008
大家的「关注 + 点赞 + 收藏」就是我创作的最大动力!谢谢大家的支持,我们下文见!
我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div
总的来说,我对ruby还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用
关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。
给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru
我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t
我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚
Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack
在选择我想要运行操作的频率时,唯一的选项是“每天”、“每小时”和“每10分钟”。谢谢!我想为我的Rails3.1应用程序运行调度程序。 最佳答案 这不是一个优雅的解决方案,但您可以安排它每天运行,并在实际开始工作之前检查日期是否为当月的第一天。 关于ruby-如何每月在Heroku运行一次Scheduler插件?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/8692687/
我有一个对象has_many应呈现为xml的子对象。这不是问题。我的问题是我创建了一个Hash包含此数据,就像解析器需要它一样。但是rails自动将整个文件包含在.........我需要摆脱type="array"和我该如何处理?我没有在文档中找到任何内容。 最佳答案 我遇到了同样的问题;这是我的XML:我在用这个:entries.to_xml将散列数据转换为XML,但这会将条目的数据包装到中所以我修改了:entries.to_xml(root:"Contacts")但这仍然将转换后的XML包装在“联系人”中,将我的XML代码修改为
我有一大串格式化数据(例如JSON),我想使用Psychinruby同时保留格式转储到YAML。基本上,我希望JSON使用literalstyle出现在YAML中:---json:|{"page":1,"results":["item","another"],"total_pages":0}但是,当我使用YAML.dump时,它不使用文字样式。我得到这样的东西:---json:!"{\n\"page\":1,\n\"results\":[\n\"item\",\"another\"\n],\n\"total_pages\":0\n}\n"我如何告诉Psych以想要的样式转储标量?解