联接表中全文搜索的性能
Performance of fulltext search in joined tables
我有三个table:
CREATE TABLE `dp_organisation` (
`OrganisationId` bigint(32) NOT NULL AUTO_INCREMENT,
`Name` text COLLATE utf8mb4_unicode_ci NOT NULL,
`ShortName` text COLLATE utf8mb4_unicode_ci,
PRIMARY KEY (`OrganisationId`),
FULLTEXT KEY `fulltext` (`Name`,`ShortName`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE `dp_organisation_member` (
`OrganisationId` bigint(32) NOT NULL,
`UserId` bigint(32) NOT NULL,
PRIMARY KEY (`OrganisationId`,`UserId`),
UNIQUE KEY `UserId` (`UserId`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE `dp_user` (
`UserId` bigint(32) NOT NULL AUTO_INCREMENT,
`Alias` varchar(125) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`Firstname` text COLLATE utf8mb4_unicode_ci NOT NULL,
`Surname` text COLLATE utf8mb4_unicode_ci,
`Email` varchar(125) COLLATE utf8mb4_unicode_ci NOT NULL,
PRIMARY KEY (`UserId`),
FULLTEXT KEY `fulltext` (`Alias`,`Firstname`,`Surname`,`Email`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
dp_organisation
包含所有组织,而 dp_users
包含所有用户。 dp_organisation_member
是用户和组织的关系。每个用户最多是一个组织的成员。
现在我想搜索匹配某个字符串的用户。我想在搜索时同时查看用户的信息和用户的组织信息,所以应该使用 dp_users
和 dp_organisation
上的全文索引。我创建了以下查询来实现此目的:
SELECT *
FROM dp_user u
LEFT JOIN dp_organisation_member m ON m.`UserId` = u.`UserId`
LEFT JOIN dp_organisation o ON o.`OrganisationId` = m.`OrganisationId`
WHERE MATCH(u.`Alias`, u.`Firstname`, u.`Surname`, u.`Email`) AGAINST ('foo')
OR MATCH(o.`Name`, o.`ShortName`) AGAINST ('foo')
但是查询执行得非常糟糕。只是为了测试,我尝试了以下,它只搜索用户的信息:
SELECT *
FROM dp_user u
LEFT JOIN dp_organisation_member m ON m.`UserId` = u.`UserId`
LEFT JOIN dp_organisation o ON o.`OrganisationId` = m.`OrganisationId`
WHERE MATCH(u.`Alias`, u.`Firstname`, u.`Surname`, u.`Email`) AGAINST ('foo')
它的运行速度快了大约 30 倍。
如果我只在组织的信息中搜索:
SELECT *
FROM dp_user u
LEFT JOIN dp_organisation_member m ON m.`UserId` = u.`UserId`
LEFT JOIN dp_organisation o ON o.`OrganisationId` = m.`OrganisationId`
WHERE MATCH(o.`Name`, o.`ShortName`) AGAINST ('foo')
查询又慢了
为了检查 dp_organisation
中的全文索引没有问题,我将查询从 dp_organisation
反向到 select 并加入 dp_user
:
SELECT *
FROM dp_organisation o
LEFT JOIN dp_organisation_member m ON m.`OrganisationId` = o.`OrganisationId`
LEFT JOIN dp_user u ON u.`UserId` = m.`UserId`
WHERE MATCH(u.`Alias`, u.`Firstname`, u.`Surname`, u.`Email`) AGAINST ('foo')
OR MATCH(o.`Name`, o.`ShortName`) AGAINST ('foo')
上面的查询速度慢,只在用户信息中查询也是这样:
SELECT *
FROM dp_organisation o
LEFT JOIN dp_organisation_member m ON m.`OrganisationId` = o.`OrganisationId`
LEFT JOIN dp_user u ON u.`UserId` = m.`UserId`
WHERE MATCH(u.`Alias`, u.`Firstname`, u.`Surname`, u.`Email`) AGAINST ('foo')
然而,仅在组织信息中搜索的查询速度很快(大约快 25 倍):
SELECT *
FROM dp_organisation o
LEFT JOIN dp_organisation_member m ON m.`OrganisationId` = o.`OrganisationId`
LEFT JOIN dp_user u ON u.`UserId` = m.`UserId`
WHERE MATCH(o.`Name`, o.`ShortName`) AGAINST ('foo')
所以我似乎只有在主 table 中进行全文搜索时才能获得良好的性能,而不是加入 table 的那些。在连接的 table?
中进行全文搜索时,我该怎么做才能获得良好的性能?
在您的查询中组合 FTS 和 JOIN 会导致速度变慢,因为 mysql 通常每个 table 使用一个索引。当您在 table 上执行 FTS 时,mysql 会在该 table 上使用全文索引,因此无法为连接使用索引。
在其他新闻中,dp_organisation_member table 上的索引没有多大意义。您已将 user_id
字段设为唯一。这意味着一个用户只能属于一个组织,这实际上意味着 dp_organisation_member table 是多余的。你已经过度规范化了。您可以删除此 table 并将组织 ID 添加到 dp_user 并删除您的一个加入。
我建议初学者切换到 InnoDB。从 5.6.4 开始,FULLTEXT
可用。有一个few differences需要注意。
当优化器在 MATCH
和其他类型的过滤器之间进行选择时,它将执行 FULLTEXT
,而不是其他。
WHERE MATCH... OR MATCH...
不好,因为 OR
。 FULTEXT
在这里表现不佳。将它变成 ( SELECT ... MATCH ) UNION ( SELECT ... MATCH )
是一种可能的解决方法。
LEFT JOIN
喜欢先过滤 'left' table。所以 table 可以使用 FULLTEXT
而不是 'right' table。一般情况下,除非需要,否则不要使用LEFT
。
我有三个table:
CREATE TABLE `dp_organisation` (
`OrganisationId` bigint(32) NOT NULL AUTO_INCREMENT,
`Name` text COLLATE utf8mb4_unicode_ci NOT NULL,
`ShortName` text COLLATE utf8mb4_unicode_ci,
PRIMARY KEY (`OrganisationId`),
FULLTEXT KEY `fulltext` (`Name`,`ShortName`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE `dp_organisation_member` (
`OrganisationId` bigint(32) NOT NULL,
`UserId` bigint(32) NOT NULL,
PRIMARY KEY (`OrganisationId`,`UserId`),
UNIQUE KEY `UserId` (`UserId`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE `dp_user` (
`UserId` bigint(32) NOT NULL AUTO_INCREMENT,
`Alias` varchar(125) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`Firstname` text COLLATE utf8mb4_unicode_ci NOT NULL,
`Surname` text COLLATE utf8mb4_unicode_ci,
`Email` varchar(125) COLLATE utf8mb4_unicode_ci NOT NULL,
PRIMARY KEY (`UserId`),
FULLTEXT KEY `fulltext` (`Alias`,`Firstname`,`Surname`,`Email`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
dp_organisation
包含所有组织,而 dp_users
包含所有用户。 dp_organisation_member
是用户和组织的关系。每个用户最多是一个组织的成员。
现在我想搜索匹配某个字符串的用户。我想在搜索时同时查看用户的信息和用户的组织信息,所以应该使用 dp_users
和 dp_organisation
上的全文索引。我创建了以下查询来实现此目的:
SELECT *
FROM dp_user u
LEFT JOIN dp_organisation_member m ON m.`UserId` = u.`UserId`
LEFT JOIN dp_organisation o ON o.`OrganisationId` = m.`OrganisationId`
WHERE MATCH(u.`Alias`, u.`Firstname`, u.`Surname`, u.`Email`) AGAINST ('foo')
OR MATCH(o.`Name`, o.`ShortName`) AGAINST ('foo')
但是查询执行得非常糟糕。只是为了测试,我尝试了以下,它只搜索用户的信息:
SELECT *
FROM dp_user u
LEFT JOIN dp_organisation_member m ON m.`UserId` = u.`UserId`
LEFT JOIN dp_organisation o ON o.`OrganisationId` = m.`OrganisationId`
WHERE MATCH(u.`Alias`, u.`Firstname`, u.`Surname`, u.`Email`) AGAINST ('foo')
它的运行速度快了大约 30 倍。
如果我只在组织的信息中搜索:
SELECT *
FROM dp_user u
LEFT JOIN dp_organisation_member m ON m.`UserId` = u.`UserId`
LEFT JOIN dp_organisation o ON o.`OrganisationId` = m.`OrganisationId`
WHERE MATCH(o.`Name`, o.`ShortName`) AGAINST ('foo')
查询又慢了
为了检查 dp_organisation
中的全文索引没有问题,我将查询从 dp_organisation
反向到 select 并加入 dp_user
:
SELECT *
FROM dp_organisation o
LEFT JOIN dp_organisation_member m ON m.`OrganisationId` = o.`OrganisationId`
LEFT JOIN dp_user u ON u.`UserId` = m.`UserId`
WHERE MATCH(u.`Alias`, u.`Firstname`, u.`Surname`, u.`Email`) AGAINST ('foo')
OR MATCH(o.`Name`, o.`ShortName`) AGAINST ('foo')
上面的查询速度慢,只在用户信息中查询也是这样:
SELECT *
FROM dp_organisation o
LEFT JOIN dp_organisation_member m ON m.`OrganisationId` = o.`OrganisationId`
LEFT JOIN dp_user u ON u.`UserId` = m.`UserId`
WHERE MATCH(u.`Alias`, u.`Firstname`, u.`Surname`, u.`Email`) AGAINST ('foo')
然而,仅在组织信息中搜索的查询速度很快(大约快 25 倍):
SELECT *
FROM dp_organisation o
LEFT JOIN dp_organisation_member m ON m.`OrganisationId` = o.`OrganisationId`
LEFT JOIN dp_user u ON u.`UserId` = m.`UserId`
WHERE MATCH(o.`Name`, o.`ShortName`) AGAINST ('foo')
所以我似乎只有在主 table 中进行全文搜索时才能获得良好的性能,而不是加入 table 的那些。在连接的 table?
中进行全文搜索时,我该怎么做才能获得良好的性能?在您的查询中组合 FTS 和 JOIN 会导致速度变慢,因为 mysql 通常每个 table 使用一个索引。当您在 table 上执行 FTS 时,mysql 会在该 table 上使用全文索引,因此无法为连接使用索引。
在其他新闻中,dp_organisation_member table 上的索引没有多大意义。您已将 user_id
字段设为唯一。这意味着一个用户只能属于一个组织,这实际上意味着 dp_organisation_member table 是多余的。你已经过度规范化了。您可以删除此 table 并将组织 ID 添加到 dp_user 并删除您的一个加入。
我建议初学者切换到 InnoDB。从 5.6.4 开始,FULLTEXT
可用。有一个few differences需要注意。
当优化器在 MATCH
和其他类型的过滤器之间进行选择时,它将执行 FULLTEXT
,而不是其他。
WHERE MATCH... OR MATCH...
不好,因为 OR
。 FULTEXT
在这里表现不佳。将它变成 ( SELECT ... MATCH ) UNION ( SELECT ... MATCH )
是一种可能的解决方法。
LEFT JOIN
喜欢先过滤 'left' table。所以 table 可以使用 FULLTEXT
而不是 'right' table。一般情况下,除非需要,否则不要使用LEFT
。