普通列和全文列的 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 BY
的LIMIT
没有多大意义
但是,如果您只想查看一些行,可以删除 ORDER BYs
。
是的,ORDER BY
和 LIMIT
需要在外面重复,这样您才能正确排序并限制为 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
没有生成派生表,因此它不需要别名。第一个也可以在没有派生表的情况下重写,但是括号和联合总是有点让人费解,所以这是一个诚实的疏忽。这里需要b
,UNION (
需要写成UNION SELECT * FROM (
,这是不需要的。另请注意,为了清楚起见,您应该始终使用UNION DISTINCT
或UNION ALL
。 UNION
在旧版本的 MySQL 中错误地用作 UNION ALL
,而实际上应该推断出 DISTINCT
。在 5.5 中修复。以上是关于普通列和全文列的 MySQL 索引的主要内容,如果未能解决你的问题,请参考以下文章