带有左联接的MySQL查询优化

时间:2018-10-01 10:26:07

标签: mysql query-optimization

我有以下查询,该查询需要1.141秒才能执行。该查询具有一堆联接。有没有一种方法可以优化查询?任何帮助都将受到赞赏。

   
SELECT `cou`.`id` AS `country_id`,
                `a`.`id` AS `area_id`,
                `y`.`id` AS `year_id`,
                `su`.`id` AS `subject_id`,
                `co1`.`name` AS `course_name`,
                `ca1`.`id` AS `root_category_id`,
                `ca1`.`name` AS `root_category_name`,
                `ca4`.`id` AS `chapter_id`,
                `ca4`.`name` AS `chapter_name`,
                `ca4`.`no_of_assets` AS `no_of_assets`,
                `ca4`.`active_status` AS `status`,
                0 AS `READ_IT`,
                0 AS `WATCH_IT`,
                0 AS `PLAY_IT`,
                0 AS `PROVE_IT`,
                count(DISTINCT `pa`.`id`) AS `APROVE_IT`,
                if((count(`pa`.`id`) > 0),'True', 'False') AS `sections_with_content`,
                count(`pa`.`id`) AS `content_count`,
                `pa`.`status` AS `content_flag`
         FROM (((((((((((((((((`edu_db`.`category_relation_xref` `crx1`
                               JOIN `edu_db`.`category` `ca1` on((`crx1`.`parent_id` = `ca1`.`id`)))
                              LEFT JOIN `edu_db`.`course` `co1` on((`ca1`.`course_id` = `co1`.`id`)))
                             JOIN `edu_db`.`category_relation_xref` `crx2` on((`crx1`.`child_id` = `crx2`.`parent_id`)))
                            JOIN `edu_db`.`category` `ca2` on((`crx2`.`parent_id` = `ca2`.`id`)))
                           JOIN `edu_db`.`category` `ca3` on((`crx2`.`child_id` = `ca3`.`id`)))
                          JOIN `edu_db`.`category_relation_xref` `crx3` on((`crx2`.`child_id` = `crx3`.`parent_id`)))
                         JOIN `edu_db`.`category` `ca4` on((`crx3`.`child_id` = `ca4`.`id`)))
                        LEFT JOIN `edu_db`.`category_relation_xref` `crx4` on((`crx3`.`child_id` = `crx4`.`parent_id`)))
                       LEFT JOIN `edu_db`.`category` `ca5` on((`crx4`.`child_id` = `ca5`.`id`)))
                      JOIN `edu_db`.`course` `co2` on((`ca4`.`course_id` = `co2`.`id`)))
                     JOIN `edu_db`.`curriculum` `cu` on((`co2`.`curriculum_id` = `cu`.`id`)))
                    JOIN `edu_db`.`year` `y` on((`cu`.`year_id` = `y`.`id`)))
                   JOIN `edu_db`.`subject` `su` on((`su`.`id` = `cu`.`subject_id`)))
                  JOIN `edu_db`.`area` `a` on((`y`.`area_id` = `a`.`id`)))
                 JOIN `edu_db`.`country` `cou` on((`a`.`country_id` = `cou`.`id`)))
                LEFT JOIN `edu_db`.`qbnk_category_published_assessment_xref` `qcpa` on((`ca4`.`id` = `qcpa`.`category_id`)))
               LEFT JOIN `edu_db`.`qbnk_published_assessment` `pa` on((`qcpa`.`published_assessment_id` = `pa`.`id`)))
         WHERE ((`pa`.`status` <> 'non_active')
                AND (`qcpa`.`status` <> 'deleted'))
         GROUP BY `ca4`.`id`

这是explain命令的输出。在这里有一个使用文件排序的选择类型,这意味着查询不使用索引。有没有一种使用索引优化此查询的方法?

 +------+---------------+---------+--------------+----------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+----------------------------------+-----------+--------------------------------+--------+------------+-------------------------------------------------------------+
| "id" | "select_type" | "table" | "partitions" |  "type"  |                                                                                      "possible_keys"                                                                                      |              "key"               | "key_len" |             "ref"              | "rows" | "filtered" |                           "Extra"                           |
+------+---------------+---------+--------------+----------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+----------------------------------+-----------+--------------------------------+--------+------------+-------------------------------------------------------------+
| "1"  | "SIMPLE"      | "pa"    | \N           | "index"  | "PRIMARY,status"                                                                                                                                                                          | "status"                         | "2"       | \N                             | "7714" | "50.00"    | "Using where; Using index; Using temporary; Using filesort" |
| "1"  | "SIMPLE"      | "qcpa"  | \N           | "ref"    | "PRIMARY,FK_qbnk_cat_id_pub_ass_tbl_to_category_tbl_id,cat_pub_status"                                                                                                                    | "PRIMARY"                        | "4"       | "edu_db.pa.id"             | "6"    | "50.00"    | "Using where"                                               |
| "1"  | "SIMPLE"      | "ca4"   | \N           | "eq_ref" | "PRIMARY,FK_curriculum_item_id_category_tbl_to_id_curriculum_item_tbl,FK_curriculum_id_category_tbl_to_id_curriculum_tbl,name,Fk_category_tbl_course_id_to_course_tbl_id,Index 6,Index 7" | "PRIMARY"                        | "4"       | "edu_db.qcpa.category_id"  | "1"    | "100.00"   | "Using where"                                               |
| "1"  | "SIMPLE"      | "co2"   | \N           | "eq_ref" | "PRIMARY,FK_curriculum_id_to_id_curriculum_tbl"                                                                                                                                           | "PRIMARY"                        | "4"       | "edu_db.ca4.course_id"     | "1"    | "100.00"   | "Using where"                                               |
| "1"  | "SIMPLE"      | "cu"    | \N           | "eq_ref" | "PRIMARY,FK_subject_id_to_id_subject_tbl,FK_year_id_to_id_year_tble"                                                                                                                      | "PRIMARY"                        | "4"       | "edu_db.co2.curriculum_id" | "1"    | "100.00"   | "Using where"                                               |
| "1"  | "SIMPLE"      | "su"    | \N           | "eq_ref" | "PRIMARY"                                                                                                                                                                                 | "PRIMARY"                        | "4"       | "edu_db.cu.subject_id"     | "1"    | "100.00"   | "Using index"                                               |
| "1"  | "SIMPLE"      | "y"     | \N           | "eq_ref" | "PRIMARY,FK_year_tbl_area_id_to_id_area_tbl"                                                                                                                                              | "PRIMARY"                        | "4"       | "edu_db.cu.year_id"        | "1"    | "100.00"   | "Using where"                                               |
| "1"  | "SIMPLE"      | "a"     | \N           | "eq_ref" | "PRIMARY,FK_country_id_to_country_tbl"                                                                                                                                                    | "PRIMARY"                        | "4"       | "edu_db.y.area_id"         | "1"    | "100.00"   | \N                                                          |
| "1"  | "SIMPLE"      | "cou"   | \N           | "eq_ref" | "PRIMARY"                                                                                                                                                                                 | "PRIMARY"                        | "4"       | "edu_db.a.country_id"      | "1"    | "100.00"   | "Using index"                                               |
| "1"  | "SIMPLE"      | "crx3"  | \N           | "ref"    | "PRIMARY,FK_child_id_to_id_category_tbl"                                                                                                                                                  | "FK_child_id_to_id_category_tbl" | "4"       | "edu_db.qcpa.category_id"  | "1"    | "100.00"   | "Using index"                                               |
| "1"  | "SIMPLE"      | "ca3"   | \N           | "eq_ref" | "PRIMARY,Index 6,Index 7"                                                                                                                                                                 | "PRIMARY"                        | "4"       | "edu_db.crx3.parent_id"    | "1"    | "100.00"   | "Using index"                                               |
| "1"  | "SIMPLE"      | "crx2"  | \N           | "ref"    | "PRIMARY,FK_child_id_to_id_category_tbl"                                                                                                                                                  | "FK_child_id_to_id_category_tbl" | "4"       | "edu_db.crx3.parent_id"    | "1"    | "100.00"   | "Using index"                                               |
| "1"  | "SIMPLE"      | "ca2"   | \N           | "eq_ref" | "PRIMARY,Index 6,Index 7"                                                                                                                                                                 | "PRIMARY"                        | "4"       | "edu_db.crx2.parent_id"    | "1"    | "100.00"   | "Using index"                                               |
| "1"  | "SIMPLE"      | "crx1"  | \N           | "ref"    | "PRIMARY,FK_child_id_to_id_category_tbl"                                                                                                                                                  | "FK_child_id_to_id_category_tbl" | "4"       | "edu_db.crx2.parent_id"    | "1"    | "100.00"   | "Using index"                                               |
| "1"  | "SIMPLE"      | "ca1"   | \N           | "eq_ref" | "PRIMARY,Index 6,Index 7"                                                                                                                                                                 | "PRIMARY"                        | "4"       | "edu_db.crx1.parent_id"    | "1"    | "100.00"   | \N                                                          |
| "1"  | "SIMPLE"      | "co1"   | \N           | "eq_ref" | "PRIMARY"                                                                                                                                                                                 | "PRIMARY"                        | "4"       | "edu_db.ca1.course_id"     | "1"    | "100.00"   | \N                                                          |
| "1"  | "SIMPLE"      | "crx4"  | \N           | "ref"    | "PRIMARY"                                                                                                                                                                                 | "PRIMARY"                        | "4"       | "edu_db.qcpa.category_id"  | "4"    | "100.00"   | "Using index"                                               |
| "1"  | "SIMPLE"      | "ca5"   | \N           | "eq_ref" | "PRIMARY,Index 6,Index 7"                                                                                                                                                                 | "PRIMARY"                        | "4"       | "edu_db.crx4.child_id"     | "1"    | "100.00"   | "Using index"                                               |
+------+---------------+---------+--------------+----------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+----------------------------------+-----------+--------------------------------+--------+------------+-------------------------------------------------------------+

以下是 category_relation_xref qbnk_category_published_assesssment_xref 表的创建代码

 CREATE TABLE `category_relation_xref` (
    `parent_id` INT(11) NOT NULL,
    `child_id` INT(11) NOT NULL,
    `template_id` INT(11) NOT NULL DEFAULT '1',
    `possition_id` INT(11) NOT NULL DEFAULT '1',
    `comment` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf16_unicode_ci',
    `display_order` INT(11) NOT NULL DEFAULT '0',
    `created_at` TIMESTAMP NULL DEFAULT NULL,
    `updated_at` TIMESTAMP NULL DEFAULT NULL,
    `created_by` INT(11) NULL DEFAULT NULL,
    `updated_by` INT(11) NULL DEFAULT NULL,
    PRIMARY KEY (`parent_id`, `child_id`),
    INDEX `FK_child_id_to_id_category_tbl` (`child_id`),
    INDEX `FK_cat_rel_xref_tbl_template_id_to_content_template_tbl` (`template_id`),
    INDEX `FK_cat_rel_xref_tbl_possition_id_to_content_template_tbl_id` (`possition_id`),
    CONSTRAINT `FK_cat_rel_xref_tbl_possition_id_to_content_template_tbl_id` FOREIGN KEY (`possition_id`) REFERENCES `content_possition` (`id`),
    CONSTRAINT `FK_cat_rel_xref_tbl_template_id_to_content_template_tbl` FOREIGN KEY (`template_id`) REFERENCES `content_template` (`id`),
    CONSTRAINT `category_relation_xref_ibfk_1` FOREIGN KEY (`parent_id`) REFERENCES `category` (`id`),
    CONSTRAINT `category_relation_xref_ibfk_2` FOREIGN KEY (`child_id`) REFERENCES `category` (`id`)
)
COMMENT='store parent child relations'
COLLATE='utf16_unicode_ci'
ENGINE=InnoDB
;

CREATE TABLE `qbnk_category_published_assessment_xref` (
    `published_assessment_id` INT(11) NOT NULL,
    `category_id` INT(11) NOT NULL,
    `status` ENUM('active','deleted') NOT NULL DEFAULT 'active' COLLATE 'utf16_unicode_ci',
    `comment` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf16_unicode_ci',
    `created_at` TIMESTAMP NULL DEFAULT NULL,
    `updated_at` TIMESTAMP NULL DEFAULT NULL,
    `created_by` INT(11) NULL DEFAULT '0',
    `updated_by` INT(11) NULL DEFAULT '0',
    PRIMARY KEY (`published_assessment_id`, `category_id`),
    INDEX `FK_qbnk_cat_id_pub_ass_tbl_to_category_tbl_id` (`category_id`),
    INDEX `cat_pub_status` (`status`),
    CONSTRAINT `FK_qbnk_cat_id_pub_ass_tbl_to_category_tbl_id` FOREIGN KEY (`category_id`) REFERENCES `category` (`id`) ON DELETE CASCADE,
    CONSTRAINT `FK_qbnk_cat_pub_ass_id_to_pub_ass_tbl_id` FOREIGN KEY (`published_assessment_id`) REFERENCES `qbnk_published_assessment` (`id`) ON DELETE CASCADE
)
COMMENT='Store category published assessments mappings'
COLLATE='utf16_unicode_ci'
ENGINE=InnoDB
;

3 个答案:

答案 0 :(得分:2)

清理查询,除去不必要的井字号和括号后,我相信下面的内容很容易理解,可以直接查看表之间的关系。通过leftAlias.leftColumn = rightAlias.rightColumn的联接,您做得很好。

现在,我可以更好地了解情况了,请考虑一下您的桌子。它们似乎大部分是查找表,您在其中具有ID和要返回的描述性列。我将创建覆盖这些表上的索引的内容,以便可以直接从索引处理联接解析,而不用转到原始数据页面。

在时间上的另一个考虑因素是添加MySQL关键字“ STRAIGHT_JOIN”,该关键字告诉引擎按照我列出的顺序查询表。不要为我思考。您的每个表都从最高级别开始,并在下游进行所有查找。您还可以通过删除STRAIGHT_JOIN子句来比较时间。在我以前使用YEARS的系统上,经过12个多小时的处理后,它挂起了具有约1500万条记录和20多个查找表的主表,并减少为一个完整的表。查询运行了一个多小时。

原始查询已清理

SELECT STRAIGHT_JOIN
      cou.id AS country_id,
      a.id AS area_id,
      y.id AS year_id,
      su.id AS subject_id,
      co1.`name` AS course_name,
      ca1.id AS root_category_id,
      ca1.`name` AS root_category_name,
      ca4.id AS chapter_id,
      ca4.`name` AS chapter_name,
      ca4.no_of_assets AS no_of_assets,
      ca4.active_status AS `status`,
      0 AS READ_IT,
      0 AS WATCH_IT,
      0 AS PLAY_IT,
      0 AS PROVE_IT,
      count(DISTINCT pa.id) AS APROVE_IT,
      if((count(pa.id) > 0),'True', 'False') AS sections_with_content,
      count(pa.id) AS content_count,
      pa.`status` AS content_flag
   FROM 
      edu_db.category_relation_xref crx1
         JOIN edu_db.category ca1
            ON crx1.parent_id = ca1.id
            LEFT JOIN edu_db.course co1
               ON ca1.course_id = co1.id

         JOIN edu_db.category_relation_xref crx2 
            ON crx1.child_id = crx2.parent_id

            JOIN edu_db.category ca2
               ON crx2.parent_id = ca2.id

            JOIN edu_db.category ca3
               ON crx2.child_id = ca3.id

            JOIN edu_db.category_relation_xref crx3 
               ON crx2.child_id = crx3.parent_id
               JOIN edu_db.category ca4 
                  ON crx3.child_id = ca4.id

                  JOIN edu_db.course co2
                     ON ca4.course_id = co2.id
                     JOIN edu_db.curriculum cu
                        ON co2.curriculum_id = cu.id
                        JOIN edu_db.`year` y 
                           ON cu.year_id = y.id
                           JOIN edu_db.area a
                              ON y.area_id = a.id
                              JOIN edu_db.country cou 
                                 ON a.country_id = cou.id
                        JOIN edu_db.subject su
                           ON cu.subject_id = su.id

                  LEFT JOIN edu_db.qbnk_category_published_assessment_xref qcpa
                     ON ca4.id = qcpa.category_id
                     LEFT JOIN edu_db.qbnk_published_assessment pa
                        ON qcpa.published_assessment_id = pa.id

               LEFT JOIN edu_db.category_relation_xref crx4
                  ON crx3.child_id = crx4.parent_id
                  LEFT JOIN edu_db.category ca5 
                     ON crx4.child_id = ca5.id
   WHERE 
          pa.`status` <> 'non_active'
      AND qcpa.`status` <> 'deleted'
   GROUP BY 
      ca4.id

另一个项目...您有一些表已左连接,甚至没有在查询中使用,并且可以完全删除。明确显示为“ LEFT JOIN edu_db.category ca5”。您没有从CA5别名中提取任何值,左连接表示您根本不在乎它。对于“ LEFT JOIN edu_db.category_relation_xref crx4”,同样如此

SELECT STRAIGHT_JOIN
      a.country_id,
      a.id AS area_id,
      y.id AS year_id,
      cu.subject_id,
      co1.`name` AS course_name,
      ca1.id AS root_category_id,
      ca1.`name` AS root_category_name,
      ca4.id AS chapter_id,
      ca4.`name` AS chapter_name,
      ca4.no_of_assets AS no_of_assets,
      ca4.active_status AS `status`,
      0 AS READ_IT,
      0 AS WATCH_IT,
      0 AS PLAY_IT,
      0 AS PROVE_IT,
      count(DISTINCT pa.id) AS APROVE_IT,
      if((count(pa.id) > 0),'True', 'False') AS sections_with_content,
      count(pa.id) AS content_count,
      pa.`status` AS content_flag
   FROM 
      edu_db.category_relation_xref crx1
         JOIN edu_db.category ca1
            ON crx1.parent_id = ca1.id
            LEFT JOIN edu_db.course co1
               ON ca1.course_id = co1.id

         JOIN edu_db.category_relation_xref crx2 
            ON crx1.child_id = crx2.parent_id
            JOIN edu_db.category_relation_xref crx3 
               ON crx2.child_id = crx3.parent_id
               JOIN edu_db.category ca4 
                  ON crx3.child_id = ca4.id
                  JOIN edu_db.course co2
                     ON ca4.course_id = co2.id
                     JOIN edu_db.curriculum cu
                        ON co2.curriculum_id = cu.id
                        JOIN edu_db.`year` y 
                           ON cu.year_id = y.id
                           JOIN edu_db.area a
                              ON y.area_id = a.id

                  JOIN edu_db.qbnk_category_published_assessment_xref qcpa
                     ON ca4.id = qcpa.category_id
                     AND qcpa.`status` <> 'deleted'

                     JOIN edu_db.qbnk_published_assessment pa
                        ON qcpa.published_assessment_id = pa.id
                        AND pa.`status` <> 'non_active'
   GROUP BY 
      ca4.id

与“ pa”和“ qcpa”相关联的WHERE子句会抵消LEFT JOIN部分,因为where将其变为WHERE子句。因此,我删除了“ LEFT”组件,并将where子句部分直接移至该连接组件。

您正在拉“主题”表(别名su),但仅抓住su.id。由于您具有来自“ cu”别名的主题ID,因此您可以仅使用“ cu.subject_id”并从查询中删除另一个表-除非您计划从主题表中获取其他描述。您所在的国家/地区,年份加入情况也可能相同。如果您已经拥有先前表格中的ID,请使用该ID并删除不需要的内容...

不要将“ ca2”或“ ca3”别名用于任何潜在的额外详细信息,描述,而不必使用它。

因此,我对每个表的索引建议将包括以下内容。这些将是更多的COVERING索引。这些不应该是同一表上的单个索引,例如ID上的Tbl1索引,Description上的Tbl1索引,而Tbl1索引应作为单个索引打开(id,description)。

table                                   index
qbnk_published_assessment               ( id, `status` ) 
qbnk_category_published_assessment_xref ( category_id, `status`, published_assessment_id )
area                                    ( id )
`year`                                  ( id, area_id ) 
curriculum                              ( id, year_id )
course                                  ( id, curriculum_id )
category                                ( id, course_id )
category_relation_xref                  ( parent_id, child_id )

答案 1 :(得分:0)

由于EXPLAIN以paocpa表开头,因此我们可以看到优化器在执行其他任何操作之前先尝试过滤结果。这很聪明,但是另一个聪明的方法是先执行GROUP BY,然后过滤结果。

新索引

考虑到这一点,让我们尝试帮助优化器做到这一点,看看是否有任何改进:

ALTER TABLE qbnk_category_published_assessment_xref
ADD INDEX so52589130_qcpa (`status`,`category_id`,`published_assessment_id`)

说明

上面的索引允许从category表的GROUP BY开始,然后使用WHERE列上的status子句与category_id进行匹配该索引最左边的两列。这样就可以立即访问published_assessment_id列,而无需进行辅助查找。

首先不尝试执行此操作的原因是,qbnk_category_published_assessment_xref上的索引不允许在没有辅助查找的情况下进行操作。

cat_pub_status允许使用status,但随后在published_assessment_id之前使用category_id,这不允许它使用category_id来快速找到所需的记录。

FK_qbnk_cat_id_pub_ass_tbl_to_category_tbl_id允许首先使用category_id,但随后仍必须返回到聚集索引以获取status

我们的新索引仅允许一次访问该表,这将有助于节省时间。

可能额外节省

与两个WHERE值为<>相比,这两个=子句为status的事实可能会稍微降低性能。如果每个=列只有两个状态值,则我将其交换为使用EXPLAIN。但是,如果有两个以上的值,那并不是太重要。

说明

因此,尝试上述索引。运行两次,丢弃第一个计时结果,因为缓冲区高速缓存将在该调用上加载。第二次答复,并且EXPLAIN计划是否更改。如果EXPLAIN计划发生了变化,但时间安排还不够快,请将新的construir_cruz() 计划添加到您的问题中,我会再看看。 (那时我可能还需要更多的表定义,但是我们将拭目以待,首先看看它是如何实现的。)

答案 2 :(得分:0)

我称此为“过度标准化”。

country不需要额外的表-所有国家/地区的CHAR(2) CHARACTER SET ascii值都非常好。请注意,INT是4个字节,因此此更改节省了空间!

这是数据类型YEAR;用它。如果您有完整的日期或日期时间,请使用DATEDATETIME,然后从YEAR中选择SELECTYEAR小于INT

了解ENUM作为可能值较小的列的可能数据类型。