当前位置:首页 > 技术文章 > 正文内容

为什么一条UPDATE语句有索引反而更慢

arlanguage5个月前 (12-15)技术文章49

先来看看今天要讲的主人翁:

UPDATE `i_msg_system` set `deliver`=1 where `uid`=10000 and `msg_group`=0 and `deliver`=0;

涉事表结构:

CREATE TABLE `i_msg_system` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `uid` int(11) DEFAULT NULL,
  `fromuid` int(11) NOT NULL,
  `msg_group` int(10) NOT NULL DEFAULT '0' COMMENT '消息组,0-系统消息, 1-远程工作消息, 2-技术圈赞,7-交易信息',
  `notice_type` varchar(100) CHARACTER SET utf8 DEFAULT '',
  `detail_id` int(11) DEFAULT NULL COMMENT '待废弃,使用extra替代',
  `content` varchar(1024) DEFAULT NULL,
  `url` varchar(100) CHARACTER SET utf8 NOT NULL COMMENT '跳转地址',
  `time` int(10) DEFAULT NULL,
  `deliver` int(1) DEFAULT NULL,
  `extra` varchar(1000) CHARACTER SET utf8 NOT NULL DEFAULT '' COMMENT 'json格式的额外数据',
  PRIMARY KEY (`id`),
  KEY `idx_u_m_d` (`uid`,`msg_group`,`deliver`),
  KEY `idx_fromuid` (`fromuid`),
  KEY `idx_uid_group_notice_type_url` (`uid`,`msg_group`,`notice_type`,`url`) USING BTREE,
  KEY `idx_uid_deliver` (`uid`,`deliver`)
) ENGINE=InnoDB AUTO_INCREMENT=5341925 DEFAULT CHARSET=utf8mb4 COMMENT='用户消息表';

定睛一看,好像没什么问题,SQL语句都有索引。

事故处理过程

上周突然发现线上异步队列挂了,消息队列里面的数据都没有推送出去,手动重启后,不到半个小时又崩掉了,此时题主内心是崩溃的。

经过各种分析业务代码,发现之前写的一个自动运维脚本,里面的逻辑是:每5分钟给队列里面发送一条特定消息,消费者消费后写入某个标记(此处即写入上面用户消息表,实际就是发送一条消息),下一个5分钟检测这个标记是否存在,如果不存在,则认为队列消费者出了异常,强制重启。猜测是这里由于某些原因,自动重启,然后没启动过来。因此题主迅速改掉此处逻辑,每5分钟发送消息并检测上一条消息是否已经消费成功,如果没有成功则只报警,不再重启,果然线上恢复正常消费,但是出现了新的问题,发现上那个消息经常延迟。延迟非常大,有时候10多分钟,但是看消费者日志,别的消息都是正常的。题主又开始一堆分析代码,搜索。

正当题主无解的时候,这时前端业务部门反馈,用户端上报,消息中心经常502,隔一会儿自动恢复。于是通过Nginx日志找到502的请求,拿到日志ID,去业务日志中查询,发现每次502请求都是执行到开篇那条SQL,业务就中断了,一直到nginx 网关超时。

于是我们拿到刚才那条SQL,先到mysql里面explain:


Mysql Explain

可以看到SQL用到了索引,ref 是const, rows是2,单看这个,这条SQL执行应该是非常快的。但是题主同时注意到了Extra里面有个Using temporary。这就是原因了,这张表有400多万记录,创建临时表的开销是非常大的。

为什么一条UPDATE语句,有索引还会用到临时表呢?

求助同事无果,求助万能的群无果,求助百度无果,求助谷歌,反复更换关键词后,终于找到一个文章:https://stackoverflow.com/questions/36143560/mysql-update-query-using-a-temporary-table-when-an-equivalent-select-query-does

A guess:

The UPDATE is changing an indexed value (readyState), correct? That means that the index in question is being changed as the UPDATE is using it? So, the UPDATE may be "protecting" itself by fetching the rows (in an inefficient way, apparently), tossing them into a tmp table, and only then performing the action.

"Index merge intersect" is almost always less efficient than a composite index: INDEX(readyState, productName) (in either order). Suggest you add that.

Since you have no ORDER BY, which "30" will be unpredictable. Suggest you add ORDER BY the-primary-key.

大概意思是说:更新语句更新到了索引列同时where条件用又用到了该索引列,Mysql为了保证where条件查到的数据的一致性,所以用到了临时表。

既然这样那我们就来改造SQL。

首先是尝试把SQL改成子查询,子查询查询出主键:

UPDATE `i_msg_system` SET `deliver` = 1 WHERE id in(
  select id from  (
  	select id from i_msg_system a  where uid = 10000 and msg_group = 0 and deliver=0
    ) b
  );

Explain下,发现不用临时表了,但是发生了全表扫描,没用到索引,原因是子查询是Mysql是先执行主查询,再执行子查询 ,由于外面的条件没有任何过滤,所以外层就是全表扫描,所以此方案放弃。

继续尝试改造SQL为联表操作:

UPDATE i_msg_system a INNER JOIN (
  select id from i_msg_system c where uid = 10000 and msg_group = 0 and deliver=0
) b on a.id=b.id set a.deliver=1;

再次explain,这次终于顺利通过。

改代码上线,速度上来了,不过前端反馈还是有零星的502,这又是什么情况?

show FULL PROCESSLIST;
SELECT * FROM  information_schema.innodb_trx;

发现居然有事务几十分钟都没提交,而且对应的进程是sleep,居然有事务没提交?

全部代码搜索用到事务的地方,一查,还真是,最近上线一个版本的代码,几个新人写的代码,居然有开启事务后,在事务内部逻辑处理中,如果中途return 并没有回滚或提交事务。既然问题清楚了,马上通知大家改代码,CR,上线。再次观察,经过一天的观察线上502问题得到解决。

通过这次事故的处理总结几点:

  1. 简单粗暴的自动化运维不可取
  2. 尽量不用事务,通过代码逻辑去保证数据的一致性,这个在大型分布式系统中也是重点
  3. 开发规范,上线规范需要规范,CR不能少,而且不能流于表面

扫描二维码推送至手机访问。

版权声明:本文由AR编程网发布,如需转载请注明出处。

本文链接:http://www.arlanguage.com/post/204.html

标签: nginx访问慢
分享给朋友:

“为什么一条UPDATE语句有索引反而更慢” 的相关文章

Linux 安装 Alist 个人云盘 alpine linux安装

1. 简介云服务器有比较大的空间,想自己搭建一个个人云盘,做大文件的转存。其他百度云盘等的下载速度感人,不想充值会员。查询了一轮之后 Alist,Nextcloud,Cloudreve,ownCloud,Seafile等等看到了不少,但是很多都需要各种配置安装。要么是通过宝塔可以快速安装,但是我的服...

Windows中使用Nginx解决前后端分离部署中的跨域问题

说明现在的Java Web项目好多都使用前后端分离的开发部署方式,这样的好处有很多,比如:可以实现真正的前后端解耦,可以减少后端服务器的并发/负载压力,方便实现多端应用(网页端、移动端共用一个后台服务)、增加代码的维护性&易读性。一般我们会采用Nginx来部署前端代码,使用Tomcat来部署后台服务...

推荐一款 Nginx 可视化配置神器

Nginx 是前后端开发工程师必须掌握的神器。该神器有很多使用场景,比如反向代理、负载均衡、动静分离、跨域等等。把 Nginx 下载下来,打开 conf 文件夹的 nginx.conf 文件,Nginx 服务器的基础配置和默认的配置都存放于此。配置是让程序员非常头疼的事,比如 Java 后端框架...

给你的Nginx加个防火墙

引言朋友的一个 WordPress 站经常访问慢。看了一下日志,发现整天被扫描网站目录,如phpmyadmin 或者 SQL 文件,和被 CC攻击。找了一下,发现 ngx_lua_waf 是个不错的方案,但是太久不更新了,而且代码我看不懂,猝最后找到 oneinstack 一键包内置的 ngx_lu...

在 Ubuntu 或 Debian 系统上安装以及配置 nginx-1.20.1.tar.gz

安装和配置 Nginx 的步骤如下:下载 Nginx:1、首先,你需要从 Nginx 的官方网站下载源代码包。在你的服务器上执行以下命令,下载 Nginx 1.20.1 版本的源代码包:wget http://nginx.org/download/nginx-1.20.1.tar.gz2、解压源代码...

nginx监控与调优(三)

nginx监控通常有两种方法:一是status监控;二是ngxtop监控。一、status监控使用status监控的步骤:1.确定nginx中status模块是否已安装[root@localhost sbin]# nginx -V nginx version: nginx/1.13.7 built...