0

scoreB ツリー インデックスを持つ列を持つ製品のテーブルがあります。現在のセッションでユーザーに表示されていない製品を返すクエリがあります。結果は列によって並べ替えられる必要があり、クエリ呼び出し間で変更される可能性がLIMITあるため、単純なページネーションを単純に使用することはできません。score

私の現在のソリューションは次のように機能します。

SELECT * 
FROM products p 
LEFT JOIN product_seen ps 
  ON (ps.session_id = ? AND p.product_id = ps.product_id )
WHERE ps.product_id is null
ORDER BY p.score DESC
LIMIT 30;

これは最初の数ページでは問題なく機能しますが、応答時間はセッションで既に表示されている製品の数に比例して増加し、この数が ~300 に達するまでに 2 番目のマークに達します。これを MySQL で固定する方法はありますか? または、この問題をまったく別の方法で解決する必要がありますか?


編集: これらは2つのテーブルです:

CREATE TABLE `products` (
 `product_id` int(15) NOT NULL AUTO_INCREMENT,
 `shop` varchar(15) NOT NULL,
 `shop_id` varchar(25) NOT NULL,
 `shop_category_id` varchar(20) DEFAULT NULL,
 `shop_subcategory_id` varchar(20) DEFAULT NULL,
 `shop_designer_id` varchar(20) DEFAULT NULL,
 `shop_designer_name` varchar(40) NOT NULL,
 `created_at` timestamp NULL DEFAULT NULL,
 `product_url` varchar(255) NOT NULL,
 `name` varchar(255) NOT NULL,
 `description` mediumtext NOT NULL,
 `price_cents` int(10) NOT NULL,
 `list_image_url` varchar(255) NOT NULL,
 `list_image_height` int(4) NOT NULL,
 `ending` timestamp NULL DEFAULT NULL,
 `category_id` int(5) NOT NULL,
 `last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
 `included_at` timestamp NULL DEFAULT NULL,
 `hearts` int(5) NOT NULL,
 `score` decimal(10,5) NOT NULL,
 `rand_field` decimal(16,15) NOT NULL,
 `last_score_update` timestamp NULL DEFAULT NULL,
 `active` tinyint(1) NOT NULL DEFAULT '0',
 PRIMARY KEY (`product_id`),
 UNIQUE KEY `unique_shop_id` (`shop`,`shop_id`),
 KEY `score_index` (`active`,`score`),
 KEY `included_at_index` (`included_at`),
 KEY `active_category_score` (`active`,`category_id`,`score`),
 KEY `active_category` (`active`,`category_id`,`product_id`),
 KEY `active_products` (`active`,`product_id`),
 KEY `active_rand` (`active`,`rand_field`),
 KEY `active_category_rand` (`active`,`category_id`,`rand_field`)
) ENGINE=InnoDB AUTO_INCREMENT=55985 DEFAULT CHARSET=utf8

CREATE TABLE `product_seen` (
 `seenby_id` int(20) NOT NULL AUTO_INCREMENT,
 `session_id` varchar(25) NOT NULL,
 `product_id` int(15) NOT NULL,
 `last_seen` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
 `sorting` varchar(10) NOT NULL,
 `in_category` int(3) DEFAULT NULL,
 PRIMARY KEY (`seenby_id`),
 KEY `last_seen_index` (`last_seen`),
 KEY `session_id` (`session_id`,`seenby_id`),
 KEY `session_id_2` (`session_id`,`sorting`,`seenby_id`)
) ENGINE=InnoDB AUTO_INCREMENT=17431 DEFAULT CHARSET=utf8


編集 2:
上記のクエリは簡略化したものです。これは、次の実際のクエリEXPLAINです。

EXPLAIN SELECT 
    DISTINCT p.product_id AS id, 
    p.list_image_url AS image, 
    p.list_image_height AS list_height, 
    hearts, 
    active AS available, 
    (UNIX_TIMESTAMP( ) - ulp.last_action) AS last_loved
FROM `looksandgoods`.`products` p
LEFT JOIN `looksandgoods`.`user_likes_products` ulp 
ON ( p.product_id = ulp.product_id AND ulp.user_id =1 )
LEFT JOIN `looksandgoods`.`product_seen` sb 
ON (sb.session_id = 'y7lWunZKKABgMoDgzjwDjZw1' 
    AND sb.sorting = 'trend'
    AND p.product_id = sb.product_id )
WHERE p.active =1
AND sb.product_id IS NULL
ORDER BY p.score DESC
LIMIT 30 ;


出力を説明します。結合のキーは存在しますが、まだ一時テーブルとファイルソートがあります。

+----+-------------+-------+-------+----------------------------------------------------------------------------------------------------+------------------+---------+----------------------------------+------+----------------------------------------------+
| id | select_type | table | type  | possible_keys                                                                                      | key              | key_len | ref                              | rows | Extra                                        |
+----+-------------+-------+-------+----------------------------------------------------------------------------------------------------+------------------+---------+----------------------------------+------+----------------------------------------------+
|  1 | SIMPLE      | p     | range | score_index,active_category_score,active_category,active_products,active_rand,active_category_rand | score_index      | 1       | NULL                             | 2299 | Using where; Using temporary; Using filesort |
|  1 | SIMPLE      | ulp   | ref   | love_count_index,user_to_product_index,product_id                                                  | love_count_index | 9       | looksandgoods.p.product_id,const |    1 |                                              |
|  1 | SIMPLE      | sb    | ref   | session_id,session_id_2                                                                            | session_id       | 77      | const                            |  711 | Using where; Not exists; Distinct            |
+----+-------------+-------+-------+----------------------------------------------------------------------------------------------------+------------------+---------+----------------------------------+------+----------------------------------------------+
4

1 に答える 1

1

新しい答え

実際のクエリの問題はDISTINCT句だと思います。product_seenこれは、およびテーブルのいずれかまたは両方が、結果セットに表示される可能性のあるuser_likes_productsそれぞれの複数の行を結合できることを意味します (テーブルにsがやや不穏に欠けている場合)。これが句を含めた理由です。残念ながら、MySQL がクエリを処理するために一時テーブルを作成する必要があることも意味します。product_idUNIQUE KEYproduct_seenDISTINCT

先に進む前に、できることなら...

ALTER TABLE product_seen ADD UNIQUE KEY (session_id, product_id, sorting);

...と...

ALTER TABLE user_likes_products ADD UNIQUE KEY (user_id, product_id);

...その場合、DISTINCT句は冗長であり、それを削除すると問題が解消されます。注:必ずしもこれらのキーを追加する必要があると言っているわけではありませんが、これらのフィールドが常に一意であることを確認するためです。

それが不可能な場合は、別の解決策があるかもしれませんが、結合に関係するテーブルについてもっと知る必要があります。

古い答え

EXPLAINクエリの結果は...

+----+-------------+-------+------+---------------+------------+---------+-------+------+-------------------------+
| id | select_type | table | type | possible_keys | key        | key_len | ref   | rows | Extra                   |
+----+-------------+-------+------+---------------+------------+---------+-------+------+-------------------------+
|  1 | SIMPLE      | p     | ALL  | NULL          | NULL       | NULL    | NULL  |   10 | Using filesort          |
|  1 | SIMPLE      | ps    | ref  | session_id    | session_id | 27      | const |    1 | Using where; Not exists |
+----+-------------+-------+------+---------------+------------+---------+-------+------+-------------------------+

...これは、テーブルでインデックスを使用していないことを示しているproductsため、テーブルスキャンとファイルソートを実行する必要があるため、低速です。

アクティブな製品のみを表示するようにクエリを変更することで使用できるインデックスが(active, score)あることに気付きました...

SELECT *
FROM products p
LEFT JOIN product_seen ps
  ON (ps.session_id = ? AND p.product_id = ps.product_id )
WHERE p.active=TRUE AND ps.product_id is null
ORDER BY p.score DESC
LIMIT 30;

...これは...に変わりEXPLAINます

+----+-------------+-------+-------+-----------------------------+-------------+---------+-------+------+-------------------------+
| id | select_type | table | type  | possible_keys               | key         | key_len | ref   | rows | Extra                   |
+----+-------------+-------+-------+-----------------------------+-------------+---------+-------+------+-------------------------+
|  1 | SIMPLE      | p     | range | score_index,active_products | score_index | 1       | NULL  |   10 | Using where             |
|  1 | SIMPLE      | ps    | ref   | session_id                  | session_id  | 27      | const |    1 | Using where; Not exists |
+----+-------------+-------+-------+-----------------------------+-------------+---------+-------+------+-------------------------+

...これは現在、範囲スキャンを実行しており、ファイルソートは実行していません。これにより、はるかに高速になるはずです。

または、非アクティブな製品も返すようにしたい場合は、インデックスscoreのみを追加する必要があります...

ALTER TABLE products ADD KEY (score);
于 2013-04-17T11:25:39.403 に答える