普通列和全文列的 MySQL 索引

Posted

技术标签:

【中文标题】普通列和全文列的 MySQL 索引【英文标题】:MySQL index for normal column and full text column 【发布时间】:2017-04-30 11:59:06 【问题描述】:

我正在尝试加快对以下内容的查询:

我的表有大约 400 万条记录。

EXPLAIN SELECT  * FROM chrecords WHERE  company_number = 'test'  OR MATCH (company_name,registered_office_address_address_line_1,registered_office_address_address_line_2) AGAINST('test') LIMIT 0, 10;
+------+-------------+-----------+------+------------------+------+---------+------+---------+-------------+
| id   | select_type | table     | type | possible_keys    | key  | key_len | ref  | rows    | Extra       |
+------+-------------+-----------+------+------------------+------+---------+------+---------+-------------+
|    1 | SIMPLE      | chrecords | ALL  | i_company_number | NULL | NULL    | NULL | 2208348 | Using where |
+------+-------------+-----------+------+------------------+------+---------+------+---------+-------------+
1 row in set (0.00 sec)

我使用以下创建了两个索引:

ALTER TABLE `chapp`.`chrecords` ADD INDEX `i_company_number` (`company_number`);

ALTER TABLE `chapp`.`chrecords`ADD FULLTEXT(
    `company_name`,
    `registered_office_address_address_line_1`,
    `registered_office_address_address_line_2`
);

但是,如何“组合”这两个索引呢?由于上述查询需要 15+ 秒才能执行(仅使用一个索引)。

整个表定义:

CREATE TABLE `chapp`.`chrecords` (
  `id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
  `company_name` VARCHAR(100) NULL,
  `company_number` VARCHAR(100) NULL,
  `registered_office_care_of` VARCHAR(100) NULL,
  `registered_office_po_box` VARCHAR(100) NULL,
  `registered_office_address_address_line_1` VARCHAR(100) NULL,
  `registered_office_address_address_line_2` VARCHAR(100) NULL,
  `registered_office_locality` VARCHAR(100) NULL,
  `registered_office_region` VARCHAR(100) NULL,
  `registered_office_country` VARCHAR(100) NULL,
  `registered_office_postal_code` VARCHAR(100) NULL
  );

ALTER TABLE `chapp`.`chrecords` ADD INDEX `i_company_name` (`company_name`);
ALTER TABLE `chapp`.`chrecords` ADD INDEX `i_company_number` (`company_number`);
ALTER TABLE `chapp`.`chrecords` ADD INDEX `i_registered_office_address_address_line_1` (`registered_office_address_address_line_1`);
ALTER TABLE `chapp`.`chrecords` ADD INDEX `i_registered_office_address_address_line_2` (`registered_office_address_address_line_2`);

ALTER TABLE `chapp`.`chrecords`ADD FULLTEXT(
    `company_name`,
    `registered_office_address_address_line_1`,
    `registered_office_address_address_line_2`
);

【问题讨论】:

【参考方案1】:
    (
        SELECT  *
            FROM  chrecords
            WHERE  company_number = 'test' 
            ORDER BY something
            LIMIT 10
    )
    UNION DISTINCT
    (
        SELECT  *
            FROM  cbrecords
            WHERE  MATCH (company_name, registered_office_address_address_line_1,
                                        registered_office_address_address_line_2)
                   AGAINST('test')
            ORDER BY something
            LIMIT 10
    ) 
    ORDER BY something
    LIMIT 10

注意事项:

不需要外部SELECT 明确地说DISTINCT(默认)或ALL(更快),这样您就会知道您考虑过是否需要重复数据删除,而不是速度。 没有ORDER BYLIMIT 没有多大意义 但是,如果您只想查看一些行,可以删除 ORDER BYs。 是的,ORDER BYLIMIT 需要在外面重复,这样您才能正确排序并限制为 10 个。

如果你需要OFFSET,里面需要一个完整的计数,比如LIMIT 50 5页,外面需要跳到第5页:LIMIT 40,10

【讨论】:

对于偏移量,它是否也与内部选择 LIMIT 40,10 和外部 LIMIT 10 具有相同的结果? @ChrisStryczynski - 否。测试用例:将偶数放在一个内部查询中;另一个是奇数。所需的 10 行来自中间——LIMIT 40, 10 会跳过它们。【参考方案2】:

尝试使用UNION 而不是OR

  SELECT *
    FROM (
       SELECT  * 
        FROM chrecords 
        WHERE company_number = 'test'
    ) a
    UNION (
       SELECT * 
         FROM cbrecords
        WHERE MATCH (company_name, 
                     registered_office_address_address_line_1, 
                     registered_office_address_address_line_2)
              AGAINST('test') 
        LIMIT 0, 10
     ) b

如果这有帮助,那是因为 mysql 难以在单个子查询中使用多个索引。这为查询规划器提供了两个查询。

您可以对每个子查询分别运行EXPLAIN 以了解它们的性能。 UNION 只是将他们的结果放在一起并消除重复项。如果您想保留重复项,请执行UNION ALL

请注意,MySQL 表上的大量单列索引通常会损害性能。您应该避免创建索引,除非它们是为了帮助特定查询而构建的。

【讨论】:

太棒了!但是,运行提供的查询会给我一个错误:您的 SQL 语法有错误;检查与您的 MariaDB 服务器版本相对应的手册,以在第 1 行的“b”附近使用正确的语法。如果我删除 b,它会起作用? 是的,删除 b。最后的SELECT 没有生成派生表,因此它不需要别名。第一个也可以在没有派生表的情况下重写,但是括号和联合总是有点让人费解,所以这是一个诚实的疏忽。这里需要bUNION ( 需要写成UNION SELECT * FROM (,这是不需要的。另请注意,为了清楚起见,您应该始终使用UNION DISTINCTUNION ALLUNION 在旧版本的 MySQL 中错误地用作 UNION ALL,而实际上应该推断出 DISTINCT。在 5.5 中修复。

以上是关于普通列和全文列的 MySQL 索引的主要内容,如果未能解决你的问题,请参考以下文章

MySQL(12)-索引

MySQL(12)-索引

mysql 普通索引和全文索引的区别

「进阶」MySQL中如何使用索引

MySQL-索引原理

mysql11---主键普通全文索引