Indexes
Using EXPLAIN ANALYZE to measure actual execution
In the previous chapter, you learned how to use EXPLAIN to inspect the execution plan selected by the planner. You also learned that EXPLAIN doesn't execute the SQL statement. As a result, values such as cost, rows, and width are estimates made by the planner.
But what if you want to compare those estimates with what actually happens when PostgreSQL executes the statement?
For that, you can use EXPLAIN ANALYZE.
Using EXPLAIN ANALYZE
Suppose you want to inspect the actual execution of the following statement:
12SELECT *FROM products;Place EXPLAIN ANALYZE before the statement:
123EXPLAIN ANALYZESELECT *FROM products;Type the command into the SQL editor and run it. You should see output similar to the following:
123456789 QUERY PLAN------------------------------------------------------------------------------------------------------------- Seq Scan on products (cost=0.00..96.00 rows=5000 width=39) (actual time=0.019..0.910 rows=5000.00 loops=1) Buffers: shared hit=46 Planning: Buffers: shared hit=33 Planning Time: 0.109 ms Execution Time: 1.925 ms(6 rows)Your timing and buffer values will probably differ from the ones shown here. They can change between executions depending on your computer, other activity in the database, and whether the required data is already cached.
The estimated cost, row count, and plan itself should usually be more stable, although they can also change if the data or database statistics change.
Estimated and actual values
The plan still begins with the estimates displayed by a plain EXPLAIN:
1cost=0.00..96.00 rows=5000 width=39Because the statement was executed, the node now contains an additional group of values:
1actual time=0.019..0.910 rows=5000.00 loops=1Let's examine each of these values.
actual time=0.019..0.910
The two numbers show how long the node took to start producing rows and how long it took to finish. Unlike PostgreSQL's estimated costs, actual times are measured in milliseconds.
In this example:
0.019milliseconds is how long the node took to produce its first row.0.910milliseconds is how long the node took to finish producing all its rows.
The second number already includes the first. So, the Seq Scan node took approximately 0.910 milliseconds to complete, not 0.929 milliseconds.
rows=5000.00
The plain EXPLAIN output contained the following estimate:
1rows=5000The actual section reports:
1rows=5000.005000 is the number of rows the planner estimated this node would produce.
5000.00 is the average number of rows the node actually produced each time it was executed.
You may notice that the actual row count is displayed as 5000.00 rather than 5000. PostgreSQL reports this value as an average per execution of the node, so it can contain decimal places.
In this example, loops=1, so the node was executed only once and actually produced 5,000 rows.
The estimate and actual result are equal:
12Estimated rows: 5000Actual rows: 5000This tells us that the planner estimated the number of rows accurately.
Comparing estimated and actual row counts is one of the most useful reasons to run EXPLAIN ANALYZE. Large differences, especially differences of several orders of magnitude, may indicate that PostgreSQL doesn't have enough information to estimate the result accurately.
An inaccurate estimate doesn't automatically mean that PostgreSQL selected a poor plan, but it's an important warning sign because row estimates influence decisions about scans, joins, sorting, and other operations.
loops=1
The loops value tells you how many times the plan node was executed.
1loops=1In this example, the Seq Scan node was executed once.
Some plan nodes can be executed multiple times. When that happens, the actual time and rows values shown for the node are averages for one execution of that node.
For example:
1actual time=0.010..0.050 rows=2.00 loops=10This means the node was executed 10 times, and each execution produced an average of 2 rows.
Buffer information
The output may also contain information such as:
1Buffers: shared hit=46PostgreSQL stores table and index data in units called blocks. When PostgreSQL accesses this data, the required blocks are held in an area of memory called shared buffers.
shared hit=46 means that 46 block accesses found the required blocks already in shared buffers. PostgreSQL didn't need to read those blocks into shared buffers again.
You may also see a read value:
1Buffers: shared hit=20 read=26read=26 means that PostgreSQL had to read 26 blocks into shared buffers. This doesn't necessarily mean that the blocks were read from physical storage, because the operating system may already have had the data in memory.
For now, the important distinction is:
hitmeans the required data was already in PostgreSQL's shared buffers.readmeans the required data had to be read into shared buffers.
Planning time
The output includes the time PostgreSQL spent creating the execution plan:
1Planning Time: 0.109 msPlanning time includes the time required to create and optimize the plan from the rewritten statement. It doesn't include the earlier parsing and rewriting stages.
For small and simple statements, planning can occasionally take longer than execution. This isn't necessarily a problem. It may simply mean that executing the selected plan required very little work.
Execution time
The output also reports:
1Execution Time: 1.925 msExecution time is the total time PostgreSQL spent running the statement through the executor. It includes executor startup and shutdown work in addition to the work performed by the plan nodes.
It doesn't include the time spent:
Parsing the statement
Rewriting the statement
Creating and optimizing the execution plan
Sending the resulting rows to the client
Although EXPLAIN ANALYZE executes the SELECT statement, it discards the result instead of sending the 5,000 product rows to the client.
Therefore, Execution Time isn't the complete time an application would experience. It measures the time PostgreSQL spent executing the statement, but it doesn't include the time required to send the resulting rows to the client. EXPLAIN ANALYZE also adds some measurement overhead of its own.
EXPLAIN ANALYZE executes the statement
As I explained earlier, EXPLAIN ANALYZE actually executes the SQL statement, unlike plain EXPLAIN.
For a SELECT statement, PostgreSQL executes the query but discards the rows instead of returning them to the client. However, statements such as INSERT, UPDATE, DELETE, and MERGE will make their normal changes to the database.
For example, the following statement would actually update the matching row:
1234EXPLAIN ANALYZEUPDATE productsSET price = 99.99WHERE id = 1;When you want to analyze a data-changing statement without keeping its changes, you can run it inside a transaction and roll the transaction back:
12345678BEGIN;
EXPLAIN ANALYZEUPDATE productsSET price = 99.99WHERE id = 1;
ROLLBACK;EXPLAIN ANALYZE still executes the UPDATE, allowing PostgreSQL to collect the actual execution statistics, but ROLLBACK undoes the transactional changes afterward.
Alternative syntax
ANALYZE can also be supplied using the parenthesized option syntax:
123EXPLAIN (ANALYZE)SELECT *FROM products;This is equivalent to:
123EXPLAIN ANALYZESELECT *FROM products;The parenthesized form is useful when you want to combine ANALYZE with other options:
123EXPLAIN (ANALYZE, FORMAT JSON)SELECT *FROM products;