This question comes as a follow up to and a result of SQL function very slow compared to query without function wrapper. I should note that I don't consider this a duplicate, since that question was asking for a solution to a specific problem. I am asking for more information about the behavior in general here, and demonstrating how it can be reproduced. (To demonstrate the difference, you can see a fairly long comment thread on the accepted answer where we discussed the behavior, and I felt it was getting off topic, especially given the length.)
I have a function. Here's a sample one that exhibits the behavior of interest:
CREATE OR REPLACE FUNCTION test(INT)
RETURNS TABLE(num INT, letter TEXT)
VOLATILE
LANGUAGE SQL
AS $$
SELECT *
FROM (VALUES (1,'a'),(2,'b'),(3,'c'),(4,'d'),(5,'e')) x
LIMIT $1
$$;
When I run this EXPLAIN
:
EXPLAIN ANALYZE SELECT * FROM test(10);
I get this result in psql (where I've removed a giant "Query Plan" header):
Function Scan on test (cost=0.25..10.25 rows=1000 width=36) (actual time=0.125..0.136 rows=5 loops=1)
Total runtime: 0.179 ms
(2 rows)
Take note of the row estimation. It estimates 1000 rows.
But, if I change the function to STABLE
or IMMUTABLE
:
CREATE OR REPLACE FUNCTION test(INT)
RETURNS TABLE(num INT, letter TEXT)
STABLE
LANGUAGE SQL
AS $$
SELECT *
FROM (VALUES (1,'a'),(2,'b'),(3,'c'),(4,'d'),(5,'e')) x
LIMIT $1
$$;
Then the same EXPLAIN
gives me a different plan:
Limit (cost=0.00..0.06 rows=5 width=36) (actual time=0.010..0.050 rows=5 loops=1)
-> Values Scan on "*VALUES*" (cost=0.00..0.06 rows=5 width=36) (actual time=0.005..0.018 rows=5 loops=1)
Total runtime: 0.087 ms
(3 rows)
Now it correctly estimates 5 rows, and it shows a plan for the query contained inside the function. The cost is an order of magnitude higher. The runtime went down as well. (The query is so short, that might not be especially significant.)
In light of the linked question dealing with much more data and having a very significant performance difference, it would seem that the planner is actually doing something different based on whether the function is VOLATILE
or STABLE
/IMMUTABLE
.
What exactly is the planner doing here, and where can I read some documentation on it?
These tests were run in PG 9.3.