With performance improvements in mind, I was wondering if and which indexes are helpful on a join table (specifically used in a Rails 3 has_and_belongs_to_many context).
My models are Foo
and Bar
and per rails convention, I have a join table called bars_foos
. There is no primary key or timestamps making the old fields in this table bar_id:integer
and foo_id:integer
. I'm interested in knowing which of the following indexes is best and is without duplication:
add_index :bars_foos, [:bar_id, :foo_id]
add_index :bars_foos, :bar_id
add_index :bars_foos, :foo_id
Basically, I'm not sure if the compound index is enough assuming it is helpful to begin with. I believe that a compound index can be used as a single index for the first item which is why I am pretty sure that using all three lines would certainly result in unnecessary duplication.
The most common usage will be given an instance of model Foo
, I will be asking for its associated bars
using the RoR syntax of foo.bars
and vice versa with bar.foos
for an instance of the model Bar
.
These will generate queries of the type SELECT * FROM bars_foos WHERE foo_id = ?
and SELECT * FROM bars_foos WHERE bar_id = ?
respectively and then using those resultant IDs to SELECT * FROM bars WHERE ID in (?)
and SELECT * FROM foos WHERE ID in (?)
.
Please correct me in the comments if I am incorrect, but I do not believe that, in the context of the Rails application, it is ever going to try to do a query where it specifies both IDs like SELECT * FROM bars_foos where bar_id = ? AND foo_id = ?
.
In the event there are database specific optimization techniques, I will most likely be using PostgreSQL. However, others using this code may want to use it in MySQL or SQLite depending on their Rails configuration so all answers are appreciated.
Indexes can help improve the performance of a nested-loop join in several ways. The biggest benefit often comes when you have a clustered index on the joining column in one of the tables. The presence of a clustered index on a join column frequently determines which table SQL Server chooses as the inner table.
Simple sequential full table scans and then a join on hashes for instance will usually be much faster.
Indexes slows performance of mass inserts, it is usually advised to drop and index before you load a table and build it after the load is complete. In some databases, index structure can get corrupted. Index performance depends on key data type and length.
TLDR: The most efficient join is also the simplest join, 'Relational Algebra'. If you wish to find out more on all the methods of joins, read further. Relational algebra is the most common way of writing a query and also the most natural way to do so.
The oft repeated answer, which tends to always be the case more often than not is, "it depends." More specifically, it depends on what your data is and how it will be used.
The short tl;dr answer for my specific case (and to cover all future bases) is choice #2 which is what I suspected. However, choice #3 would work just fine as, depending on my usage of the data, the extra time and space used creating the compound index could reduce future query lookups.
The reason for this is that databases try to be smart and try to do things as fast as possible regardless of programmer input. The most basic item to consider when adding an index is will this object be looked up by this key. If yes, an index can potentially help speed that up. However, whether this index is even used all comes down to selectivity and the cardinality of the field.
Since foreign keys are typically the IDs of another AR class, cardinality usually will be high. But again, this depends on your data. In my example if there are many Foo
s but few Bar
s, many of the entries in my join table will have simliar bar_id
s. With bar_id
s having a low cardinality, an index on bar_id
may never be used and may be getting in the way by having the database devote time and resources* to adding to this index every time a new bars_foos
entry is created. The same goes with many Bar
s and few Foo
s and few of both.
The general lesson is that when considering an index on a table, decide if the entries will be both looked up by this field and if this field has a high cardinality. That is, does this field have many distinct values? In the case of most join tables "it depends" and we must think more carefully about what the data represents and the relationships themselves. In my case, I will have both many Foo
s and Bar
s and will be looking up Foo
s by their associated bar
s and vice versa.
Another good answer I got at the office was, "why are you worrying about your indexes? Build your app!"
* In a similar question on indexes on STI it was pointed out that the cost of an index is very low so when in doubt, just add it.
Depends on how you are going to query the data.
Assuming you want to search for all of these...
WHERE bar_id = ?
WHERE foo_id = ?
WHERE bar_id = ? AND foo_id = ?
...then you should probably go with an index on {bar_id, foo_id}
and an index on {foo_id}
.
While you could also create a third index on {bar_id}
, the price of maintaining additional index would probably outweigh the benefit of better clustering in the smaller index.
Also, how do you plan to cover your queries with indexes? Some of the alternatives, such as...
{foo_id, bar_id}
and {bar_id}
{foo_id, bar_id}
and {bar_id, foo_id}
...might cover certain kinds of queries better.
Covering is a balancing act - sometimes adding a field to an index just for covering purposes is justified, sometimes it's not. You won't know until you measure on realistic amounts of data.
(Disclaimer: I'm not familiar with Ruby. This answer is purely from the database perspective.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With