因为mysql的每条查询,每个表上只能选择一个索引。如果使用了 idx_id_mobile 索引,恰好有一条数据,因为有 limit 1 ,那么恭喜很快得到结果;但如果 f_mobile 没有数据,那 f_phone 字段只能在f_id条件下挨个查找,扫描12w行。 or 跟 and 不一样,甚至有开发认为添加 (f_xxx_id,f_mobile,f_phone)不就完美了吗,要吐血了~
那么优化sql呢,很简单(注意f_mobile,f_phone上都要有相应的索引),方法一:
1
2
3
(select f_crm_id from d_dbname1.t_tbname1 where f_xxx_id = 900000 and f_mobile ='1234567891' limit 1 )
UNION ALL
(select f_crm_id from d_dbname1.t_tbname1 where f_xxx_id = 900000 and f_phone ='1234567891' limit 1 )
sql1 = select f_crm_id from d_dbname1.t_tbname1 where f_xxx_id = 900000 and f_mobile ='1234567891' limit 1;
sq1.execute();
if no result sql1:
sql1 = select f_crm_id from d_dbname1.t_tbname1 where f_xxx_id = 900000 and f_phone ='1234567891' limit 1;
sql1.execute();
复杂一点的场景是止返回一条记录那么简单,limit 2:
1
2
3
select a.f_crm_id from d_dbname1.t_tbname1 as a
where (a.f_create_time > from_unixtime('1464107527') or a.f_modify_time > from_unixtime('1464107527') )
limit 0,200
(select a.f_crm_id from d_dbname1.t_tbname1 as a
where a.f_create_time > from_unixtime('1464397527')
limit 0,200 )
UNION ALL
(select a.f_crm_id from d_dbname1.t_tbname1 as a
where a.f_modify_time > from_unixtime('1464397527') and a.f_create_time <= from_unixtime('1464397527')
limit 0,200 )
有人说 把 UNION ALL 改成 UNION 不就去重了吗?如果说查询比较频繁,或者limit比较大,数据库还是会有压力,所以需要做trade off。
这种情况更多还是适合方法二,包括有可能需要 order by limit 情况。改造伪代码:
1
2
3
4
5
6
7
8
sql1 = (select a.f_crm_id from d_dbname1.t_tbname1 as a where a.f_create_time > from_unixtime('1464397527') limit 0,200 );
sql1.execute();
sql1_count = sql1.result.count
if sql1_count < 200 :
sql2 = (select a.f_crm_id from d_dbname1.t_tbname1 as a where a.f_modify_time > from_unixtime('1464397527') and a.f_create_time <= from_unixtime('1464397527') limit 0, (200 - sql1_count) );
sql2.execute();
final_result = paste(sql1,sql2);
or条件在数据库上很难优化,能在代码里优化逻辑,不至于拖垮数据库。只有在 or 条件下无需索引时(且需要比较的数据量小),才考虑。
相同字段 or 可改成 in,如 f_id=1 or f_id=100 -> f_id in (1,100)。 效率问题见文章 mysql中or和in的效率问题 。