On 23/01/2012 7:24 PM, Ryan Johnson wrote:
On 23/01/2012 3:09 PM, Ryan Johnson wrote:
On 23/01/2012 12:51 PM, Richard Hipp wrote:
On Mon, Jan 23, 2012 at 12:48 PM, Simon Slavin<slav...@bigfraud.org> wrote:
I don't know if Dr Hipp is pursuing this privately or expecting it to be
solved collaboratively on this list.


I don't have a test case to work on.
My database file is ~150MB, but was generated by the TPC-H data generator program. Assuming a linux-like environment (including cygwin), the following will reproduce the setup in under five minutes: The problem persists with a freshly-generated database on my machine, using a just-compiled sqlite-3.7.10.

OK, it looks like I didn't install the 3.7.10 binary properly, because the situation is significantly different once ANALYZE has run.

The first query from the OP is remarkably similar to a query mentioned in where.c (from the 3.7.5 sources, which I happened to have handy):
Previous versions of SQLite [did not always find the lowest-cost plan] for scripts such as the following:
    **
    **   CREATE TABLE t1(a, b);
    **   CREATE TABLE t2(c, d);
    **   SELECT * FROM t2, t1 WHERE t2.rowid = t1.a;
    **
The best strategy is to iterate through table t1 first. However it is not possible to determine this with a simple greedy algorithm. Since the cost of a linear scan through table t2 is the same as the cost of a linear scan through table t1, a simple greedy algorithm may choose to use t2 for the outer loop, which is a much costlier approach.

Re-casting my first query as the above gives:
sqlite> explain query plan select * from customer t2, orders t1 where t2.rowid = t1.custkey;
0|0|0|SCAN TABLE customer AS t2 (~15000 rows)
0|1|1|SEARCH TABLE orders AS t1 USING INDEX OrderCustomers (custKey=?) (~15 rows)

The same query plan is chosen if t1 comes first; execution times and stats confirm the improvement.

For query 2, the situation is less clear. First of all, it appears to execute more than 4x faster overall (10s and .2s -- WOW!) but the optimizer still seems to choose the wrong plan (13.6M rows examined):
1|0|0|SCAN TABLE orders (~37500 rows)
1|0|0|USE TEMP B-TREE FOR DISTINCT
0|0|2|SCAN SUBQUERY 1 AS Y (~37500 rows)
0|1|0|SCAN TABLE orders AS O (~75000 rows)
0|2|1|SEARCH TABLE lineitem AS L USING INDEX LineItemOrders (orderKey=?) (~2 rows)

If I force the proper ordering, "X cross join Y", then the following, better-than-expected plan is used instead (only 300k rows examined!):
1|0|0|SCAN TABLE orders (~37500 rows)
1|0|0|USE TEMP B-TREE FOR DISTINCT
0|0|0|SCAN TABLE orders AS O (~75000 rows)
0|1|1|SEARCH TABLE lineitem AS L USING INDEX LineItemOrders (orderKey=?) (~5 rows)
0|2|2|SCAN SUBQUERY 1 AS Y (~18750 rows)

The main difference seems to be that Y should be the innermost loop but isn't.
Update: Running the different pieces alone shows:
- The scan of Orders for Y returns 5681 rows (not 37k)
- The temp B+Tree collapses that to 90 distinct rows
- The scan of Orders for X returns 104k rows (75k is the right ball-park)
- Each order averages 4 line items (5 was a pretty good estimate)
- Subquery 1 as Y has 90 rows (not 18k or 37k)

Given the above, the problem seems to lie with cardinality estimation, particularly because the estimate for DISTINCT's effectiveness was vastly overconservative. I assume the cardinality estimates in the two plans vary for the reason mentioned in where.c, that the estimated cost of a scan might not be as low when it's in the outer loop because certain (index-related?) cardinality reductions only happen in the innermost loop.

While I certainly won't complain if the above weakness could be fixed, at this point I'm satisfied that there's not an easy code tweak (or glaring omission in the SQL) to make the problem go away...

Ryan

_______________________________________________
sqlite-users mailing list
sqlite-users@sqlite.org
http://sqlite.org:8080/cgi-bin/mailman/listinfo/sqlite-users

Reply via email to