如何在这个多对多关联中正确添加索引?

时间:2014-12-15 12:43:44

标签: mysql ruby-on-rails ruby indexing has-many-through

我有以下模型方案:

酒店(250,000条记录)

class Hotel < ActiveRecord::Base
  has_many :hotel_services, dependent: :destroy
  has_many :services, through: :hotel_services
end

服务(60条记录)

class Service < ActiveRecord::Base
  has_many :hotel_services
  has_many :hotels, through: :hotel_services
end

hotel_service (1,200,000条记录)

class HotelService < ActiveRecord::Base
  belongs_to :hotel
  belongs_to :service
end

我正面临 n + 1 问题。我正在运行这样的查询:

@hotels = Hotel.includes(:services).where(...)

此查询执行速度非常快(1-2秒),但由于表中的有很多关系和1,200,000,000条记录 hotel_services ,此部分需要30-45秒(取决于在 where 部分。)

我在考虑使用索引来加速执行查询,但是我应该在这个方案中使用哪一个?

先谢谢你们,伙计们。

编辑:hotel_services表格上添加索引:

| 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      |         |               |

并生成EXPLAIN命令:

+----+-------------+------------------+-------+-----------------------------------------------------+-----------------------------------------------------+---------+------+-------+-----------------------+
| 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)

3 个答案:

答案 0 :(得分:1)

指数理论非常大,你应该在其他地方进一步阅读。

无论如何,对于您的特定问题,当您在hotel_services

中的两个字段上添加索引时,会有很大的提升

在您的迁移文件中:

add_index :hotel_services, [:hotel_id, :service_id]

有时生成的索引名称太长而且mysql抱怨它(不应该是这种情况,但只是为了覆盖一些边缘情况)。在这种情况下,我通常将索引命名为:

add_index :hotel_services, [:hotel_id, :service_id], name: :on_foreign_keys

一个完全自以为是的评论:运行一个查询需要1-2秒。

对于特定查询问题,您可以使用explain命令。

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 |       |
+----+-------------+-------+------+---------------+------+---------+------+------+-------+

作为提示,rows值越低越好。向DB添加索引通常会减少数量。

被编入索引的候选人:

  • 联接条款中涉及的字段
  • 涉及where claues的字段

尽量避免在大表中使用type = ALL(完全访问权限)

位于select子句中的索引元素无用

答案 1 :(得分:0)

两个主表的ID上的常用键对于这些表就足够了。我倾向于在链接表上添加包含hotel_id和service_id的双字段索引,而不是在每个字段上添加一个。

它很容易测试,所以玩一下。在进行最终迁移之前,在mysql(或特定数据库品牌的任何工具)中手动添加它们

答案 2 :(得分:0)

如果优化发送到数据库的sql查询,则Rails应用程序将获得显着的性能提升。除非绝对必要,否则不得使用请求来访问数据库。

优化的两个基本步骤:

<强> 1。在所有外键上添加数据库索引

add_index :tasks, :project_id

<强> 2。在明智的地方使用预先加载以避免n+1查询问题。

Project.find(12).includes(:tasks, :notes)

相关问题