【问题标题】:Suggestions for speeding up LEFT JOIN in MySQL在 MySQL 中加快 LEFT JOIN 的建议
【发布时间】:2017-12-09 10:24:03
【问题描述】:

我有一个名为 usr_data 的表格,其中包含大约 40,000 条记录,我想通过连接中的选择与用户的组织数据(大约 1000 条记录)进行连接,显然这真的很慢(最多 150 秒)。

我想知道是否有任何方法可以加快速度?不幸的是,这是连接中子选择的最快查询。

我的查询

    SELECT  usr_data.usr_id,usr_data.login,orgus.title FROM usr_data
    LEFT JOIN (
        SELECT object_reference.ref_id,rbac_ua.usr_id,object_data.obj_id,object_data.title
        FROM rbac_ua
        JOIN rbac_fa ON rbac_fa.rol_id = rbac_ua.rol_id
        JOIN object_reference ON rbac_fa.parent = object_reference.ref_id
        JOIN object_data ON object_data.obj_id = object_reference.obj_id
        JOIN object_data role ON role.obj_id = rbac_ua.rol_id
        WHERE object_data.type = 'orgu') as orgus on orgus.usr_id = usr_data.usr_id
    WHERE usr_data.usr_id > 0 AND usr_data.login <> "anonymous" 

查询描述

+------+-------------+------------------+--------+-----------------------+---------+---------+--------------------------------+------+--------------------------+
| id   | select_type | table            | type   | possible_keys         | key     | key_len | ref                            | rows | Extra                    |
+------+-------------+------------------+--------+-----------------------+---------+---------+--------------------------------+------+--------------------------+
|    1 | SIMPLE      | usr_data         | range  | PRIMARY,i1_idx        | PRIMARY | 4       | NULL                           | 8148 | Using where              |
|    1 | SIMPLE      | rbac_ua          | ref    | PRIMARY,i1_idx,i2_idx | PRIMARY | 4       | ildCPC.usr_data.usr_id         |    2 | Using where; Using index |
|    1 | SIMPLE      | rbac_fa          | ref    | PRIMARY,i1_idx        | PRIMARY | 4       | ildCPC.rbac_ua.rol_id          |    1 | Using where; Using index |
|    1 | SIMPLE      | role             | eq_ref | PRIMARY               | PRIMARY | 4       | ildCPC.rbac_ua.rol_id          |    1 | Using index              |
|    1 | SIMPLE      | object_reference | eq_ref | PRIMARY,i1_idx        | PRIMARY | 4       | ildCPC.rbac_fa.parent          |    1 | Using where              |
|    1 | SIMPLE      | object_data      | eq_ref | PRIMARY,i1_idx        | PRIMARY | 4       | ildCPC.object_reference.obj_id |    1 | Using where              |
+------+-------------+------------------+--------+-----------------------+---------+---------+--------------------------------+------+--------------------------+

usr_data 表

+----------------------+---------------+------+-----+---------+-------+
| Field                | Type          | Null | Key | Default | Extra |
+----------------------+---------------+------+-----+---------+-------+
| usr_id               | int(11)       | NO   | PRI | 0       |       |
| login                | varchar(80)   | YES  | MUL | NULL    |       |
| passwd               | varchar(80)   | YES  |     | NULL    |       |
| firstname            | varchar(32)   | YES  |     | NULL    |       |
| lastname             | varchar(32)   | YES  |     | NULL    |       |
| title                | varchar(32)   | YES  |     | NULL    |       |
| gender               | char(1)       | YES  |     | m       |       |
| email                | varchar(80)   | YES  |     | NULL    |       |

usr_data 索引

+----------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table    | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+----------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| usr_data |          0 | PRIMARY  |            1 | usr_id      | A         |       29354 |     NULL | NULL   |      | BTREE      |         |               |
| usr_data |          1 | i1_idx   |            1 | login       | A         |       29354 |     NULL | NULL   | YES  | BTREE      |         |               |
| usr_data |          1 | i1_idx   |            2 | passwd      | A         |       29354 |     NULL | NULL   | YES  | BTREE      |         |               |
| usr_data |          1 | i2_idx   |            1 | ext_account | A         |           2 |     NULL | NULL   | YES  | BTREE      |         |               |
| usr_data |          1 | i2_idx   |            2 | auth_mode   | A         |           4 |     NULL | NULL   | YES  | BTREE      |         |               |
+----------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+

我尝试在 LEFT JOIN 中为该 SELECT 创建一个临时表,但它并没有真正加快速度,查询目前最多需要 150 秒,使用右连接可以将其降低到大约1秒。 (这是较小的桌子)。

select * from object_data where type = 'orgu' returns 1058 rows.

显示创建表 rbac_ua

+---------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table   | Create Table                                                                                                                                                                                                                                                                                                                                                |
+---------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| rbac_ua | CREATE TABLE `rbac_ua` (
  `usr_id` int(11) NOT NULL DEFAULT '0',
  `rol_id` int(11) NOT NULL DEFAULT '0',
  PRIMARY KEY (`usr_id`,`rol_id`),
  KEY `i1_idx` (`usr_id`),
  KEY `i2_idx` (`rol_id`),
  KEY `rol_id` (`rol_id`,`usr_id`),
  KEY `rol_usr` (`rol_id`,`usr_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci ROW_FORMAT=DYNAMIC |
+---------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+

显示创建表 rbac_fa

+---------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table   | Create Table                                                                                                                                                                                                                                                                                                                                                                                                    |
+---------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| rbac_fa | CREATE TABLE `rbac_fa` (
  `rol_id` int(11) NOT NULL DEFAULT '0',
  `parent` int(11) NOT NULL DEFAULT '0',
  `assign` char(1) COLLATE utf8_unicode_ci DEFAULT NULL,
  `protected` char(1) COLLATE utf8_unicode_ci DEFAULT 'n',
  PRIMARY KEY (`rol_id`,`parent`),
  KEY `i1_idx` (`parent`),
  KEY `parent` (`parent`,`rol_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci ROW_FORMAT=DYNAMIC |
+---------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+

显示创建表对象数据

+-------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table       | Create Table                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
+-------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| object_data | CREATE TABLE `object_data` (
  `obj_id` int(11) NOT NULL DEFAULT '0',
  `type` char(4) COLLATE utf8_unicode_ci DEFAULT 'none',
  `title` char(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `description` char(128) COLLATE utf8_unicode_ci DEFAULT NULL,
  `owner` int(11) NOT NULL DEFAULT '0',
  `create_date` datetime DEFAULT NULL,
  `last_update` datetime DEFAULT NULL,
  `import_id` char(50) COLLATE utf8_unicode_ci DEFAULT NULL,
  PRIMARY KEY (`obj_id`),
  KEY `i1_idx` (`type`),
  KEY `i2_idx` (`title`),
  KEY `i4_idx` (`import_id`),
  FULLTEXT KEY `i3_idx` (`title`,`description`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci ROW_FORMAT=DYNAMIC |
+-------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

SHOW CREATE TABLE object_reference

+------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table            | Create Table                                                                                                                                                                                                                                                                                                                                               |
+------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| object_reference | CREATE TABLE `object_reference` (
  `ref_id` int(11) NOT NULL DEFAULT '0',
  `obj_id` int(11) NOT NULL DEFAULT '0',
  `deleted` datetime DEFAULT NULL,
  PRIMARY KEY (`ref_id`),
  KEY `i1_idx` (`obj_id`),
  KEY `i2_idx` (`deleted`),
  KEY `obj_id` (`obj_id`,`ref_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci ROW_FORMAT=DYNAMIC |
+------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+

如果我缺少任何有用的信息,请告诉我。

提前致谢。

【问题讨论】:

  • 在没有子选择的情况下重写它,这应该会大大提高性能。
  • 你提到了right join。你的意思是直内join
  • 从技术上讲,没有用户就没有“组织”。所以正确和直接最终会产生相同的结果。不过,我很感激您的帮助,我知道仅使用架构的一小部分摘录来查看它有点困难。
  • LEFT 是红鲱鱼;它被优化器忽略了。

标签: mysql join query-performance


【解决方案1】:

通过删除子选择并使用连接,您应该能够提高性能。首先处理子选择,生成一个临时表,这对性能有很大影响。

SELECT  
    usr_data.usr_id,
    usr_data.login,
    object_data.title,
    object_reference.ref_id,
    rbac_ua.usr_id,
    object_data.obj_id,
    object_data.title
FROM usr_data
LEFT JOIN rbac_ua
ON rbac_ua.usr_id = usr_data.usr_id
JOIN rbac_fa ON rbac_fa.rol_id = rbac_ua.rol_id
JOIN object_reference ON rbac_fa.parent = object_reference.ref_id
JOIN object_data ON object_data.obj_id = object_reference.obj_id
JOIN object_data role ON role.obj_id = rbac_ua.rol_id
WHERE 
    object_data.type = 'orgu' AND 
    usr_data.usr_id > 0 AND 
    usr_data.login <> "anonymous"

【讨论】:

  • 这比右连接工作得快很多,但实际上并不允许我像 object_data 那样“左连接”(或者即使他们没有“组织”也可以获取用户)。 type = 'orgu' 在 where 中。如果我将其移至连接(LEFT JOIN object_data ON object_data.obj_id = object_reference.obj_id AND object_data.type = 'orgu'),它的速度与以前大致相同。
  • object_data.type 上有索引吗?
【解决方案2】:

通过使用LEFT JOIN,您需要一个大型结果集,其中包含与您的WHERE usr_data whatever 子句匹配的每个项目的行。即使查询计划器做得很好,这也需要时间来推送到您的客户端软件。

usr_data.usr_id &gt; 0 似乎是多余的,因为您的 usr_id 列似乎是一个自动增量列。所有值都大于零吗?

子选择绝对是性能消耗者。

先生。我想,Thrasher 已经很接近了。问题是在WHERE 子句中提到了LEFT JOINed 表的列:它将LEFT JOIN 转换为直JOIN

试试这个,看看你是否得到了合适的结果。我不明白你的架构,所以这里有一些猜测。

     SELECT usr_data.usr_id, usr_data.login, object_data.title 
       FROM usr_data
  LEFT JOIN rbac_ua ON rbac_ua.usr_id = usr_data.usr_id
  LEFT JOIN rbac_fa ON rbac_fa.rol_id = rbac_ua.rol_id
  LEFT JOIN object_reference ON rbac_fa.parent = object_reference.ref_id
  LEFT JOIN object_data   ON object_data.obj_id = object_reference.obj_id
                         AND object_data.type = 'orgu'
  LEFT JOIN object_data role ON role.obj_id = rbac_ua.rol_id
      WHERE usr_data.login <> 'anonymous'

注意object_data.type = 'orgu' 是如何出现在ON 子句中的。 (是的,ON 子句可以包含与 WHERE 子句相同的内容!)这使它不会将 LEFT JOIN 变成直接的 JOIN

我不确定这条线的意义。

  LEFT JOIN object_data role ON role.obj_id = rbac_ua.rol_id

该表似乎对您的结果集没有贡献。

【讨论】:

  • 我将把它标记为正确,这是我最终选择的路线。它并没有非常 比子选择快得多,但它很明显。我认为左连接会很昂贵,因为我想我只是没有意识到有多少。谢谢!
【解决方案3】:

EXPLAIN,我看到优化器认为“LEFT”没有影响。所以它删除了它。然后它决定“派生表”可以变成JOIN。你本可以做到的。

但性能上的真正问题是过滤的重要部分在type = 'orgu' 上,但它隐藏在“最后一个”表中。

(以后我猜测是因为 每个 表都缺少SHOW CREATE TABLE。而且DESCRIBE 不像描述性广告SHOW CREATE。)

让我们看看这个:

SELECT  ud.usr_id, ud.login, od.title
    FROM  object_data AS od
    JOIN  object_reference AS r  ON od.obj_id = r.obj_id
    JOIN  rbac_fa AS rfa  ON rfa.parent = r.ref_id
    JOIN  rbac_ua AS rua  ON rfa.rol_id = rua.rol_id
    JOIN  usr_data AS ud  ON ud.usr_id = rua.usr_id
     -- unnec?:  JOIN  object_data role  ON role.obj_id = rua.rol_id
    WHERE  od.type = 'orgu'
      AND  ud.usr_id > 0
      AND  ud.login <> "anonymous" 

我认为它在添加以下索引后会更有效。如果type = 'orgu' 具有足够的选择性,则很可能会出现这种情况。

请注意,role 除了验证给定obj_id 的角色确实存在之外,不会向查询添加任何内容。

需要索引以便它可以开始 type = 'orgu':

object_data: INDEX(type)
object_reference:  INDEX(obj_id, ref_id)  -- (covering, too)
rbac_fa:  INDEX(parent, rol_id)  -- (covering, too)
rbac_ua:  INDEX(rol_id, usr_id)  -- (covering, too)

如果这没有帮助,请提供结果的EXPLAIN SELECT ...。并告诉我们有多少行 object_data 有 type=orgu。

【讨论】:

  • 嗨瑞克,谢谢你的回复,我真的很感激。您的查询运行速度非常快(0.10 秒),我也添加了适当的索引,我注意到您说优化器说 LEFT JOIN 对查询没有影响,但是在原始查询中使用 LEFT JOIN 我确实收到了更多行(不属于“组织”的用户,大约 800 行。)比我在您的查询中使用直接连接时所做的那样,不过花了 3 分钟 2.58 秒。如果忽略左连接,这可能吗?我已经用更多相关信息更新了我的问题,再次感谢您的帮助!
  • 优化器将忽略LEFT 如果它可以确定结果集将是相同的。 STRAIGHT_JOINJOIN相同。
猜你喜欢
  • 2018-07-10
  • 1970-01-01
  • 2014-05-06
  • 2011-04-09
  • 2010-12-21
  • 2011-07-06
  • 2013-02-07
  • 2012-04-03
  • 1970-01-01
相关资源
最近更新 更多