【问题标题】:MySQL Index Hints with Fulltext indexes带有全文索引的 MySQL 索引提示
【发布时间】:2020-11-09 21:03:43
【问题描述】:

我在全文索引方面遇到问题,并尝试同时使用索引提示。 这是一个示例表:

CREATE TABLE IF NOT EXISTS `products` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `idcategory` bigint(20) NOT NULL,
  `name` text NOT NULL,
  `short_description` text,
  `description` longtext NOT NULL,
  PRIMARY KEY (`id`),
  KEY `idcategory` (`idcategory`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

ALTER TABLE `products` ADD FULLTEXT INDEX `name_fulltext` (`name`);
ALTER TABLE `products` ADD FULLTEXT INDEX `short_description_fulltext` (`short_description`);
ALTER TABLE `products` ADD FULLTEXT INDEX `description_fulltext` (`description`);

这个查询运行良好:

SELECT *
FROM products USE INDEX (idcategory)
WHERE idcategory = 1

这个查询运行良好:

SELECT *
FROM products AS p
WHERE p.idcategory = 1
AND (
    MATCH(p.name) AGAINST ("*test*" IN BOOLEAN MODE)
    OR MATCH(p.short_description) AGAINST ("*test*" IN BOOLEAN MODE)
    OR MATCH(p.description) AGAINST ("*test*" IN BOOLEAN MODE)
)

此查询有错误:

SELECT *
FROM products AS p USE INDEX (idcategory)
WHERE p.idcategory = 1
AND (
    MATCH(p.name) AGAINST ("*test*" IN BOOLEAN MODE)
    OR MATCH(p.short_description) AGAINST ("*test*" IN BOOLEAN MODE)
    OR MATCH(p.description) AGAINST ("*test*" IN BOOLEAN MODE)
)

Error: #1191 - Can't find FULLTEXT index matching the column list

我不明白为什么不使用索引以及为什么它寻找 FULTEXT 索引来匹配。 匹配什么?

来自文档 (https://dev.mysql.com/doc/refman/8.0/en/index-hints.html):

对于布尔模式搜索,使用 FOR ORDER BY 或 FOR GROUP BY 的索引提示会被忽略。使用 FOR JOIN 或没有 FOR 修饰符的索引提示是受尊重的。与提示如何应用于非 FULLTEXT 搜索相比,提示用于查询执行的所有阶段(查找行和检索、分组和排序)。即使为非 FULLTEXT 索引提供提示也是如此。

那么问题是什么? 它表示索引是尊重,即使在 FULLTEXT 搜索的情况下为非 FULLTEXT 索引提供了提示。 我错过了什么吗?我是否误解了文档?

感谢您阅读本文。

【问题讨论】:

  • 对此不确定,但如果您设置一个 multiple-column 索引(而不是 3 个单独的索引),是否仍会导致错误?如所写,我认为您不能在提示中指定单个索引,因为 WHERE 子句需要/使用所有三个。
  • @b-frid 你的意思是我应该把它们都包含在索引提示中?
  • 请参阅下面的“答案”以了解相关信息。在对非 FULLTEXT 索引/键“idcategory”进行索引的同时在其他列上运行这些 BOOLEAN 搜索时,我认为我们不能强制(通过显式使用 USE_INDEX)不包括完成上述MATCH / AGAINST 搜索所需的那些。 (如果我不在基地,希望其他人可以参与进来。)
  • 补充一点,我不完全确定 'idcategory' 有一个隐式建立的索引。 Primary keys indeed appear to be implicitly indexed,但非主键我不太确定。如果不是,我能想出的唯一原因是您上面的第一个SELECT 查询没有错误是索引请求被简单地忽略了,而在第三个SELECT 它错误,因为您添加了需要的搜索其他索引(尽管行为略有不一致)。
  • 我不认为它被忽略了。尝试用随机记录填充表并在第一个查询上运行解释。你ll see that the records are indeed restricted based on the idcategory`索引。默认情况下这是否无关紧要,因为在您使用 MATCH 的情况下,它会被忽略或导致错误。

标签: mysql sql full-text-indexing


【解决方案1】:

** 11/10 编辑**

崩溃并开始摆弄自己。经过一些研究和测试,我整理了这组查询,希望能帮助阐明一些额外的信息。 (我使用了dbfiddle。请注意,失败的查询会被注释掉,因为如果留在其中,则不会运行其他任何东西,至少在dbfiddle 上。)

尝试运行这些,希望输出和 cmets 能最好地解释。特别是,查询集 7 和 8 展示了如何设置多列 FULLTEXT INDEX,因为我认为这可能是您的最终意图。

-------------------------------- Query Set 1 ------------------------------------
-- Only possible key is `idcategory`, because each FULLTEXT INDEX is for a single 
-- column, but using 3 different MATCH clauses, each for a different column. 
-- (i.e. Can't index into all 3 simultaneously using only a single column index.)
-- Keys/Indexes on `idcategory`.

DROP TABLE IF EXISTS `products`;

CREATE TABLE `products` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `idcategory` bigint(20) NOT NULL,
  `name` text NOT NULL,
  `short_description` text,
  `description` longtext NOT NULL,
  PRIMARY KEY (`id`),
  KEY `idcategory` (`idcategory`),
  FULLTEXT INDEX `name_fulltext` (`name`),
  FULLTEXT INDEX `short_description_fulltext` (`short_description`),
  FULLTEXT INDEX `description_fulltext` (`description`)  
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


INSERT INTO `products` (`idcategory`, `name`, `short_description`, `description`)
VALUES (1, 'test1', 'sdesc1', 'desc1'), (1, 'test2', 'sdesc2', 'desc2'), (2, 'test3', 'sdesc3', 'desc3');

EXPLAIN SELECT *
FROM products AS p
WHERE p.idcategory = 1
AND (
    MATCH(p.name) AGAINST ("*test*" IN BOOLEAN MODE)
    OR MATCH(p.short_description) AGAINST ("*test*" IN BOOLEAN MODE)
    OR MATCH(p.description) AGAINST ("*test*" IN BOOLEAN MODE)
);

SELECT *
FROM products AS p
WHERE p.idcategory = 1
AND (
    MATCH(p.name) AGAINST ("*test*" IN BOOLEAN MODE)
    OR MATCH(p.short_description) AGAINST ("*test*" IN BOOLEAN MODE)
    OR MATCH(p.description) AGAINST ("*test*" IN BOOLEAN MODE)
);

-------------------------------- Query Set 2 ------------------------------------

-- Possible keys are `idcategory` and `name_fulltext`. All FULLTEXT INDEXES are 
-- single column, and only 1 MATCH clause [for a single column, `name`] is 
-- specified. Keys/Indexes on `name_fulltext`. (When faced with a choice of 
-- multiple indexes, the optimizer will choose the most selective option [i.e. 
-- that option with the lowest number of rows]).

DROP TABLE IF EXISTS `products`;

CREATE TABLE `products` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `idcategory` bigint(20) NOT NULL,
  `name` text NOT NULL,
  `short_description` text,
  `description` longtext NOT NULL,
  PRIMARY KEY (`id`),
  KEY `idcategory` (`idcategory`),
  FULLTEXT INDEX `name_fulltext` (`name`),
  FULLTEXT INDEX `short_description_fulltext` (`short_description`),
  FULLTEXT INDEX `description_fulltext` (`description`)  
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


INSERT INTO `products` (`idcategory`, `name`, `short_description`, `description`)
VALUES (1, 'test1', 'sdesc1', 'desc1'), (1, 'test2', 'sdesc2', 'desc2'), (2, 'test3', 'sdesc3', 'desc3');

EXPLAIN SELECT *
FROM products AS p
WHERE p.idcategory = 1
AND (
    MATCH(p.name) AGAINST ("*test*" IN BOOLEAN MODE)
);

SELECT *
FROM products AS p
WHERE p.idcategory = 1
AND (
    MATCH(p.name) AGAINST ("*test*" IN BOOLEAN MODE)
);

-------------------------------- Query Set 3 ------------------------------------

-- Same as Query Set 2, but explicitly call for INDEX `name_fulltext`. Now only
-- possible key is name_fulltext.

DROP TABLE IF EXISTS `products`;

CREATE TABLE `products` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `idcategory` bigint(20) NOT NULL,
  `name` text NOT NULL,
  `short_description` text,
  `description` longtext NOT NULL,
  PRIMARY KEY (`id`),
  KEY `idcategory` (`idcategory`),
  FULLTEXT INDEX `name_fulltext` (`name`),
  FULLTEXT INDEX `short_description_fulltext` (`short_description`),
  FULLTEXT INDEX `description_fulltext` (`description`)  
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


INSERT INTO `products` (`idcategory`, `name`, `short_description`, `description`)
VALUES (1, 'test1', 'sdesc1', 'desc1'), (1, 'test2', 'sdesc2', 'desc2'), (2, 'test3', 'sdesc3', 'desc3');

EXPLAIN SELECT *
FROM products AS p USE INDEX (`name_fulltext`)
WHERE p.idcategory = 1 
AND (
    MATCH(p.name) AGAINST ("*test*" IN BOOLEAN MODE)
);

SELECT *
FROM products AS p USE INDEX (`name_fulltext`)
WHERE p.idcategory = 1
AND (
    MATCH(p.name) AGAINST ("*test*" IN BOOLEAN MODE)
);

-------------------------------- Query Set 4 ------------------------------------
----------------------------------- FAILS ---------------------------------------
-- Same as Query Set 1 but trying to force INDEX `name_fulltext`. Doesn't work
-- because `name_fulltext` only indexes a single column, but MATCH / AGAINST
-- has three different clauses, each specifying a different column. (And hence
-- why the only possible key is `idcategory`, as shown in output of EXPLAIN, 
-- Query Set 1.)

--DROP TABLE IF EXISTS `products`;

--CREATE TABLE `products` (
--  `id` bigint(20) NOT NULL AUTO_INCREMENT,
--  `idcategory` bigint(20) NOT NULL,
--  `name` text NOT NULL,
--  `short_description` text,
--  `description` longtext NOT NULL,
--  PRIMARY KEY (`id`),
--  KEY `idcategory` (`idcategory`),
--  FULLTEXT INDEX `name_fulltext` (`name`),
--  FULLTEXT INDEX `short_description_fulltext` (`short_description`),
--  FULLTEXT INDEX `description_fulltext` (`description`)  
--) ENGINE=InnoDB DEFAULT CHARSET=utf8;


--INSERT INTO `products` (`idcategory`, `name`, `short_description`, `description`)
--VALUES (1, 'test1', 'sdesc1', 'desc1'), (1, 'test2', 'sdesc2', 'desc2'), (2, 'test3', 'sdesc3', 'desc3');

--EXPLAIN SELECT *
--FROM products AS p USE INDEX (`name_fulltext`)
--WHERE p.idcategory = 1
--AND (
--    MATCH(p.name) AGAINST ("*test*" IN BOOLEAN MODE)
--    OR MATCH(p.short_description) AGAINST ("*test*" IN BOOLEAN MODE)
--    OR MATCH(p.description) AGAINST ("*test*" IN BOOLEAN MODE)
--);

--SELECT *
--FROM products AS p USE INDEX (`name_fulltext`)
--WHERE p.idcategory = 1
--AND (
--    MATCH(p.name) AGAINST ("*test*" IN BOOLEAN MODE)
--    OR MATCH(p.short_description) AGAINST ("*test*" IN BOOLEAN MODE)
--    OR MATCH(p.description) AGAINST ("*test*" IN BOOLEAN MODE)
--);


-------------------------------- Query Set 5 ------------------------------------
----------------------------------- FAILS ---------------------------------------

-- Trying to force keying/indexing on `idcategory` instead of `name_fulltext`. 
-- Doesn't work because `idcategory` is not established as a FULLTEXT INDEX, 
-- which is required for MATCH / AGAINST? It keyed on this field when not 
-- explicitly calling USE INDEX and no other index was possible, and when
-- explicitly calling USE INDEX but with no MATCH / AGAINST call, so
-- this behavior seems a bit inconsistent.

--DROP TABLE IF EXISTS `products`;

--CREATE TABLE `products` (
--  `id` bigint(20) NOT NULL AUTO_INCREMENT,
--  `idcategory` bigint(20) NOT NULL,
--  `name` text NOT NULL,
--  `short_description` text,
--  `description` longtext NOT NULL,
--  PRIMARY KEY (`id`),
--  KEY `idcategory` (`idcategory`),
--  FULLTEXT INDEX `name_fulltext` (`name`),
--  FULLTEXT INDEX `short_description_fulltext` (`short_description`),
--  FULLTEXT INDEX `description_fulltext` (`description`)  
--) ENGINE=InnoDB DEFAULT CHARSET=utf8;


--INSERT INTO `products` (`idcategory`, `name`, `short_description`, `description`)
--VALUES (1, 'test1', 'sdesc1', 'desc1'), (1, 'test2', 'sdesc2', 'desc2'), (2, 'test3', 'sdesc3', 'desc3');

--EXPLAIN SELECT *
--FROM products AS p USE INDEX (`idcategory`)
--WHERE p.idcategory = 1 
--AND (
--    MATCH(p.name) AGAINST ("*test*" IN BOOLEAN MODE)
--);

--SELECT *
--FROM products AS p USE INDEX (`idcategory`)
--WHERE p.idcategory = 1
--AND (
--   MATCH(p.name) AGAINST ("*test*" IN BOOLEAN MODE)
--);

-------------------------------- Query Set 6 ------------------------------------
----------------------------------- FAILS ---------------------------------------

-- We can't make `idcategory` a FULLTEXT INDEX of course, because it is a 
-- bigint(20). (Could try making this KEY a text field instead, 
-- see if error persists...)

--DROP TABLE IF EXISTS `products`;

--CREATE TABLE `products` (
--  `id` bigint(20) NOT NULL AUTO_INCREMENT,
--  `idcategory` bigint(20) NOT NULL,
--  `name` text NOT NULL,
--  `short_description` text,
--  `description` longtext NOT NULL,
--  PRIMARY KEY (`id`),
--  KEY `idcategory` (`idcategory`),
--  FULLTEXT INDEX `idcategory_fulltext` (`idcategory`) 
--) ENGINE=InnoDB DEFAULT CHARSET=utf8;


--INSERT INTO `products` (`idcategory`, `name`, `short_description`, `description`)
--VALUES (1, 'test1', 'sdesc1', 'desc1'), (1, 'test2', 'sdesc2', 'desc2'), (2, 'test3', 'sdesc3', 'desc3');

--EXPLAIN SELECT *
--FROM products AS p USE INDEX (`idcategory`)
--WHERE p.idcategory = 1 
--AND (
--    MATCH(p.name) AGAINST ("*test*" IN BOOLEAN MODE)
--);

--SELECT *
--FROM products AS p USE INDEX (`idcategory`)
--WHERE p.idcategory = 1
--AND (
--    MATCH(p.name) AGAINST ("*test*" IN BOOLEAN MODE)
--);

-------------------------------- Query Set 7 ------------------------------------

-- A multi-column index. The MATCH / AGAINST search functions like an 'OR' 
-- clause for all three columns. (i.e. if the text pattern is present in any
-- of the three columns, a match is found). Must specify all three columns in MATCH
-- clause, because the FULLTEXT INDEX includes all three.
-- Keys/Indexes on 'name_sdesc_desc_fulltext` (multi-column fulltext index),
-- but shows `idcategory` as possible index.
-- (Perhaps when all is said and done this is the desired behavior?)

DROP TABLE IF EXISTS `products`;

CREATE TABLE `products` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `idcategory` bigint(20) NOT NULL,
  `name` text NOT NULL,
  `short_description` text,
  `description` longtext NOT NULL,
  PRIMARY KEY (`id`),
  KEY `idcategory` (`idcategory`),
  FULLTEXT INDEX `name_sdesc_desc_fulltext` (`name`, `short_description`, `description`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


INSERT INTO `products` (`idcategory`, `name`, `short_description`, `description`)
VALUES (1, 'test1', 'sdesc1', 'desc1'), (1, 'test2', 'sdesc2', 'desc2'), (2, 'test3', 'sdesc3', 'desc3');

EXPLAIN SELECT *
FROM products AS p
WHERE p.idcategory = 1
AND (
    MATCH(p.name, p.short_description, p.description) AGAINST ("*test*" IN BOOLEAN MODE)
);

SELECT *
FROM products AS p
WHERE p.idcategory = 1
AND (
    MATCH(p.name, p.short_description, p.description) AGAINST ("*test*" IN BOOLEAN MODE)
);

-------------------------------- Query Set 8 ------------------------------------

-- Same as Query Set 7, but explicitly calls for `name_sdesc_desc` FULLTEXT INDEX.
-- Now, only possible index is `name_sdesc_desc_fulltext`.

DROP TABLE IF EXISTS `products`;

CREATE TABLE `products` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `idcategory` bigint(20) NOT NULL,
  `name` text NOT NULL,
  `short_description` text,
  `description` longtext NOT NULL,
  PRIMARY KEY (`id`),
  KEY `idcategory` (`idcategory`),
  FULLTEXT INDEX `name_sdesc_desc_fulltext` (`name`, `short_description`, `description`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


INSERT INTO `products` (`idcategory`, `name`, `short_description`, `description`)
VALUES (1, 'test1', 'sdesc1', 'desc1'), (1, 'test2', 'sdesc2', 'desc2'), (2, 'test3', 'sdesc3', 'desc3');

EXPLAIN SELECT *
FROM products AS p USE INDEX (`name_sdesc_desc`)
WHERE p.idcategory = 1
AND (
    MATCH(p.name, p.short_description, p.description) AGAINST ("*test*" IN BOOLEAN MODE)
);

SELECT *
FROM products AS p USE INDEX (`name_sdesc_desc`)
WHERE p.idcategory = 1
AND (
    MATCH(p.name, p.short_description, p.description) AGAINST ("*test*" IN BOOLEAN MODE)
);

【讨论】:

  • 是的,我什至无法创建表。我明白了:#1170 - BLOB/TEXT 列“名称”在没有密钥长度的密钥规范中使用
  • 我忘记了KEY idcategory (idcategory) 行后面的逗号,我在上面已经更正了。假设您在之前运行时尚未自行更正,如果使用上面更正的语法再次尝试,您是否仍会得到相同的结果?
  • 查看编辑后的答案。查询集 7 和 8 应该提供设置多列索引的方法。
  • 没用。确实如此,但是,我买不起综合指数。我尝试使用它的实际表有大约 500k 条记录,并且更新非常频繁。我在开发环境中尝试过,选择速度慢了一倍以上。
猜你喜欢
  • 2023-03-13
  • 1970-01-01
  • 1970-01-01
  • 2013-03-12
  • 2017-11-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多