【问题标题】:MYSQL - Indexing and Optimize Select QueryMYSQL - 索引和优化选择查询
【发布时间】:2016-06-14 20:14:31
【问题描述】:

我有一个超过 500 万行的表。当我执行选择查询时,大约需要 20 秒。

SELECT CompUID,Weburl FROM `CompanyTable` WHERE (Alias1='match1' AND Alias2='match2' )OR Alias3='match3' OR Alias4='match4'

这是表结构:

CREATE TABLE `CompanyMaster` (
  `CompUID` int(11) NOT NULL AUTO_INCREMENT,
  `Weburl` varchar(150) DEFAULT NULL,
  `CompanyName` varchar(200) DEFAULT NULL,
  `Alias1` varchar(150) DEFAULT NULL,
  `Alias2` varchar(150) DEFAULT NULL,
  `Alias3` varchar(150) DEFAULT NULL,
  `Alias4` varchar(150) DEFAULT NULL,  
  `Created` datetime DEFAULT NULL,
  `LastModified` datetime DEFAULT NULL,  
  PRIMARY KEY (`CompUID`),
  KEY `Alias` (`Alias1`,`Alias2`,`Alias3`,`Alias4`)
) ENGINE=InnoDB AUTO_INCREMENT=5457968 DEFAULT CHARSET=latin1

这是该查询的解释:

--------+------------------------------------------------------------------------------------------------------+
| id | select_type | table        | type  | possible_keys | key    | key_len  | ref  | rows    | Extra         |
+----+-------------+----------+-------+---------------+------+---------+------+---------+----------------------+
|  1 | SIMPLE      | CompanyTable | ALL   |     Alias     | NULL   | NULL     | NULL | 5255929 |  Using where  |
+----+-------------+----------+-------+---------------+------+---------+------+---------+----------------------+

我使用了复合索引Alias (Alias1,Alias2,Alias3,Alias4)。 但我相信这不是最好的。请为此选择查询查找建议我正确的索引。

【问题讨论】:

  • OR Alias3='match3' OR Alias4='match4' 是强制进行全表扫描的原因。该子句基本上没有索引。为了优化此查询,您需要在 Alias3 和 Alias4 上添加索引。
  • 您可以通过提供如下提示来检查查询的性能:SELECT CompUID,Weburl FROM CompanyTable` use index(Alias) ...`。这会影响速度吗?
  • 以下查询的共享计数:“select count() from CompanyTable where Alias1='match1' AND Alias2='match2';”和 "select count() from CompanyTable where Alias1='match3'" 和 "select count(*) from CompanyTable where Alias1='match4"。

标签: mysql select indexing


【解决方案1】:

要让查询引擎使用复合索引中的列,必须首先满足左侧的列。也就是说,列必须用作限制,以减少从左到右读取的候选行。

OR alias3(或 alias4)子句违反了此规则,因为它说“我不在乎左侧部分(alias1 或 alias2(或 alias3))是什么,因为我不依赖它们”。

然后需要进行全表扫描以查看是否有 any alias3(或 alias4)值满足条件。

在这种情况下可能有用的指标是:

  • INDEX(alias1, alias2): alias1 AND alias2 涵盖此复合索引
  • 索引(别名 3)
  • 索引(别名 4)

and plan selection 的实际统计数据需要进一步调查 - 但至少现在查询规划器可以使用。


话虽如此 - 我不确定“别名”的作用是什么 - 规范化表格可能是有意义的。以下内容确实略微改变了语义,因为它删除了“别名位置”(可以重新添加)并且应该验证语义正确性。

CREATE TABLE `CompanyMaster` (
  `CompUID` int(11) NOT NULL AUTO_INCREMENT
 ,`CompanyName` varchar(200) DEFAULT NULL
 ,PRIMARY KEY (`CompUID`)
)

-- (This establishes a unique alias-per-company, which may be incorrect.)
CREATE TABLE `CompaniesAliases` (
  `CompUID` int(11) NOT NULL
 ,`Alias` varchar(150) NOT NULL
  -- Both CompUID and Alias appear in 'first' positions:
  --   CompUID for Join, Alias for filter
 ,PRIMARY KEY (`CompUID`, `Alias`)
 ,KEY (`Alias`)
 -- Alternative, which may change plan selection by eliminating options:
 -- ,PRIMARY KEY (`Alias`, `CompUID`) -- and no single KEY/index on Alias or CompUID
 ,FOREIGN KEY(CompUID) REFERENCES CompanyMaster(CompUID)
)

然后可以查询它与原始近似,不同之处在于它不关心哪个“别名”匹配哪个值:

-- AND constructed by joins (could also use GROUP BY .. HAVING COUNT)
SELECT c.CompUID FROM `CompanyTable` c
JOIN `CompaniesAliases` ac1
ON ac1.CompUID = c.CompUID AND Alias = 'match1'
JOIN `CompaniesAliases` ac2
ON ac2.CompUID = c.CompUID AND Alias = 'match2'

-- OR constructed by union(s)
UNION
SELECT c.CompUID FROM `CompanyTable` c
JOIN `CompaniesAliases` ac1
ON ac1.CompUID = c.CompUID AND (Alias = 'match3' OR Alias = 'match4')

我希望这样的查询能够在 SQL Server - YMMV 和 MySQL 中高效实现。

【讨论】:

    【解决方案2】:

    我会建议以下解决方案,创建一个带有 complex_alias_field 的表。它增加了一点你的数据,你的数据现在是多余的,但我认为这是一个简单直接的解决方案。

    1.创建表格

    CREATE TABLE `CompanyMaster` (
    `CompUID` int(11) NOT NULL AUTO_INCREMENT,
      `Weburl` varchar(150) DEFAULT NULL,
      `CompanyName` varchar(200) DEFAULT NULL,
      `Alias1` varchar(150) DEFAULT NULL,
      `Alias2` varchar(150) DEFAULT NULL,
      `Alias3` varchar(150) DEFAULT NULL,
      `Alias4` varchar(150) DEFAULT NULL,
      `Created` datetime DEFAULT NULL,
      `LastModified` datetime DEFAULT NULL,
      `ComplexAliasQuery` BOOLEAN DEFAULT FALSE,
      PRIMARY KEY (`CompUID`),
      KEY `Alias` (`Alias1`,`Alias2`,`Alias3`,`Alias4`),
      KEY `AliasQuery` (`ComplexAliasQuery`)
    ) ENGINE=InnoDB AUTO_INCREMENT=5457968 DEFAULT CHARSET=latin1;
    

    2。填写您的新字段 ComplexAliasQuery

    UPDATE CompanyMaster set ComplexAliasQuery = TRUE WHERE (Alias1='match1' AND Alias2='match2' )OR Alias3='match3' OR Alias4='match4';
    

    3.用于更新字段 Alias1、Alias2、Alias3、Alias4 之一

    对于更新,也只需填写 ComplexAliasQuery。您可以使用触发器 http://dev.mysql.com/doc/refman/5.7/en/trigger-syntax.html 或在您的代码中执行此操作,如果您不能使用触发器,因为您正在运行集群。

    4.您的简单查询已结束

    SELECT CompUID,Weburl FROM `CompanyMaster` WHERE ComplexAliasQuery IS TRUE;
    

    点击索引

    +----+-------------+---------------+------+---------------+------+---------+------+------+-------------+
    | id | select_type | table         | type | possible_keys | key  | key_len | ref  | rows | Extra       |
    +----+-------------+---------------+------+---------------+------+---------+------+------+-------------+
    |  1 | SIMPLE      | CompanyMaster | ALL  | NULL          | NULL | NULL    | NULL |    1 | Using where |
    +----+-------------+---------------+------+---------------+------+---------+------+------+-------------+
    

    另一种解决方案

    如果您不喜欢表 CompanyMaster 中的字段,您可以将其外包到一个新表中并将其命名为 IndexAliasCompanyMaster,然后加入此表。 p>

    【讨论】:

      【解决方案3】:

      以上都不是。重新设计架构。

      如果这 4 个别名只是公司的同义词,请不要在表中显示它们的数组,将它们移动到另一个表中。 (user2864740 到了一半;我是说一直走。)

      CREATE TABLE `CompanyMaster` (
        `CompUID` int(11) NOT NULL AUTO_INCREMENT,
        `Weburl` varchar(150) DEFAULT NULL,
        `CompanyName` varchar(200) DEFAULT NULL,
        `Created` datetime DEFAULT NULL,
        `LastModified` datetime DEFAULT NULL,  
        PRIMARY KEY (`CompUID`),
      ) ENGINE=InnoDB DEFAULT CHARSET=latin1
      
      CREATE TABLE `CompaniesAliases` (
        `CompUID` int(11) NOT NULL,
        `Alias` varchar(150) NOT NULL,
        PRIMARY KEY (Alias)  -- Assuming no two companies can have the same Alias
        KEY (CompUID)
      ) ENGINE=InnoDB;
      

      (您确实应该将所有表都转换为 InnoDB。)

      现在,您的原始查询变为

      SELECT  CompUID, Weburl
          FROM  `CompanyTable`
          JOIN  CompaniesAliases  USING(CompUID)
          WHERE  Alias IN ('match1', 'match2', 'match3', 'match4');
      

      它会运行得更快。

      如果您需要显示公司名称及其别名,请考虑

      SELECT CompanyName,
             GROUP_CONCAT(Alias) AS 'Also known as'
          FROM  `CompanyTable`
          JOIN  CompaniesAliases  USING(CompUID)
          WHERE ...
          GROUP BY CompUID;
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-04-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多