Bootstrap

mysql关于in、exists、join查询性能真实案例分析

  1. 数据准备

测试数据5000条,Mysql 8.0

DROP TABLE IF EXISTS `reconc_billing_proposal_request_cancle_info`;
CREATE TABLE `reconc_billing_proposal_request_cancle_info`  (
  `id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
  `send_company_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '发送公司ID',
  `accept_company_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '接受公司ID',
  `send_tenant_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '发送租户ID',
  `accept_tenant_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '接受租户ID',
  `request_status` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '申请动作',
  `request_reason` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '申请理由',
  `buyer_response_status` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '买方回应动作',
  `buyer_response_reason` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '买方回应原因',
  `buyer_response_remark` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '买方回应备注',
  `request_at` datetime NULL DEFAULT NULL COMMENT '申请时间',
  `response_at` datetime NULL DEFAULT NULL COMMENT '回应时间',
  `updated_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '更新人',
  `updated_at` datetime NULL DEFAULT NULL COMMENT '更新时间',
  `created_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '创建人',
  `created_at` datetime NULL DEFAULT NULL COMMENT '创建时间',
  `is_deleted` bigint NULL DEFAULT 0 COMMENT '删除标记',
  `version` bigint NULL DEFAULT 0 COMMENT '版本号',
  `void_bp_line_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '申请作废结算单id',
  `company_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '申请作废公司(卖方公司)id',
  `tenant_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '申请作废租户(卖方租户)id',
  `operator_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '操作人id',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '结算单申请作废记录表' ROW_FORMAT = Dynamic;
  1. 具体分析

In
EXPLAIN SELECT
    id,
    void_bp_line_id,
    created_at 
FROM
    reconc_billing_proposal_request_cancle_info 
WHERE
    id IN (
    SELECT
        SUBSTRING_INDEX ( GROUP_CONCAT( a.id ORDER BY a.created_at DESC ), ',', 1 ) kngId 
    FROM
        reconc_billing_proposal_request_cancle_info a 
    GROUP BY
        a.void_bp_line_id 
    );
explain执行结果:
查询结果分析:

查询时间接近57s,是所有查询方式里最慢的,通过explain分析,它都是全表扫描。

exists
EXPLAIN SELECT
    b.*
FROM
    reconc_billing_proposal_request_cancle_info b 
WHERE
    exists (
    SELECT
        *
    FROM
        (
        SELECT
            SUBSTRING_INDEX ( GROUP_CONCAT( a.id ORDER BY a.created_at DESC ), ',', 1 ) kngId 
        FROM
            reconc_billing_proposal_request_cancle_info a 
        GROUP BY
            a.void_bp_line_id 
        ) temp 
    WHERE
        temp.kngId = b.id 
    );
explain执行结果:
查询结果分析:

查询时间很短,0.057s,通过explain分析,它是会走部分primary key,同时exists适合用于驱动表小,被驱动表较大时,因为查询被驱动表可以走索引,比全表扫描快。

join
EXPLAIN SELECT
    b.* 
FROM
    reconc_billing_proposal_request_cancle_info b
    JOIN (
    SELECT
        SUBSTRING_INDEX ( GROUP_CONCAT( a.id ORDER BY a.created_at DESC ), ',', 1 ) id 
    FROM
        reconc_billing_proposal_request_cancle_info a 
    GROUP BY
        a.void_bp_line_id 
    ) temp ON b.id = temp.id;
explain执行结果:
查询结果分析:

查询时间很短,0.058s,通过explain分析,它也是会走部分primary key,如果把join 换成left join,就变成全表扫描,Extra中会出现Using join buffer (hash join),在连接查询执行过程中,当被驱动表不能有效的利用索引加快访问速度,MySQL一般会为其分配一块名叫join buffer的内存块来加快查询速度,也就是我们所讲的基于块的嵌套循环算法。

内连接(隐式)
EXPLAIN SELECT
    b.* 
FROM
    reconc_billing_proposal_request_cancle_info b,
    (
    SELECT
        SUBSTRING_INDEX ( GROUP_CONCAT( a.id ORDER BY a.created_at DESC ), ',', 1 ) id 
    FROM
        reconc_billing_proposal_request_cancle_info a 
    GROUP BY
        a.void_bp_line_id 
    ) temp 
WHERE
    b.id = temp.id;
explain执行结果:
查询结果分析:

查询时间很短,0.052s,通过explain分析,它也是会走部分primary key。

  1. 总结:

编写sql语句时尽量使用exists,少用in,使用not exists 代替not in ,join本身底层用的是简单嵌套查询算法,尽量能使用基于索引的嵌套查询算法减少查询被驱动表次数,mysql8.0后,默认开启了基于块的嵌套查询算法减少查询驱动表次数,查询执行分析器默认会先使用基于索引方式查询,如果没有索引,则会使用基于块方式。

;