天天看点

一次sql语句编写+优化——使用join代替子查询

需求: 将所有没有订单记录的用户取出来

1.查询所有用户

       SELECT * FROM userinfo

 2.查询有订单记录的用户(下订单的用户)

       SELECT UserID FROM ordersinfo

 3.查询没有订单记录的用户

       SELECT * FROM userinfo

       WHERE UserID NOT in (SELECT UserID FROM ordersinfo )

4.sql优化,使用JOIN代替

       SELECT * FROM userinfo

       LEFT JOIN ordersinfo ON userinfo.UserID=ordersinfo.UserID

       WHERE ordersinfo.UserID IS NULL

优化分析:

逻辑上,查的是用户,两个表都有用户ID(UserID),要查询没有下单的,如果是等值连接,就是查询的所有下单的用户,左外连接的话,既包含下单的用户,也包含没有下单的用户,下单的和没下单的区别在于,订单记录表的用户ID(UserID)是否为null。

前提是能做等值连接,才能做左外连接。

原理上,无索引的情况:比如你要查询id为123的记录,mysql根本不知道到底是否存在id等于123的记录,只能把数据表从头到尾扫描一遍,此时有多少个磁盘块就需要进行多少IO操作,所以查询速度很慢。

         子查询会在内存中创建临时表来完成这个逻辑上的需要两个步骤的查询工作,而你创建的索引在原来的表上,导致我们创建的索引起不到我们想要的作用。

所以Mysql子查询很慢,子查询导致索引不起作用,没有索引就会全表扫描,要避免使用子查询。

而连接(JOIN)不需要在内存中创建临时表,所以更有效率一些。

补充:

和in也有关系。

把3步拆开来看。

SELECT count(*) FROM userinfo;    //大约0.01sec

SELECT count(*) FROM ordersinfo ;    //大约0.01sec

SELECT * FROM userinfo WHERE UserID NOT in (SELECT UserID FROM ordersinfo ) ;   //大约33.58sec

为什么会这么慢呢?

官方文档解释:in 子句在查询的时候有时会被转换为 exists 的方式来执行,变成逐条记录进行遍历(版本 5.5 中存在,5.6 中已做优化)。

参考:

https://dev.mysql.com/doc/refman/5.5/en/subquery-optimization.html

解决方案(5.5版)

1.使用临时表

SELECT * FROM userinfo WHERE UserID NOT in (SELECT  UserID FROM  (SELECT UserID FROM ordersinfo)as tb ) ;

2.使用join

SELECT * FROM userinfo

 LEFT JOIN ordersinfo ON userinfo.UserID=ordersinfo.UserID

 WHERE ordersinfo.UserID IS NULL

版本 5.6 已针对子查询做了优化,方式跟使用临时表的方式一样。