MySQL不使用子查询的原因及优化案例
目录
- MySQL不使用子查询的原因及优化案例
- 目录
- 不推荐使用子查询和JOIN的原因
- 解决方案
- 优化案例
- 案例1:查询所有有库存的商品信息
- 案例2:使用EXISTS优化子查询
- 案例3:使用JOIN代替子查询
- 案例4:优化子查询以减少数据量
- 案例5:使用索引覆盖
- 案例6:使用临时表优化复杂查询
- 案例7:使用窗口函数替代子查询
- 案例8:优化子查询以避免全表扫描
- 案例9:使用LIMIT子句限制子查询返回数据量
- 案例10:使用JOIN代替子查询以利用索引
- 总结
不推荐使用子查询和JOIN的原因
在MySQL中,不推荐使用子查询和JOIN主要有以下原因:
- 性能问题:子查询执行时,MySQL需创建临时表存储内层查询结果,查询完再删除,增加CPU和IO资源消耗,易产生慢查询。JOIN操作效率也较低,尤其数据量大时,性能难保证。
- 索引失效:子查询可能使索引失效,MySQL会将查询转为联接执行,子查询不能先执行,若外表大,性能受影响。
- 查询优化器复杂度:子查询影响查询优化器判断,致执行计划不够优化。相比之下,联表查询更易被优化器理解和处理。
- 数据传输开销:子查询可能致大量不必要数据传输,每个子查询都需将结果返回给主查询。而联表查询可通过一次查询返回所有所需数据,减少数据传输开销。
- 维护成本:使用JOIN写的SQL语句,在修改表schema时较复杂,成本大,尤其系统大时,不易维护。
解决方案
针对这些问题,可采取以下解决方案:
- 应用层关联:在业务层单表查询出数据后,作为条件给下一个单表查询,减少数据库层负担。
- 使用IN代替子查询:若子查询结果集小,可用“IN”操作符查询,数据量小时,查询效率更高。
- 使用WHERE EXISTS:WHERE EXISTS比“IN”更好,它检查子查询是否返回结果集,能明显提高查询速度。
- 改写为JOIN:用JOIN查询替代子查询,无需建立临时表,速度快,若查询中用索引,性能更好。
优化案例
案例1:查询所有有库存的商品信息
原始查询(使用子查询):
SELECT * FROM products WHERE id IN (SELECT product_id FROM inventory WHERE stock > 0);
此查询会导致查询速度慢,影响用户体验。
优化方案(使用EXISTS):
SELECT * FROM products WHERE EXISTS (SELECT 1 FROM inventory WHERE inventory.product_id = products.id AND inventory.stock > 0);
该优化方案可大幅提升查询速度,改善用户体验。
案例2:使用EXISTS优化子查询
原始查询:
SELECT * FROM orders WHERE customer_id IN (SELECT customer_id FROM customers WHERE country = 'USA');
优化方案:
SELECT * FROM orders WHERE EXISTS (SELECT 1 FROM customers WHERE orders.customer_id = customers.customer_id AND customers.country = 'USA');
使用EXISTS代替IN子查询可减少回表查询次数,提高查询效率。
案例3:使用JOIN代替子查询
原始查询:
SELECT * FROM orders WHERE customer_id IN (SELECT customer_id FROM customers WHERE country = 'USA');
优化方案:
SELECT o.* FROM orders o JOIN customers c ON o.customer_id = c.customer_id WHERE c.country = 'USA';
使用JOIN代替子查询可减少子查询开销,且更容易利用索引。
案例4:优化子查询以减少数据量
原始查询:
SELECT * FROM orders WHERE customer_id IN (SELECT customer_id FROM customers);
优化方案:
SELECT * FROM orders WHERE customer_id IN (SELECT customer_id FROM customers WHERE active = 1);
限制子查询返回数据量,减少主查询需检查的行数,提高查询效率。
案例5:使用索引覆盖
原始查询:
SELECT customer_id FROM customers WHERE country = 'USA';
优化方案:
CREATE INDEX idx_country ON customers(country);
SELECT customer_id FROM customers WHERE country = 'USA';
为country字段创建索引,使子查询可直接在索引中找到数据,避免回表查询。
案例6:使用临时表优化复杂查询
原始查询:
SELECT * FROM orders WHERE customer_id IN (SELECT customer_id FROM customers WHERE last_order_date > '2023-01-01');
优化方案:
CREATE TEMPORARY TABLE temp_customers AS SELECT customer_id FROM customers WHERE last_order_date > '2023-01-01';
SELECT * FROM orders WHERE customer_id IN (SELECT customer_id FROM temp_customers);
对于复杂子查询,用临时表存储中间结果,简化查询并提高性能。
案例7:使用窗口函数替代子查询
原始查询:
SELECT employee_id, salary, (SELECT AVG(salary) FROM employees WHERE department_id = e.department_id) AS avg_salary FROM employees e;
优化方案:
SELECT employee_id, salary, AVG(salary) OVER (PARTITION BY department_id) AS avg_salary FROM employees;
用窗口函数替代子查询,提高查询效率。
案例8:优化子查询以避免全表扫描
原始查询:
SELECT * FROM users WHERE username IN (SELECT username FROM orders WHERE order_date = '2024-01-01');
优化方案:
CREATE INDEX idx_order_date ON orders(order_date);
SELECT * FROM users WHERE username IN (SELECT username FROM orders WHERE order_date = '2024-01-01');
为order_date字段创建索引,避免全表扫描,提高子查询效率。
案例9:使用LIMIT子句限制子查询返回数据量
原始查询:
SELECT * FROM orders WHERE customer_id IN (SELECT customer_id FROM customers WHERE country = 'USA');
优化方案:
SELECT * FROM orders WHERE customer_id IN (SELECT customer_id FROM customers WHERE country = 'USA' LIMIT 100);
用LIMIT子句限制子查询返回数据量,减少主查询需处理数据量,提高查询效率。
案例10:使用JOIN代替子查询以利用索引
原始查询:
SELECT * FROM transactions WHERE product_id IN (SELECT product_id FROM products WHERE category = 'Equity');
优化方案:
SELECT t.* FROM transactions t JOIN products p ON t.product_id = p.product_id WHERE p.category = 'Equity';
用JOIN代替子查询,并可更容易利用products表上category索引。
总结
这些案例展示了如何通过不同优化策略提升MySQL查询性能,特别是在处理子查询时。以下是一些额外的优化建议:
- 创建合适的索引:经常用于
WHERE
和JOIN
的字段应建立索引,避免在低选择性的字段上建立索引(如性别字段)。 - 避免索引失效的情况:使用函数计算的字段不会使用索引,如
SELECT * FROM orders WHERE YEAR(order_date) = 2023;
应优化为SELECT * FROM orders WHERE order_date >= '2023-01-01';
。 - 组合索引的最左前缀法则:确保查询条件从组合索引的最左列开始。
- 使用EXPLAIN分析查询执行计划:通过
EXPLAIN
关键字可以帮助我们了解查询的执行计划,从而发现性能瓶颈。 - 优化查询语句:避免使用
SELECT *
,使用LIMIT
限制返回行数,重写子查询为JOIN。 - 合理调整Join Buffer:在无索引或索引不可用的情况下,Join Buffer是优化Block Nested-Loop Join的关键,其大小直接影响外层表加载的行数和内层表的扫描效率。
通过这些优化策略,可以显著提升MySQL查询性能,改善用户体验。