Query Optimisation

Working with our airline example database, and the following assumptions: We will ask "who flew to Miami on a Friday, and paid less than $1000":
        SELECT p.name
        FROM airports a, flights f, reservations r, people p
        WHERE r.paid<1000
          AND r.day='F'
          AND a.name='Miami'
          AND a.code=f.dest
          AND f.number=r.flight
          AND r.passenger=p.id
The first three conditions are filters accepting only the records we are interestde in; the last three conditions are effectively the natural join conditions, telling us how to combine entries from individual tables.

First, we look at the Canonical Query Tree which represents the way a non-optimising database system might attack the question. I have been unable to find a good way to draw a tree in HTML, so I'll represent it as a sequence of steps instead:
  1. Join airports with flights (where a.code=f.dest), call the resultant table T1.
  2. Join T1 with reservations (where r.flight=f.number), call the resultant table T2.
  3. Join T2 with people (where r.passenger=p.id), call the resultant table T3.
  4. Filter T3 with (r.paid<1000) and (r.day='F') and (a.name='Miami'), call the resultant table T4.
  5. Project T4 keeping only p.name, call the resultant table T5.
  6. T5 is the answer.
Now we work out how long each step takes, and how much data they produce, remembering that we are not using any indexes.
  1. Joining unindexed tables requires nested searches through them both; number of disc operations = size-of-one time size-of-other. time is 100,000 ops. Each flight record is only successfully joined with one airport record, so #T1 = #flights = 1,000.
  2. Same as above, #T1=1,000, #reservations=1,000,000, so time is 1,000,000,000 ops. Each reservation matches only one flight, so #T2 = #reservations = 1,000,000.
  3. Same as above, #T2=1,000,000, #people=100,000, so time is 100,000,000,000 ops. Each reservation only successfully matches one person, so #T3 = #T2 = 1,000,000.
  4. Each of the 1,000,000 incoming records must be scanned, taking time 1,000,000 ops. only 50%×5%×20% = 0.5% will survive the filtering, so #T4 = 5,000.
  5. Each of the 5,000 records must be individually processed to remove the unwanted information, taking a time of 5,000 ops and delivering a final answer with 5,000 records describing people who flew to Miami on a Friday for less than $1,000.
The total time is about 100,000,000,000 disc operations. Ignoring the (perfectly real) possibility of reading multiple records at the same time, and assuming 11mS per access, that comes to 1,100,000,000 seconds or 35 years.


We know that Select operations can move through the query tree, so long as they don't try to use information before it becomes available. Project operations can also be moved, but that does not usually produce much of an improvement. Moving Selects down the tree is usually good because Select operations discard some (usually most) incoming records, so the sizes of intermediate tables can be significantly reduced. Let's try it: Step 3 was the most time-consuming step, so we'll stop the reanalysis here; by moving the selects, we have got things working 200 times faster, which is a huge improvement, but the query will still take 2 months.


The lesson is that indexes are really important. Optimisation does a lot of good, but without indexes the situation is often hopeless. If we had indexes on each of the natural join attributes of each of the tables, the time taken to perform a join would be proportional to the size of the result, not to the product of the sizes of the inputs, so step 3 would be down to (some small constant times) 5,000 ops. That is what would make everything possible.

Remember also that there is also the possibility of joining the tables in a different order. We could not sensibly have started by joining reservations with airports, becasue there is no direct connection between those two tables, but our first join could have been between people and reservations, and that could have made a big difference to the timing. For each viable ordering of the Joins, move all Selects as far down the tree as possible, work out the timings and pick the best.