警惕 MySql 更新 sql 的 WHERE 从句中的 IN() 子查询时出现的陷阱

转自:http://blog.csdn.net/defonds/article/details/46745143

mer_stage 表有 216423 条记录,DDL:

[sql]  view plain  copy
 print ?
  1. CREATE TABLE `mer_stage` (  
  2.   `STAGE_ID` int(11) NOT NULL AUTO_INCREMENT,  
  3.   `MER_ID` int(11) NOT NULL,  
  4.   `MER_CODE` varchar(16) DEFAULT NULL,  
  5.   `MER_NAME` varchar(80) NOT NULL,  
  6.   `INS_CODE` varchar(16) NOT NULL,  
  7.   `INS_NAME` varchar(64) DEFAULT NULL,  
  8.   `AGENT_CODE` varchar(16) DEFAULT NULL,  
  9.   `AGENT_NAME` varchar(64) DEFAULT NULL,  
  10.   `BIG_CATEGORY_NAME` varchar(32) DEFAULT NULL,  
  11.   `SUB_CATEGORY_CODE` char(4) DEFAULT NULL,  
  12.   `SUB_CATEGORY_NAME` varchar(64) DEFAULT NULL,  
  13.   `LICENSE_CODE` varchar(64) DEFAULT NULL,  
  14.   `LICENSE_NAME` varchar(64) DEFAULT NULL,  
  15.   `SHORT_NAME` varchar(25) DEFAULT NULL,  
  16.   `MER_STATUS` tinyint(4) DEFAULT NULL,  
  17.   `PROVINCE_NAME` varchar(16) DEFAULT NULL,  
  18.   `CITY_CODE` char(4) DEFAULT NULL,  
  19.   `CITY_NAME` varchar(12) DEFAULT NULL,  
  20.   `REGISTER_ADDRESS` varchar(128) DEFAULT NULL,  
  21.   `BIZ_ADDRESS` varchar(128) DEFAULT NULL,  
  22.   `TAX_REGISTRATION` varchar(32) DEFAULT NULL,  
  23.   `INSTITUTION` varchar(16) DEFAULT NULL,  
  24.   `LEGAL_NAME` varchar(40) DEFAULT NULL,  
  25.   `LEGAL_CARD` varchar(32) DEFAULT NULL,  
  26.   `LEGAL_PHONE` varchar(16) DEFAULT NULL,  
  27.   `BIZ_SCOPE` varchar(128) DEFAULT NULL,  
  28.   `BIZ_CONTENT` varchar(64) DEFAULT NULL,  
  29.   `BIZ_TIME` varchar(32) DEFAULT NULL,  
  30.   `LICENSE_EXPIRED` varchar(16) DEFAULT NULL,  
  31.   `AVG_SINGLE_TRADE` int(11) DEFAULT NULL,  
  32.   `AVG_MONTH_TRADE` int(11) DEFAULT NULL,  
  33.   `BIZ_PLACE_OWNER` varchar(64) DEFAULT NULL,  
  34.   `REGISTERED_CAPITAL` decimal(11,0) DEFAULT NULL,  
  35.   `PAID_IN_CAPITAL` int(11) DEFAULT NULL,  
  36.   `BIZ_PERIOD` tinyint(4) DEFAULT NULL,  
  37.   `BIZ_AREA` int(11) DEFAULT NULL,  
  38.   `SETTLE_PERIOD` tinyint(4) DEFAULT NULL,  
  39.   `DELAY_TIME` varchar(50) DEFAULT NULL,  
  40.   `DELAY_TYPE` tinyint(4) DEFAULT '0',  
  41.   `BANK_CODE` varchar(40) DEFAULT NULL,  
  42.   `BRANCH_CODE` varchar(25) DEFAULT NULL,  
  43.   `BRANCH_CODE_ONE` varchar(25) DEFAULT NULL,  
  44.   `BRANCH_CODE_TWO` varchar(25) DEFAULT NULL,  
  45.   `BRANCH_NAME` varchar(128) DEFAULT NULL,  
  46.   `ACCOUNT_CODE` varchar(32) DEFAULT NULL,  
  47.   `ACCOUNT_NAME` varchar(80) DEFAULT NULL,  
  48.   `BRANCH_PROVINCE` varchar(32) DEFAULT NULL,  
  49.   `BRANCH_CITY_CODE` varchar(10) DEFAULT NULL,  
  50.   `BRANCH_CITY_NAME` varchar(50) DEFAULT NULL,  
  51.   `SETTLE_CURRENCY` varchar(16) DEFAULT NULL,  
  52.   `SETTLE_PARAM` char(1) DEFAULT NULL,  
  53.   `CUP_TYPE` tinyint(4) NOT NULL DEFAULT '1',  
  54.   `CUP_CD` varchar(6) DEFAULT NULL,  
  55.   `CUP_NM` varchar(80) DEFAULT NULL,  
  56.   `UPI_TYPE` tinyint(4) NOT NULL DEFAULT '1',  
  57.   `UPI_CD` varchar(6) DEFAULT NULL,  
  58.   `UPI_NM` varchar(80) DEFAULT NULL,  
  59.   `VISA_EDC_FEE` double DEFAULT NULL,  
  60.   `VISA_DCC_FEE` double DEFAULT NULL,  
  61.   `MASTERCARD_EDC_FEE` double DEFAULT NULL,  
  62.   `MASTERCARD_DCC_FEE` double DEFAULT NULL,  
  63.   `JCB_EDC_FEE` double DEFAULT NULL,  
  64.   `AE_EDC_FEE` double DEFAULT NULL,  
  65.   `DC_EDC_FEE` double DEFAULT NULL,  
  66.   `CONTACT_NAME` varchar(40) DEFAULT NULL,  
  67.   `CONTACT_FIXED` varchar(32) DEFAULT NULL,  
  68.   `CONTACT_MOBILE` varchar(32) DEFAULT NULL,  
  69.   `CONTACT_FAX` varchar(32) DEFAULT NULL,  
  70.   `CONTACT_EMAIL` varchar(80) DEFAULT NULL,  
  71.   `CONTACT_ADDRESS` varchar(128) DEFAULT NULL,  
  72.   `CONTACT_ZIP` varchar(8) DEFAULT NULL,  
  73.   `biz_license` text COMMENT '营业执照',  
  74.   `tax_register_cert` text COMMENT '税务登记证',  
  75.   `ins_cert` text COMMENT '组织机构代码证',  
  76.   `legal_id_card` text COMMENT '法人身份证',  
  77.   `open_license` text COMMENT '开户许可证',  
  78.   `auth_letter` text COMMENT '授权书',  
  79.   `portal_photo` text COMMENT '门头照片',  
  80.   `cashier_photo` text COMMENT '收银台照片',  
  81.   `scene_photo` text COMMENT '经营场景照片',  
  82.   `mer_agreement` text COMMENT '商户协议',  
  83.   `other_qualification` text COMMENT '其他特殊资质',  
  84.   `EXPECT_OPEN_TIME` datetime DEFAULT NULL,  
  85.   `IN_OUT_FLAG` varchar(32) DEFAULT NULL,  
  86.   `DCC_MODE` int(2) DEFAULT '0',  
  87.   `SPECIAL_FLAG` tinyint(4) DEFAULT NULL,  
  88.   `TRADING_CURRENCY` varchar(3) DEFAULT NULL,  
  89.   `STATUS` int(11) DEFAULT '0',  
  90.   `EDITABLE` tinyint(4) DEFAULT NULL,  
  91.   `MER_SINGLE_LIMIT` decimal(30,5) DEFAULT NULL,  
  92.   `MER_DAY_LIMIT` decimal(30,5) DEFAULT NULL,  
  93.   `MER_NATION` varchar(3) DEFAULT NULL,  
  94.   `ROUTE_SCHEME` varchar(13) DEFAULT NULL,  
  95.   `CREATOR_ID` int(11) DEFAULT NULL,  
  96.   `CREATOR_NAME` varchar(32) DEFAULT NULL,  
  97.   `create_time` datetime NOT NULL COMMENT '记录创建时间',  
  98.   `modify_time` datetime NOT NULL COMMENT '最好修改时间',  
  99.   `TERM_CNT` int(11) DEFAULT NULL,  
  100.   `DATA_SRC` tinyint(4) NOT NULL DEFAULT '1',  
  101.   `CUP_CARD_PLAN` bit(1) DEFAULT NULL,  
  102.   `UPI_CARD_PLAN` bit(1) DEFAULT NULL,  
  103.   `RISK_DESC` varchar(50) DEFAULT NULL,  
  104.   `IS_FLAG` char(1) DEFAULT NULL,  
  105.   `ALP` decimal(22,3) DEFAULT NULL,  
  106.   `WXP` decimal(22,3) DEFAULT NULL,  
  107.   `dfs_edc_fee` decimal(22,3) DEFAULT NULL,  
  108.   `prp_edc_fee` decimal(22,3) DEFAULT NULL,  
  109.   `in_account_id_card` text COMMENT '入账人身份证',  
  110.   `in_account_bank_card` text COMMENT '入账银行卡信息',  
  111.   `ins_credit_card` text COMMENT '机构信用代码证',  
  112.   `ins_store_photo` text COMMENT '仓库照片',  
  113.   `lease_agreement` text COMMENT '租赁协议',  
  114.   `sct` decimal(22,3) DEFAULT NULL COMMENT '扫码支付(支付宝、微信整合)',  
  115.   `card_type` char(1) DEFAULT '1' COMMENT '法人证件类型(1:身份证,2:护照)',  
  116.   PRIMARY KEY (`STAGE_ID`),  
  117.   KEY `mer_stage_s_e_ms` (`STATUS`,`EDITABLE`,`MER_STATUS`) USING BTREE  
  118. ) ENGINE=InnoDB AUTO_INCREMENT=216826 DEFAULT CHARSET=utf8;  

proc 表有 6450 条记录,DDL:
[sql]  view plain  copy
 print ?
  1. CREATE TABLE `proc` (  
  2.   `proc_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '流程id',  
  3.   `proc_name` varchar(32) NOT NULL COMMENT '流程名称,如 新增商户全聚德审批流程',  
  4.   `proc_type` tinyint(4) NOT NULL COMMENT '流程类型:1-新增商户,2-变更商户,3-新增终端',  
  5.   `associated_id` int(11) NOT NULL COMMENT '流程关联的商户id或其他',  
  6.   `node_id` tinyint(4) NOT NULL COMMENT '流程进行到哪个节点',  
  7.   `associated_name` varchar(64) DEFAULT NULL COMMENT '流程关联的商户名称',  
  8.   `proc_status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '流程状态:1-启动流程,2-进行中,3-已完成',  
  9.   `starter_id` int(11) NOT NULL COMMENT '流程发起者用户id',  
  10.   `starter_name` varchar(32) NOT NULL COMMENT '流程发起者用户名',  
  11.   `node_name` varchar(64) NOT NULL COMMENT '节点名称',  
  12.   `next_id` tinyint(4) NOT NULL COMMENT '下一节点id',  
  13.   `next_name` varchar(64) NOT NULL COMMENT '下一节点名称',  
  14.   `create_time` datetime NOT NULL COMMENT '记录创建时间',  
  15.   `ass_version` datetime NOT NULL COMMENT '关联版本号',  
  16.   `node_remark` varchar(255) DEFAULT NULL COMMENT '备注',  
  17.   `modify_time` datetime DEFAULT NULL COMMENT '上一节点完成时间',  
  18.   `mer_id` int(11) NOT NULL,  
  19.   PRIMARY KEY (`proc_id`),  
  20.   KEY `proc_mer_id_index` (`mer_id`) USING BTREE  
  21. ) ENGINE=InnoDB AUTO_INCREMENT=6451 DEFAULT CHARSET=utf8 COMMENT='流程';  

关于这两张表的一个慢查询日志如下:
# Time: 150703 15:13:33
# User@Host: test[test] @ localhost [127.0.0.1]  Id:     1
# Query_time: 2.101248  Lock_time: 0.046034 Rows_sent: 0  Rows_examined: 865689
SET timestamp=1435907613;
update mer_stage set editable = 1 where stage_id in(
select associated_id from proc where proc_id in(6446 , 6447 , 6450));

日志中可以看出该 sql 的执行时间是 2.101 s。
我们来查看一下该 sql 的执行计划:
我们来查看一下该 sql 的执行计划
注意:select_type 里出现了 DEPENDENT SUBQUERY。
这意味着什么?——子查询取决于外面的查询,MySql 先执行外查询,内查询根据这个查询结果(如执行计划里所述,190102 rows)的每一条记录组成新的查询语句:
[sql]  view plain  copy
 print ?
  1. select associated_id from proc where proc_id in(6446 , 6447 , 6450) and associated_id = '外查询结果.stage_id';  

这就是个坑。我相信,每个写出上面这种 sql 的程序员都不会想到 MySql 会对其这样执行,这是大家不想看到的结果。
怎么办?
Uncorrelated subqueries treated as DEPENDENT by MySQL  提出了同样的问题但是却没有给出解决方案。
MySql 官方 给出的解决方案是:
If you have a slow 'correlated' subquery with IN, you can optimize it with a join to get around the bug described by Ryan and Stephen. After the optimization the execution time is no longer O(M×N).
于是我们的 update 语句改写为:
[sql]  view plain  copy
 print ?
  1. update mer_stage m join proc p on m.stage_id = p.associated_id set m.editable = 1  
  2.         where p.proc_id =6446 or p.proc_id =6447 or p.proc_id =6450;  
它的执行计划是:
它的执行计划是
执行这个 update,用时 0.047s,意料之中。搞定。
有趣的是,我们来做一个尝试,把该 update 改为 select:
[sql]  view plain  copy
 print ?
  1. select * from mer_stage where stage_id in (select associated_id from proc where proc_id in (6446 , 6447 , 6450));  

它的执行时间是 0.053 s,毫秒级。
该 sql 的执行计划是:
update改为select后的执行计划

同样的写法,唯一不同的是一个 update 另一个 select,差别咋就那么大呢?看来优化器并不总是那么靠谱的,它在这里就对 update 那条 sql 的子查询优化的很糟糕。


参考资料

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值