Take the 2-minute tour ×
Database Administrators Stack Exchange is a question and answer site for database professionals who wish to improve their database skills and learn from others in the community. It's 100% free, no registration required.

I have a table with a multicolumn index, and I have doubts about the proper sorting of the indexes to get the maximum performance on the queries.

The scenario:

  • PostgreSQL 8.4, table with about 1MM rows

  • Values in column c1 can have about 100 different values. We can assume the values are evenly distributed, so we have about 10000 rows for every possible value.

  • Column c2 can have 1000 different values. We have 1000 rows for every possible value.

When searching data, the condition always includes values for these two columns, so the table has a multicolumn index combining c1 and c2. I have read about the importance of properly ordering the columns in a multicolumn index if you have queries using just one column for filtering. This is not the case in our scenario.

My question is this one:

Given the fact that one of the filters selects a much smaller set of data, could I improve performance if the first index is the most selective one (the one which allows a smaller set)? I had never considered this question until I saw the graphics from the referenced article:

enter image description here

Image taken from the referenced article about multicolumn indexes.

share|improve this question
    
Do all your queries have WHERE c1=@ParameterA AND c2=@ParameterB? –  ypercube Jan 19 '13 at 13:55
    
No queries with just WHERE c1=@ParameterA or WHERE c2=@ParameterB or WHERE c1=@ParameterA GROUP BY c2 ? Or WHERE c1=@ParameterA AND c2 BETWEEN @ParamB1 AND @ParamB2? –  ypercube Jan 19 '13 at 13:56
    
No. The queries use values from the two columns for filtering. I have no queries using just one column for filtering. Al of them are: WHERE c1=@ParameterA AND c2=@ParameterB –  jap1968 Jan 19 '13 at 13:57
    
And the filtering is always equality checks (=), never range conditions (inequality or BETWEEN)? –  ypercube Jan 19 '13 at 13:59
    
There are also conditions like this: WHERE c1 = "abc" AND c2 LIKE "ab%" –  jap1968 Jan 19 '13 at 14:00
add comment

2 Answers

up vote 5 down vote accepted

Answer

Since you refer to the website use-the-index-luke.com, consider the chapter

Use The Index, Luke › The Where Clause › Searching For Ranges › Greater, Less and BETWEEN

It has an example that matches your situation perfectly (two-column index, one is tested for equality, the other for range), explains (with more of those nice index graphics) why @ypercube's advice is accurate and sums it up:

Rule of thumb: index for equality first — then for ranges.

Also good for just one column?

What to do for queries on just one column seems to be clear. More details and benchmarks concerning that under these related question:
Working of indexes in PostgreSQL
Is a composite index also good for queries on the first field?

Less selective column first?

Apart from that, what if you have only equality conditions for both columns?

One of the answers (now deleted) claims the less selective column should go first. I claim it doesn't matter. Put the column first that is more likely to receive conditions of its own, which actually matters.

Consider this demo, or reproduce it yourself. I create a simple table of two columns with 100k rows. One with very few, the other one with lots of distinct values:

CREATE TEMP TABLE t AS
SELECT (random() * 10000)::int AS lots
      ,(random() * 4)::int     AS few
FROM generate_series (1, 100000);

DELETE FROM t WHERE random() > 0.9;  -- create some dead tuples, more "real-life"

ANALYZE t;

SELECT count(distinct lots)   -- 9999
      ,count(distinct few)    --    5
FROM   t;

Query:

SELECT *
FROM   t
WHERE  lots = 2345
AND    few = 2;

EXPLAIN ANALYZE output (Best of 10 to exclude caching effects):

Seq Scan on t  (cost=0.00..5840.84 rows=2 width=8)
               (actual time=5.646..15.535 rows=2 loops=1)
  Filter: ((lots = 2345) AND (few = 2))
  Buffers: local hit=443
Total runtime: 15.557 ms

Add index, retest:

CREATE INDEX t_lf_idx ON t(lots, few);

Index Scan using t_lf_idx on t  (cost=0.00..3.76 rows=2 width=8)
                                (actual time=0.008..0.011 rows=2 loops=1)
  Index Cond: ((lots = 2345) AND (few = 2))
  Buffers: local hit=4
Total runtime: 0.027 ms

Add other index, retest:

DROP INDEX t_lf_idx;
CREATE INDEX t_fl_idx  ON t(few, lots);

Index Scan using t_fl_idx on t  (cost=0.00..3.74 rows=2 width=8)
                                (actual time=0.007..0.011 rows=2 loops=1)
  Index Cond: ((few = 2) AND (lots = 2345))
  Buffers: local hit=4
Total runtime: 0.027 ms
share|improve this answer
    
Thanks a lot! That was just the information I was looking for. –  jap1968 Jan 20 '13 at 9:26
add comment

If, as you say, the queries involving these 2 columns, are all equality checks of both columns, e.g.:

WHERE c1=@ParameterA AND c2=@ParameterB

do not bother with this. I doubt there will be any difference and if there is one, it will be negligible. You can always test of course, with your data and your server settings. Different versions of a DBMS can behave slightly differently regarding optimization.

The order inside the index would matter for other types of queries, having checks of one column only, or inequality conditions, or conditions on one column and grouping in the other, etc.

If I were to choose one of the two orders, I'd choose to put the less selective column first. Consider a table with columns year and month. It's more probable that you need a WHERE year = 2000 condition or a WHERE year BETWEEN 2000 AND 2013 or a WHERE (year, month) BETWEEN (1999, 6) AND (2000, 5).

A query of the type WHERE month = 7 GROUP BY year may be wanted sure (Find people born on July), but would be less often. That depends of course on the actual data stored in your table. Choose one order for now, say the (c1, c2) and you can always add another index later (c2, c1).


Update, after the OP's comment:

There are also conditions like this: WHERE c1 = 'abc' AND c2 LIKE 'ab%'

This type of query if exactly a range condition on c2 column and would need a (c1, c2) index. If you also have queries of the reverse type:

WHERE c2 = 'abc' AND c1 LIKE 'ab%'

then it would be good if you had a (c2, c1) index as well.

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.