sql >> データベース >  >> RDS >> Mysql

Distinct vs Group By

    通常、DISTINCTを使用することをお勧めします GROUP BYの代わりに 、それが実際に必要なことなので、オプティマイザに「最良の」実行計画を選択させます。ただし、完璧なオプティマイザはありません。 DISTINCTを使用する オプティマイザは、実行プランに対してより多くのオプションを持つことができます。しかし、それはまた、悪い計画を選択するためのより多くのオプションがあることを意味します 。

    DISTINCT クエリは「遅い」ですが、数字はわかりません。私のテストでは( MariaDB 10.0.19 の10倍の行数) および10.3.13DISTINCT クエリは(のみ)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
    



    1. MySQLでの重複の削除

    2. .NETを介したPL/SQLコレクション型パラメータを使用したOracleプロシージャの呼び出し

    3. 結果をグループ化せずにSUM()を使用する

    4. GROUP BYはどのように機能しますか?