Reputation: 48453
I have a following scheme of models:
hotel (250,000 records)
class Hotel < ActiveRecord::Base
has_many :hotel_services, dependent: :destroy
has_many :services, through: :hotel_services
end
service (60 records)
class Service < ActiveRecord::Base
has_many :hotel_services
has_many :hotels, through: :hotel_services
end
hotel_service (1,200,000 records)
class HotelService < ActiveRecord::Base
belongs_to :hotel
belongs_to :service
end
I am facing the n+1 problem. I am running a query like this:
@hotels = Hotel.includes(:services).where(...)
This query is executed pretty fast (1-2 seconds), but because of the has-many relationship and 1,200,000 million records on the table hotel_services, this part takes between 30-45 seconds (depends on the where part).
I was thinking about using indexes to speed up executing queries, but which one should I use in this scheme?
Thank you in advance, guys.
EDIT: Adding indexes on the hotel_services
table:
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+------------------+------------+-----------------------------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| hotel_services | 0 | PRIMARY | 1 | id | A | 1044995 | NULL | NULL | | BTREE | | |
| hotel_services | 1 | index_hotel_services_on_hotel_id_and_service_id | 1 | hotel_id | A | 522497 | NULL | NULL | YES | BTREE | | |
| hotel_services | 1 | index_hotel_services_on_hotel_id_and_service_id | 2 | service_id | A | 1044995 | NULL | NULL | YES | BTREE | | |
and generated EXPLAIN
command:
+----+-------------+------------------+-------+-----------------------------------------------------+-----------------------------------------------------+---------+------+-------+-----------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+------------------+-------+-----------------------------------------------------+-----------------------------------------------------+---------+------+-------+-----------------------+
| 1 | SIMPLE | hotel_services | range | index_hotel_services_on_hotel_id_and_service_id | index_hotel_services_on_hotel_id_and_service_id | 5 | NULL | 10254 | Using index condition |
+----+-------------+------------------+-------+-----------------------------------------------------+-----------------------------------------------------+---------+------+-------+-----------------------+
1 row in set (0.36 sec)
Upvotes: 1
Views: 70
Reputation: 3347
Index theory is quite big, and you should read further somewhere else.
Anyway, for your particular problem, the big boost will be when you add an index on both fields in your hotel_services
In your migration file:
add_index :hotel_services, [:hotel_id, :service_id]
Sometimes the generated index name is too long and mysql complains about it (this should not be the case, but just to cover some edge cases). In this case I usually name the index:
add_index :hotel_services, [:hotel_id, :service_id], name: :on_foreign_keys
And a completely opinionated comment: 1-2 seconds for a query to run is a lot of time.
For specific query issues you can use the explain
command.
mysql> explain select * from users;
+----+-------------+-------+------+---------------+------+---------+------+------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+------+-------+
| 1 | SIMPLE | users | ALL | NULL | NULL | NULL | NULL | 129 | |
+----+-------------+-------+------+---------------+------+---------+------+------+-------+
As a hint, the lower the rows
value, the better. Adding indices to the DB will usually reduce the number.
Candidates to be indexed:
Try to avoid type=ALL (full access) in big tables
Indexing elements located in the select
clause is not helpful
Upvotes: 1
Reputation: 807
Rails applications will get significant performance boost if the sql
queries sent to the database are optimized. Database must not be hit with requests unless there is an absolute necessity.
1. Add database indexes on all foreign keys
add_index :tasks, :project_id
2. Use Eager loading to avoid n+1
query problems, in sensible places.
Project.find(12).includes(:tasks, :notes)
Upvotes: 0
Reputation:
The usual keys on the ID of the two main tables will be sufficient for those tables. I'd be inclined to add a two-field index encompassing hotel_id and service_id on the link table, rather than one each on each field.
It'll be quite easy to test, so play around a bit. Add them manually in mysql (or whatever tool for your particular db brand) before making the final migration
Upvotes: 0