在我們平時寫SQL時,如果遇到需要排除某些數據時,往往使用id <> xxx and id <> xxx,進而改進為id not in (xxx, xxx);
這樣寫沒有問題,而且簡化了SQL,但是往往有些極端情況,使用not in就會造成極大的性能損耗,例如:
select * from test where id not in (select id from test_back) and info like '%test%';
這樣的話select id from test_back將成為一個子查詢,而且不會走索引,每次走一遍全表掃描。
每一條滿足info like '%test%'的記錄都會去調用這個方法去判斷id是否不在子查詢中,具體的執行計划見下面的例子。
改進方法:
1)使用test和test_back進行聯合查詢,id <> id明顯是不行的,這樣只會判斷同一關聯條件下的一行中的id是否相同,無法做到排除某些id。
2)正確的方式應該使用not exists,將條件下推到里面,就不會出現子查詢了:
select * from test t1 where info like '%test%' and not exits (select 1 from test_back t2 where t2.id = t1.id);
apple=# \d test
Table "public.test"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------
id | integer | | not null |
info | text | | |
Indexes:
"test_pkey" PRIMARY KEY, btree (id)
apple=# truncate test;
TRUNCATE TABLE
apple=# insert into test select generate_series(1, 100), 'test'||round(random()*10000)::text;
INSERT 0 100
apple=# select * from test limit 1;
id | info
----+----------
1 | test9526
(1 row)
apple=# insert into test select generate_series(101, 200), 'tes'||round(random()*10000)::text;
INSERT 0 100
apple=# create table test_back as select * from test where id between 50 and 70;
SELECT 21
apple=# explain select * from test where id not in (select id from test_back) and info like '%test%';
QUERY PLAN
---------------------------------------------------------------------
Seq Scan on test (cost=25.88..30.88 rows=49 width=12)
Filter: ((NOT (hashed SubPlan 1)) AND (info ~~ '%test%'::text))
SubPlan 1
-> Seq Scan on test_back (cost=0.00..22.70 rows=1270 width=4)
(4 rows)
apple=# explain select * from test t1 where info like '%test%' and not exists (select 1 from test_back t2 where t2.id = t1.id);
QUERY PLAN
-------------------------------------------------------------------------
Hash Anti Join (cost=1.47..7.13 rows=89 width=12)
Hash Cond: (t1.id = t2.id)
-> Seq Scan on test t1 (cost=0.00..4.50 rows=99 width=12)
Filter: (info ~~ '%test%'::text)
-> Hash (cost=1.21..1.21 rows=21 width=4)
-> Seq Scan on test_back t2 (cost=0.00..1.21 rows=21 width=4)
(6 rows)
例子里面沒有建索引,建索引后,這種優化方式效果更好。
那么進一步擴展來說:
1)!= 不是標准的SQL,<>才是,這兩個在PostgreSQL中是等效的。
2)exits和not exits的意思是逐條將條件下放到判斷條件,而jion方式是先對表進行笛卡爾積,然后判斷同行之間的各列值是否滿足關系。
