通常、DISTINCT
を使用することをお勧めします GROUP BY
の代わりに 、それが実際に必要なことなので、オプティマイザに「最良の」実行計画を選択させます。ただし、完璧なオプティマイザはありません。 DISTINCT
を使用する オプティマイザは、実行プランに対してより多くのオプションを持つことができます。しかし、それはまた、悪い計画を選択するためのより多くのオプションがあることを意味します 。
DISTINCT
クエリは「遅い」ですが、数字はわかりません。私のテストでは( MariaDB 10.0.19 の10倍の行数) および10.3.13 )DISTINCT
クエリは(のみ)25%遅くなります(562ms / 453ms)。 EXPLAIN
結果はまったく役に立ちません。それも「嘘つき」です。 LIMIT 100, 30
を使用 少なくとも130行を読み取る必要があります(これが私のEXPLAIN
です 実際にGROUP BY
を検索します )、ただし65が表示されます。
実行時間の25%の違いを説明することはできませんが、エンジンはいずれにせよ完全なテーブル/インデックススキャンを実行しているようで、100をスキップして30行を選択する前に結果を並べ替えます。
おそらく最良の計画は次のとおりです。
-
idx_reg_date
から行を読み取ります インデックス(テーブルA
)降順で1つずつ -
idx_order_id
に一致するものがあるかどうかを確認します インデックス(テーブルB
) - 一致する行を100行スキップします
- 一致する30行を送信する
- 終了
A
に10%の行がある場合 B
に一致するものがありません 、このプランはA
から143行のようなものを読み取ります 。
どういうわけかこの計画を強制するために私ができる最善のことは:
SELECT A.id
FROM `order` A
WHERE EXISTS (SELECT * FROM order_detail_products B WHERE A.id = B.order_id)
ORDER BY A.reg_date DESC
LIMIT 30
OFFSET 100
このクエリは、156ミリ秒で同じ結果を返します(GROUP BY
の3倍の速さ) )。しかし、それでも遅すぎます。そして、おそらくまだテーブルA
のすべての行を読み取っています 。
「小さな」サブクエリのトリックで、より良い計画が存在する可能性があることを証明できます。
SELECT A.id
FROM (
SELECT id, reg_date
FROM `order`
ORDER BY reg_date DESC
LIMIT 1000
) A
WHERE EXISTS (SELECT * FROM order_detail_products B WHERE A.id = B.order_id)
ORDER BY A.reg_date DESC
LIMIT 30
OFFSET 100
このクエリは「時間なし」(〜0 ms)で実行され、テストデータで同じ結果を返します。また、100%信頼できるわけではありませんが、オプティマイザが適切に機能していないことを示しています。
だから私の結論は何ですか:
- オプティマイザーは常に最善の仕事をするわけではなく、時には助けが必要です
- 「最善の計画」を知っていても、常にそれを実施できるとは限りません
-
DISTINCT
GROUP BY
より常に高速であるとは限りません - すべての句にインデックスを使用できない場合-物事はかなりトリッキーになっています
テストスキーマとダミーデータ:
drop table if exists `order`;
CREATE TABLE `order` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`reg_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_reg_date` (`reg_date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
insert into `order`(reg_date)
select from_unixtime(floor(rand(1) * 1000000000)) as reg_date
from information_schema.COLUMNS a
, information_schema.COLUMNS b
limit 218860;
drop table if exists `order_detail_products`;
CREATE TABLE `order_detail_products` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`order_id` bigint(20) unsigned NOT NULL,
`order_detail_id` int(11) NOT NULL,
`prod_id` int(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_order_detail_id` (`order_detail_id`,`prod_id`),
KEY `idx_order_id` (`order_id`,`order_detail_id`,`prod_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
insert into order_detail_products(id, order_id, order_detail_id, prod_id)
select null as id
, floor(rand(2)*218860)+1 as order_id
, 0 as order_detail_id
, 0 as prod_id
from information_schema.COLUMNS a
, information_schema.COLUMNS b
limit 437320;
クエリ:
SELECT DISTINCT A.id
FROM `order` A
JOIN order_detail_products B ON A.id = B.order_id
ORDER BY A.reg_date DESC
LIMIT 30 OFFSET 100;
-- 562 ms
SELECT A.id
FROM `order` A
JOIN order_detail_products B ON A.id = B.order_id
GROUP BY A.id
ORDER BY A.reg_date DESC
LIMIT 30 OFFSET 100;
-- 453 ms
SELECT A.id
FROM `order` A
WHERE EXISTS (SELECT * FROM order_detail_products B WHERE A.id = B.order_id)
ORDER BY A.reg_date DESC
LIMIT 30 OFFSET 100;
-- 156 ms
SELECT A.id
FROM (
SELECT id, reg_date
FROM `order`
ORDER BY reg_date DESC
LIMIT 1000
) A
WHERE EXISTS (SELECT * FROM order_detail_products B WHERE A.id = B.order_id)
ORDER BY A.reg_date DESC
LIMIT 30 OFFSET 100;
-- ~ 0 ms