Left Join vs. Scalar Subquery |
글쓴이 : 모델광
날짜 : 2022-07-16 (토) 03:17
조회 : 4738
|
|
A recent post on a naver cafe prompted me to write this article: https://cafe.naver.com/dbian/5442 The poster gave a quiz about how we can reduce the number of block IOs of a specific query. And I wondered how query performance will be in PostgreSQL. So I have copied the test scripts (with minor cosmetic changes) and done the test in PostgreSQL. We start with two tables:
주문 is a parent table and 주문처리내역 is a child table. Let's load some data into these tables.
I have updated some values of a column in the 주문 table.
I have disabled parallelism for the purpose of simplicity. SET MAX_PARALLEL_WORKERS_PER_GATHER = 0; The following is the query we have to investigate. SELECT T1.주문ID, T1.주문일시, COALESCE(T1.주문요청자ID,T2.주문처리자ID) 주문요청자ID Here,
the poster asked readers about how we can improve above query
performance. To be honest, I couldn't find any inefficiency in the above
query even after reading the execution plan. The following is the
output I got when I ran the EXPLAIN command with the (analyze, buffers, costs off) option. Sort (actual time=149.187..149.509 rows=10000 loops=1) The following is how to read the plan. 1. The first thing done is a Seq Scan on "주문". (actual time=5.900..110.233 rows=10000 loops=1) means that the Seq Scan was executed 1 time (the loops value), that it returned 10,000 rows, and that the actual time was 110.233 ms. Buffers: shared hit=2300 means that while doing a Seq Scan, 2300 data blocks were found in the shared buffer. You can find the number of blocks the table 주문 has with a query against pg_class like so: SELECT relpages FROM pg_class WHERE relname = '주문'; relpages ------------ 2280 I can not explain why it accessed 2300 buffers, not 2280. The results of the Seq Scan are passed up to a Nested Loop Left Join node. 2. For each row from Seq Scan on "주문", Nested Loop Left Join node calls Index Scan. 3. Index Scan probes the pk_주문처리내역 index to find a match with the Index Cond. When Index Scan finds a match, it passes it up to the Nested Loop Left Join node. 4. The Nested Loop Left Join node calls Index Scan again with its second row. 5. It iterates the process of the operation 2 to 4 until the Nested Loop Left Join exhausts 10,000 rows, which means that Index Scan is executed 10,000 times. We can infer that it took about 30 ms doing the Index Scan (0.003 * 10,000 = 30). 6. While doing the Index Scan 10,000 times, PostgreSQL had to access 50,000 blocks, which is a relatively high number compared to 2300 blocks it had to access while doing a Seq Scan. So
after reading the execution plan, can you spot any inefficiency?
Instinctually I thought that the join method might not be optimal and I
tried to force the planner to do a hash join. But it resulted in slower
elapsed time. So I had to see the explanation of the poster. The drawback of the above execution plan is that it is accessing the index 10,000 times. When we take a close look at the COALESCE(T1.주문요청자ID,T2.주문처리자ID) function, we can infer that we don't have to access the T2 table when T1.주문요청자ID is not null. However, the execution plan shows that PostgreSQL is accessing the index of the T2 table regardless of the T1.주문요청자ID value. So
the trick here is to rewrite the query using a scalar subquery. The
scalar subquery is executed after the main query is executed. SELECT T1.주문ID ,T1.주문일시 Sort (actual time=106.273..106.577 rows=10000 loops=1) From the execution plan, we can deduce the following: - The scalar subquery was executed 100 times (loops=100). - While accessing the primary key index, it had to access 5 blocks. By
transforming the original SQL statement into using a scalar subquery we
could decrease the block I/O from 52300 to 2800. And the elapsed time
dropped from 149 ms to 106 ms. Conclusion When we join two tables using the LEFT JOIN clause, consider transforming the query into using a scalar subquery or vice versa. |










