一段时间内我一直认为使用or就会导致全表扫描,原因是因为大概2010年的时候写过一段数据处理的存储过程,因为在进行几张大表关联的时候or的使用不当,导致数据的查询非常缓慢。那次经历之后,一直对or的使用心存敬畏,每次在使用or的时候都觉得会导致全表扫描。当时为了解决or的性能问题,采用的方案是union all的方式来替换or。但是这同样带来一个问题——sql的简洁程度降低了。

时隔多年,近期因为一直听到DBA在公司里说很多项目因为or的使用导致查询性能下降,于是又开始问自己,or真的用了就会导致全表扫描吗?那么or这个语法存在价值又是如何呢?

带着这些疑问,觉得多年的一个疑问还是有必要自己试验一下(自己还是缺乏对技术细节深究的精神,这么久才决定自己测试,很是惭愧。)


测试数据库:mysql5.7.10 

测试过程: 

新建表

<span style="font-size:14px;"><span style="font-size:12px;">SET NAMES utf8;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
--  Table structure for `t_in_or_union_test`
-- ----------------------------
DROP TABLE IF EXISTS `t_in_or_union_test`;
CREATE TABLE `t_in_or_union_test` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `text_1` varchar(10) DEFAULT NULL,
  `text_2` varchar(10) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_1` (`text_1`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

SET FOREIGN_KEY_CHECKS = 1;</span></span>
<span style="font-size:14px;"><span style="font-size:12px;">
</span></span>
<span style="font-size:14px;"><span style="font-size:12px;">-- 初始化100W条数据
begin
declare i int;
set i=0;
while i<1000000 do
insert into t_in_or_union_test(text_1,text_2) values(i+1,i+2);
set i=i+1;
end while;
end</span></span>



初始化数据之后,执行下面的脚本,解析之后发现都会走索引。而且去掉explain直接执行sql查询会发现基本上每段查询执行都非常快就查询出结果。

<span style="font-size:14px;"><span style="font-size:12px;">explain
select * from t_in_or_union_test
where text_1 ='100'
or text_1 = '101'
or text_1 = '102'
or text_1 = '103'
or text_1 = '104'
or text_1 = '105'
or text_1 = '106'
or text_1 = '107'
or text_1 = '108'
or text_1 = '109'
or text_1 = '110'</span></span>


此处虽然使用了or,但是并没有导致预想中全表扫描。

再创建一张类似的表进行测试:

<span style="font-size:14px;"><span style="font-size:12px;">SET NAMES utf8;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
--  Table structure for `t_in_or_union_test_2`
-- ----------------------------
DROP TABLE IF EXISTS `t_in_or_union_test_2`;
CREATE TABLE `t_in_or_union_test_2` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `text_1` varchar(10) DEFAULT NULL,
  `text_2` varchar(10) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_1` (`text_1`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

SET FOREIGN_KEY_CHECKS = 1;</span></span>



同样使用脚本进行测试数据的初始化

<span style="font-size:14px;"><span style="font-size:12px;">begin
declare i int;
set i=0;
while i<100000 do
insert into t_in_or_union_test_2(text_1,text_2) values(i+1,i+2);
set i=i+1;
end while;
end</span></span>

然后分别执行以下两段sql:

<span style="font-size:14px;"><span style="font-size:12px;">EXPLAIN
select * from t_in_or_union_test_2 A
left join t_in_or_union_test B on A.text_1 = B.text_1
and B.text_1 = '100'
or B.text_1 = '101'

select * from t_in_or_union_test_2 A
left join t_in_or_union_test B on A.text_1 = B.text_1
and B.text_1 = '100'
or B.text_2 = '101'</span></span>



从sql的解析结果看,第一段会sql会走索引,但是第二段不走索引,去掉explain执行sql查询,结果也是第二段需要执行很长时间。

从上面执行结果来看,两个不同的字段使用or时似乎导致了全表扫描,尽管这两个字段上都建立索引。

再写三段直接通过主键查询的sql

<span style="font-size:14px;"><span style="font-size:12px;">explain
select * from t_in_or_union_test
where 1=1 or id = 100056 or id = 10005

explain
select * from t_in_or_union_test
where 1=1 and (id = 100056 or id = 10005)

explain
select * from t_in_or_union_test
where id = 100056 or id = 10005</span></span>




分别执行,发现第一段sql不走主键索引,第二、三段会走主键索引。

通过上面的几个小试验,基本可以验证在两个相同字段之间使用or不会导致全表扫描,只有出现不通字段自建使用or时会导致全表扫描。

个人认为是一个索引要定位到第1条数据,一个索引要定位到第100条数据,查询优化器无法同时满足这个需求,只能使用全表扫描来查询数据。


简单的做了一些验证,认识了一些以前不确认的点,关于or的使用,大家如果有不同的认识,多多交流。