web-dev-qa-db-ja.com

制限とインデックスによる結合クエリのパフォーマンスの向上

2つの大きなテーブルに対するクエリがあります。 1つ目は場所での最新のユーザーアクティビティを記録し、2つ目は場所の自然な主キーを持つディメンションテーブルです。

ここでのテーブルサイズはuser_location_ratingで約1億行、dim_locationで約1000万行です。ほとんどのユーザーのuser_location_ratingには1000件未満のレコードがあり、それらのユーザーにはクエリのパフォーマンスが適切です。

アクティビティデータが多いユーザーの場合、このクエリは2つの単純な選択ですが、それでも遅くなる可能性があります。クエリのパフォーマンスを改善したいと思います。追加のインデックスを追加してこれを行うことはできますか?別の方法として、インデックスを活用して完全なクエリよりも制限されたクエリ(以下など)をより効率的にする方法はありますか?

SELECT d.create_time
FROM user_location_rating f
JOIN dim_location d using(location_id)
WHERE f.user_id=?
  AND f.platform=?
  AND d.category=?;

SELECT d.create_time
FROM user_location_rating f
JOIN dim_location d using(location_id)
WHERE f.user_id=?
  AND f.platform=?
  AND d.category=?
ORDER BY d.create_time DESC
LIMIT 1000;

EXPLAIN SELECTは、これらのクエリで次のようになります(たとえば、999イベントのユーザーの場合)。

+----+-------------+----------------------+--------+-------------------------------+-----------+---------+----------------------------------+------+-------------+
| id | select_type | table                | type   | possible_keys                 | key       | key_len | ref                              | rows | Extra       |
+----+-------------+----------------------+--------+-------------------------------+-----------+---------+----------------------------------+------+-------------+
|  1 | SIMPLE      | user_location_rating | ref    | k_userloc,k_locplat,k_usrplat | k_usrplat | 8       | const,const                      |  999 | Using index |
|  1 | SIMPLE      | dim_location         | eq_ref | PRIMARY                       | PRIMARY   | 4       | user_location_rating.location_id |    1 | Using where |
+----+-------------+----------------------+--------+-------------------------------+-----------+---------+----------------------------------+------+-------------+


+----+-------------+----------------------+--------+-------------------------------+-----------+---------+----------------------------------+------+----------------------------------------------+
| id | select_type | table                | type   | possible_keys                 | key       | key_len | ref                              | rows | Extra                                        |
+----+-------------+----------------------+--------+-------------------------------+-----------+---------+----------------------------------+------+----------------------------------------------+
|  1 | SIMPLE      | user_location_rating | ref    | k_userloc,k_locplat,k_usrplat | k_usrplat | 8       | const,const                      |  999 | Using index; Using temporary; Using filesort |
|  1 | SIMPLE      | dim_location         | eq_ref | PRIMARY                       | PRIMARY   | 4       | user_location_rating.location_id |    1 | Using where                                  |
+----+-------------+----------------------+--------+-------------------------------+-----------+---------+----------------------------------+------+----------------------------------------------+

テーブル定義

CREATE TABLE `user_location_rating` (
  `user_activity_id` int(16) NOT NULL AUTO_INCREMENT,
  `user_id` int(16) NOT NULL DEFAULT '0',
  `location_id` int(16) NOT NULL DEFAULT '0',
  `platform` int(2) NOT NULL DEFAULT '-1',
  `rating` int(2) NOT NULL DEFAULT '-1'
  PRIMARY KEY (`location_id`,`user_activity_id`),
  UNIQUE KEY `k_userloc` (`user_id`,`location_id`),
  KEY `k_locplat` (`location_id`,`platform`),
  KEY `k_usrplat` (`user_id`,`platform`)
) ENGINE=InnoDB ROW_FORMAT=DYNAMIC

CREATE TABLE `dim_location` (
  `location_id` int(16) NOT NULL AUTO_INCREMENT,
  `category` int(2) NOT NULL DEFAULT '0',
  `create_time` datetime DEFAULT NULL,
  PRIMARY KEY (`location_id`)
) ENGINE=InnoDB ROW_FORMAT=DYNAMIC

(ここで実際の定義をクリーンアップして最小限の関連例に近づけるように努力しました。タイプミスが侵入した場合は謝罪してください。)

3
cohoz

最初のテーブルの場合、両方のクエリで、_(platform, user_id, location_id)_または_(user_id, platform, location_id)_のインデックスが最適です。既存のインデックス_k_usrplat_は2番目のインデックスに相当します(InnoDBインデックスには暗黙的にPK列が含まれます)。インデックスが実際に両方のクエリで使用されていることがわかります。

2番目のテーブルの場合は、より複雑です。少なくとも最初のクエリでは、主キーの既存のインデックスを使用して適切なパフォーマンスを得ることができます。可能な改善点は_(category, location_id, create_time)_インデックスです。

_(category, create_time, location_id)_インデックスも試すことができます。これは、2番目のクエリに役立つ場合があります。多くはデータの分布に依存し、効率はさまざまなパラメーターによって異なります。


整数列の定義は奇妙に見えます。 int (16)int (2)があるのはなぜですか?それが列の可能な値に対する制限を意味すると考える場合、それは間違いです。列は同じタイプです。括弧内の数字は、ほとんど無視されているユーザーインターフェイスへのディレクティブにすぎません。これらの列の一部、たとえばplatformが小さな値(たとえば、0-100または0-2000)しか保持できない場合は、適切に小さい型を使用します。

_tiny int   (-128 .. +127)      : 1 byte 
small int  (-32768 .. + 32767) : 2 bytes
medium int (-2^23 .. + 2^23-1) : 3 bytes
tiny int unsigned   (0 .. +255)    : 1 byte 
small int unsigned  (0 .. +65535)  : 2 bytes
medium int unsigned (0 .. +2^24-1) : 3 bytes
_

これにより、ディスクとメモリ使用量の両方で、テーブルとそのインデックスの領域が節約されます。
スペースが少ない-> I/Oが少ない->クエリが高速

5
ypercubeᵀᴹ