mysql 性能优化之exists 写法不一样,性能提升400倍

本文探讨了MySQL中使用EXISTS与IN子查询时的性能问题,特别是在不同版本间的表现差异,并提出了有效的优化策略。

业务需求 查询订单中包含商品《高性能 mysql》 的单据总量

上代码

select count(1) from saleorder a 
                          where a.tid = 180571   and exists(select 1 from saleorderdetail e1 where a.tid = e1.tid and a.billid = e1.billid and e1.goodsid in (1061)

执行时间为 16s

 

mysql 版本为 

5.6.16-log

查看执行计划

1PRIMARYarefPRIMARY,bo_billdate,bo_createdate,bo_billno,fk_saleorder_bid,fk_saleorder_cmpid,fk_saleorder_empid,fk_saleorder_storeid,fk_saleorder_useridbo_billdate4const13994Using where; Using index
2DEPENDENT SUBQUERYe1refPRIMARY,fk_saleorderdetail_skuid,fk_saleorderdetail_storeidfk_saleorderdetail_skuid8userdb0001.a.tid,const4

Using where; Using index

 

相同的语句 我再来看 8.0  中执行,只需要0.04S

mysql 版本为 

8.0.18

再 看执行计划 

SIMPLE<subquery2> ALL     100.00Using where
SIMPLEa eq_refPRIMARY,bo_billdate,bo_createdate,bo_billno,fk_saleorder_bid,fk_saleorder_cmpid,fk_saleorder_empid,fk_saleorder_storeid,fk_saleorder_useridPRIMARY12const,<subquery2>.billid1100.00Using index
MATERIALIZEDe1 refPRIMARY,fk_saleorderdetail_skuid,fk_saleorderdetail_storeidfk_saleorderdetail_skuid8const,const2402100.00Using index

 可以看到 8.0对子查询做了优化。

那么在 8.0以前版本中 exists怎么优化呢。

两种方案,1种是改写为 join

      select count(distinct a.tid,a.billid) from saleorder a  inner join saleorderdetail e1 on a.tid = e1.tid and a.billid = e1.billid
                                where a.tid = 180571  and  e1.goodsid in (1061) 

查询时间为0.04S

 

第二种方案,改in

select count(1) from saleorder a 
                                where a.tid = 180571  and (a.tid,a.billid) in (select tid,billid from  saleorderdetail e1 where e1.tid =180571 and  e1.goodsid in (1061))
     同样查询时间也只有 0.04S

 

 

结论 mysql 8.0以前的版本 要注意 exists与in 的子查询 会不会产生DEPENDENT SUBQUERY 如果产生,则必须优化它。

mysql 8.0及以后版本在子查询方面,做了较大优化。可以放心写。

 

 

<think>嗯,用户之前问了关于在MyBatis中使用MySQL的IN查询参数过多的问题,现在又接着问除了IN之外还有没有其他写法能达到同样效果。首先,我需要确定用户的需求是什么。他们可能遇到了IN子句的限制,想寻找替代方案,或者想优化查询性能。 用户之前的问题是关于处理超过1000个参数的IN查询,现在他们可能想了解是否有其他SQL方法可以实现类似IN的功能,而用受到参数数量的限制。可能需要考虑同的SQL操作符或技术,比如JOIN、EXISTS、临时表、OR组合等。 接下来,我需要回想MySQL中哪些方法可以替代IN。比如,使用JOIN来连接数据,将IN中的参数作为临时表或者子查询。或者使用EXISTS子查询,特别是当检查存在性时。另外,OR条件也可以替代多个等值条件,但需要注意性能问题。 用户可能还想知道这些替代方法的优缺点,比如性能如何,是否容易实现,是否需要修改数据库结构或配置。比如,临时表方法虽然有效,但需要创建和清理临时表,可能增加复杂性。而JOIN方法可能需要将参数列表转换为一个表,这在MyBatis中如何处理。 另外,用户可能对性能优化感兴趣,比如使用范围查询代替多个离散值,或者利用覆盖索引来加速查询。需要解释这些方法的使用场景和限制。 还需要考虑用户是否有权限修改数据库配置,比如调整max_allowed_packet,或者是否有能力修改业务逻辑,比如分批次查询但合并结果,尽管用户之前提到想分多次查询,但或许有变通的方法。 最后,应该总结这些替代方案,并根据同情况给出建议,帮助用户根据他们的具体场景选择最合适的方法。比如,参数非常多时推荐临时表或批量JOIN,参数可以分段时用OR组合,存在性检查用EXISTS等。</think>在 MySQL 中,除了 `IN` 子句外,还有多种替代方案可以实现类似的效果。以下是 **6 种常见场景的替代写法**,附带实现逻辑和适用场景分析: --- ### 一、`JOIN` 关联查询(推荐) 通过将参数列表转换为临时表或子查询,用 `JOIN` 替代 `IN` 的条件匹配。 #### 示例场景:查询 ID 在指定列表中的记录 ```sql -- 原始 IN 写法 SELECT * FROM user WHERE id IN (1,2,3,...,1000); -- JOIN 替代方案 SELECT u.* FROM user u JOIN (SELECT 1 AS id UNION SELECT 2 UNION ... SELECT 1000) tmp ON u.id = tmp.id; ``` - **优点**:规避参数数量限制,适合超长列表 - **缺点**:需要构造临时表或子查询 - **MyBatis 实现**:通过动态 SQL 拼接 `UNION` 子查询或预先创建临时表 --- ### 二、`EXISTS` 子查询 通过关联子查询判断存在性,适合替代 `IN` 的存在性检查。 #### 示例场景:查询有订单的用户 ```sql -- 原始 IN 写法 SELECT * FROM user WHERE id IN (SELECT user_id FROM order); -- EXISTS 替代方案 SELECT * FROM user u WHERE EXISTS (SELECT 1 FROM order o WHERE o.user_id = u.id); ``` - **优点**:对索引友好,性能更优 - **缺点**:仅适用于存在性判断,无法直接匹配离散值列表 --- ### 三、`OR` 条件组合 将 `IN (v1, v2, v3)` 改写为 `= v1 OR = v2 OR = v3`,适合少量参数。 #### 示例场景:查询特定状态订单 ```sql -- 原始 IN 写法 SELECT * FROM order WHERE status IN (2,5,7); -- OR 替代方案 SELECT * FROM order WHERE status = 2 OR status = 5 OR status = 7; ``` - **优点**:简单直观 - **缺点**:参数过多时 SQL 会冗长,可能触发 `max_allowed_packet` 限制 --- ### 四、`BETWEEN` 范围查询(仅连续值) 用范围查询替代离散值,适合连续数值或日期。 #### 示例场景:查询 2023 年 1 月数据 ```sql -- 原始 IN 写法(假设日期已离散化) SELECT * FROM log WHERE date IN ('2023-01-01', '2023-01-02', ..., '2023-01-31'); -- BETWEEN 替代方案 SELECT * FROM log WHERE date BETWEEN '2023-01-01' AND '2023-01-31'; ``` - **优点**:简洁高效 - **缺点**:仅适用于连续范围,无法处理离散值 --- ### 五、`FIND_IN_SET` 函数(慎用) 将参数拼接为逗号分隔字符串,用字符串匹配函数查询。 #### 示例场景:查询特定标签文章(非规范化设计) ```sql -- 原始 IN 写法(假设 tags 字段为逗号分隔字符串) SELECT * FROM article WHERE FIND_IN_SET('java', tags) > 0; -- 替代方案(直接查询) SELECT * FROM article WHERE tags LIKE '%java%'; ``` - **优点**:兼容逗号分隔字符串存储设计 - **缺点**:违背第一范式,无法走索引,性能差 --- ### 六、JSON 函数(MySQL 5.7+) 利用 JSON 类型字段和 `JSON_CONTAINS` 函数查询。 #### 示例场景:查询包含特定权限的用户 ```sql -- 假设 permissions 字段为 JSON 数组 ["read", "write"] SELECT * FROM user WHERE JSON_CONTAINS(permissions, '"write"'); ``` - **优点**:适合半结构化数据查询 - **缺点**:需要 MySQL 5.7+ 版本,性能低于关系型查询 --- ### 性能对比总结 | 方案 | 适用场景 | 性能 | 参数限制 | 索引友好 | |---------------|----------------------------------|------|----------|----------| | `IN` | 少量离散值 | ★★☆ | 有 | 是 | | `JOIN` | 超长参数列表 | ★★★ | 无 | 是 | | `EXISTS` | 存在性检查 | ★★★ | 无 | 是 | | `OR` | 少量离散值 | ★★☆ | 有 | 是 | | `BETWEEN` | 连续范围值 | ★★★ | 无 | 是 | | `FIND_IN_SET` | 历史遗留逗号分隔字段(推荐) | ★☆☆ | 无 | 否 | | JSON 函数 | 半结构化数据查询 | ★★☆ | 无 | 否 | --- ### 实战建议: 1. **超长参数列表** ➜ 优先选择 `JOIN` + 临时表方案 2. **存在性检查** ➜ 使用 `EXISTS` 子查询 3. **连续范围值** ➜ 改用 `BETWEEN` 或范围分段 4. **高频查询场景** ➜ 建立覆盖索引,例如对 `WHERE status IN (...)` 的字段加索引
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值