搜索具有相关性的大型 mysql 数据库

Posted

技术标签:

【中文标题】搜索具有相关性的大型 mysql 数据库【英文标题】:Searching a big mysql database with relevance 【发布时间】:2013-01-11 17:52:42 【问题描述】:

我正在为我们公司的 Intranet 构建一个相当大的“搜索”引擎,它有 1miljon 以及多个条目 它在相当快的服务器上运行,但某些搜索查询最多需要 1 分钟。

这就是表格的样子

我尝试为它创建一个索引,但似乎我遗漏了什么,这就是显示索引的显示方式

这是查询本身,主要是排序减慢了查询速度,但即使是没有排序的查询也有点慢。

SELECT SQL_CALC_FOUND_ROWS *
FROM `businessunit`
INNER JOIN `businessunit-postaddress` ON `businessunit`.`Id` = `businessunit-postaddress`.`BusinessUnit`
WHERE `businessunit`.`Name` LIKE 'tanto%'
ORDER BY `businessunit`.`Premium` DESC ,
CASE WHEN `businessunit`.`Name` = 'tanto'
THEN 0
WHEN `businessunit`.`Name` LIKE 'tanto %'
THEN 1
WHEN `businessunit`.`Name` LIKE 'tanto%'
THEN 2
ELSE 3
END , `businessunit`.`Name`
LIMIT 0 , 30

非常感谢任何帮助

编辑: 使这个查询窒息 99% 是按与通配符 % 的相关性排序 当我进行解释时,它说使用 where;使用 fsort

【问题讨论】:

更改查询:删除查询的某些部分并再次运行(反复试验),您可以自己找出导致结果变慢的部分。 当然可以,但问题仍然存在,我应该如何继续按相关性排序,但又要保持快速 你试过EXPLAIN SELECT吗? 你找到减速的部分了吗?例如:删除所有order by,然后测试,如果它很慢,那么它不是order by,删除where子句等,您应该确定确切的部分 你能告诉我们它加入的另一个表的结构吗? 【参考方案1】:

你应该试试 sphinx 搜索解决方案,它是全文搜索引擎,它会给你带来非常好的性能以及很多设置相关性的选项。

Click here了解更多详情。

【讨论】:

+1 表示快速和高级的搜索在 SQL 中并不容易。我个人在Solr 上取得了很大的成功,它有一个good library for php (Solr PHP Client)。 看一看,似乎很有趣=) 取决于你需要匹配的搜索类型;如果您只需要匹配很多精确的值,正如您的数据所建议的那样,那么 SQL 应该可以很好地执行。 @ElYobo 是的,它只是匹配最多 255 个字符的字符串,这是世界各地的常规公司名称,只要我从“case”删除到“end”,查询的执行速度就会快 1000 倍,所以它的排序与相关性令人窒息 我最终使用了这个,它最接近我想要完成的目标【参考方案2】:

似乎索引不包括Premium,但这是第一个ORDER BY 参数。

使用EXPLAIN your query here 找出查询计划并更改索引以删除任何表扫描,如http://dev.mysql.com/doc/refman/5.0/en/using-explain.html 中所述

【讨论】:

【参考方案3】:

MySQL 非常适合存储数据,但在基于快速文本的搜索方面却不是很好。

除了已经推荐的 Sphinx,我推荐两个很棒的搜索引擎:

    Solr 和 http://pecl.php.net/package/solr - 非常流行的搜索引擎。用于 NetFlix 等海量服务。

    Elastic Search - 相对较新的软件,但拥有非常活跃的社区和很多尊重

两种解决方案都基于同一个库Apache Lucene

【讨论】:

你能建议如何使用 PHP +mysqli 吗?【参考方案4】:

如果“ORDER BY”确实是瓶颈,直接的解决方案是从查询中删除“ORDER BY”逻辑,并使用 C# 排序直接在应用程序代码中重新实现排序。不幸的是,这意味着您还必须将分页移动到您的应用程序中,因为您需要获得完整的结果集,然后才能对其进行排序和分页。我只是提到这一点,因为到目前为止似乎没有其他人想到它。

坦率地说(就像其他人指出的那样),您在顶部显示的查询不需要全文索引。只要有问题的列上有 BTREE(而不是 HASH)索引,单个后缀通配符(例如,LIKE 'ABC%')就应该非常有效。

而且,就我个人而言,我什至不反感双通配符(例如,LIKE '%ABC%"),它当然永远不会使用索引,只要全表扫描便宜。大概 250,000 行是我开始认真考虑全文索引的地方。100,000 绝对没有问题。

不过,我始终确保我的 SELECT 是脏读(未对选择应用事务性)。

无论如何,一旦进入用户的眼球就很脏!

【讨论】:

【参考方案5】:

大多数面向搜索引擎的网站都使用FULL-TEXT-SEARCH。 与selectLIKE 相比,它会更快... 我添加了一个示例和一些链接... 我想这对你有用... 在这个全文搜索中也有一些条件...

步骤:1

CREATE TABLE articles (
    id INT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY,
    title VARCHAR(200),
    body TEXT,
    FULLTEXT (title,body)
);

步骤:2

INSERT INTO articles (title,body) VALUES
    ('MySQL Tutorial','DBMS stands for DataBase ...'),
    ('How To Use MySQL Well','After you went through a ...'),
    ('Optimizing MySQL','In this tutorial we will show ...'),
    ('1001 MySQL Tricks','1. Never run mysqld as root. 2. ...'),
    ('MySQL vs. YourSQL','In the following database comparison ...'),
    ('MySQL Security','When configured properly, MySQL ...');

STEP:3 自然语言全文搜索:

SELECT * FROM articles
    WHERE MATCH (title,body) AGAINST ('database');

布尔全文搜索

SELECT * FROM articles WHERE MATCH (title,body)
     AGAINST ('+MySQL -YourSQL' IN BOOLEAN MODE);

浏览此链接 viralpatel.net,devzone.zend.com,sqlmag.com,colorado.edu,en.wikipedia.org

【讨论】:

谢谢你.......如果你有任何疑问,请告诉我......我将这个用于大型数据库...... 我让它运行得很好,但是当我运行这个查询SELECT SQL_CALC_FOUND_ROWS *, MATCH(businessunit.Name) AGAINST('städ') AS score FROM businessunit INNER JOIN businessunit-postaddress` ON businessunit时,我仍然得到“使用位置,使用文件排序” .Id=businessunit-postaddress.BusinessUnit WHERE MATCH(Name) AGAINST('städ') ORDER BY businessunit.Premium DESC, score DESC LIMIT 60,30` 所以排序仍然影响性能 您好,请为选择数据添加索引......然后你可以通过查询进行检查...... c-sharpcorner.com/UploadFile/krishnasarala/… 参考这个 这是索引img526.imageshack.us/img526/7168/indexes.png 这是解释结果img824.imageshack.us/img824/8253/explainy.png【参考方案6】:

这是一个很奇怪的查询 :) 让我们试着理解它的作用。

在某些条件下,“businessunit”表中的结果少于 30 行。

第一个条件是“businessunit-postaddress”表的外键。 请检查businessunit-postaddress.BusinessUnit列是否有索引。

第二个是一个过滤器,只返回以“tanto”开头的businessunit.Name 的行。 如果我没记错的话,你有一个非常复杂的索引“业务”包含 11 个字段! 并且字段“名称”不是该索引中的第一个字段。 所以当你运行“like tanto%”的查询时,这个索引是没有用的。 我完全怀疑这个索引的必要性。 顺便说一句,它需要相当大的资源来维护和减慢此表的编辑操作。 您必须使用唯一字段“名称”创建索引。

过滤查询后对结果进行排序,并以一些奇怪的方式进行。 起初它按字段排序 businessunit.Premium - 这很正常。 但是,带有 CASE 的下一个语句也没有用。 这就是为什么。 将零分配给 Name = 'tanto'(完全正确)。 下一行是在“tanto”之后有空格的行 - 在任何情况下(特殊符号除外),这些行都将在“tanto”之后,因为空格小于任何字母。 接下来的两行是在“tanto”之后带有一些字母的行(包括空格!)。根据定义,这些行也将按此顺序排列。 这三个是“保留”用于“其他”行,但您不会得到“其他”行 - 请记住 [WHERE businessunit.Name LIKE 'tanto%'] 条件。 所以 ORDER BY 的这一部分是没有意义的。 在 ORDER BY 的末尾,又是businessunit.Name...

我的建议:您需要从头开始重建查询,牢记您想要得到什么。

无论如何我猜你可以使用

SELECT SQL_CALC_FOUND_ROWS *
FROM `businessunit`
INNER JOIN `businessunit-postaddress` ON `businessunit`.`Id` = `businessunit-postaddress`.`BusinessUnit`
WHERE `businessunit`.`Name` LIKE 'tanto%'
ORDER BY `businessunit`.`Premium` DESC,
`businessunit`.`Name`
LIMIT 0 , 30

不要忘记字段businessunit-postaddress.BusinessUnit的索引!

而且我对字段溢价有很强的假设。 我猜它是为存储二进制数据而设计的(是/否)。 所以一个普通的(BTREE)索引不匹配。 你必须使用位图索引。

附:我不确定您是否真的需要使用 SQL_CALC_FOUND_ROWS MySQL: Pagination - SQL_CALC_FOUND_ROWS vs COUNT()-Query

【讨论】:

2 认为很快就想到了 mysql 中没有 bitman 索引,据我所知,与全文搜索相比,搜索本身仍然很慢,无论如何我已经进行了测试,但是 Count vs SQL_Calc 部分似乎很有趣,谢谢 m8 @Breezer 您是否尝试了我的查询而不是原来的查询?当然,请检查 businessunit-postaddress.BusinessUnit 上是否存在索引。如果您愿意,我们可以通过电子邮件讨论我的帖子。在我看来,这个问题比你想象的要简单得多。在这种情况下,您不需要全文搜索。【参考方案7】:

它可以是全文(http://dev.mysql.com/doc/refman/5.0/en/fulltext-search.html),也可以是 php 和 mysql 端的模式匹配(http://dev.mysql.com/doc/refman/5.0/en/pattern-matching.html)。

从经验和理论:

全文的优点- 1) 结果非常相关,并且在搜索查询中分隔字符(如空格)不会妨碍搜索。全文的缺点 - 1) webhosters 使用停用词作为限制,以防止数据负载过大。(例如,不显示包含单词“one”或“moz”的搜索结果。如果您正在运行自己的服务器,则可以避免这种情况不保留停用词。 2) 如果我输入“ree”,它只会显示包含“ree”而不是“three”或“reed”的单词。

模式匹配的优势 - 1) 它没有全文中的任何停用词,如果您搜索“ree”,它会显示任何包含“ree”的词,例如“reed”或“three”,这与全文只检索确切的词不同。模式匹配的缺点- 1) 如果在您的搜索词中使用了空格等分隔符,并且如果这些空格在结果中不存在,因为每个词都与任何分隔符分开,所以它不返回任何结果。

【讨论】:

【参考方案8】:

如果 LIKE 的参数不以通配符开头,就像在您的示例中一样,LIKE 运算符应该能够take advantage of indexes。

在这种情况下,LIKE 运算符应该比 LOCATE 或 LEFT 执行得更好,所以我怀疑像这样更改条件可能会使事情变得更糟,但我仍然认为值得尝试(谁知道?):

WHERE LOCATE('tanto', `businessunit`.`Name`)=1

或:

WHERE LEFT(`businessunit`.`Name`,5)='tanto'

我还会更改您的 order by 条款:

ORDER BY
  `businessunit`.`Premium` DESC ,
   CASE WHEN `businessunit`.`Name` LIKE 'tanto %' THEN 1
        WHEN `businessunit`.`Name` = 'tanto'      THEN 0
        ELSE 2 END,
   `businessunit`.`Name`

名称必须已经是 LIKE 'tanto%',因此您可以跳过一个条件(CASE 永远不会返回值 3)。当然,请确保 Premium 字段已编入索引。

希望这会有所帮助。

【讨论】:

【参考方案9】:

我认为您只需要收集密钥,对它们进行排序,然后最后加入

SELECT A.*,B.* FROM
(
    SELECT * FROM (
        SELECT id BusinessUnit,Premium
            CASE
                WHEN Name = 'tanto'      THEN 0
                WHEN Name LIKE 'tanto %' THEN 1
                WHEN Name LIKE 'tanto%'  THEN 2
                ELSE 3
            END SortOrder
        FROM businessunit Name LIKE 'tanto%'
    ) AA ORDER BY Premium,SortOrder LIMIT 0,30
) A LEFT JOIN `businessunit-postaddress` B USING (BusinessUnit);

这仍然会生成一个文件排序。

您可能需要考虑在可以索引的单独表中预加载所需的键。

CREATE TABLE BusinessKeys
(
    id int not null auto_increment,
    BusinessUnit int not null,
    Premium      int not null,
    SortOrder    int not null,
    PRIMARY KEY (id),
    KEY OrderIndex (Premuim,SortOrder,BusinessUnit)
);

填充所有匹配的键

INSERT INTO BusinessKeys (BusinessUnit,Premuim,SortOrder)
SELECT id,Premium
    CASE
        WHEN Name = 'tanto'      THEN 0
        WHEN Name LIKE 'tanto %' THEN 1
        WHEN Name LIKE 'tanto%'  THEN 2
        ELSE 3
    END
FROM businessunit Name LIKE 'tanto%';

然后,为了分页,只在 BusinessKeys 上运行 LIMIT

SELECT A.*,B.*
FROM
    (
        SELECT FROM BusinessKeys
        ORDER BY Premium,SortOrder
        LIMIT 0,30
    ) BK
    LEFT JOIN businessunit A ON BK.BusinessUnit = A.id
    LEFT JOIN `businessunit-postaddress` B ON A.BusinessUnit = B.BusinessUnit
;

CAVEAT:我使用LEFT JOIN 而不是INNER JOIN,因为LEFT JOIN 保留了查询左侧的键顺序。

【讨论】:

我无法让第一个查询运行,但我喜欢你的方法,只要我能让它运行,这样我就可以看到它的性能了【参考方案10】:

我已阅读使用 Sphinx 优化搜索的答案。但根据我的经验,我会建议一个不同的解决方案。我们使用 Sphinx 已经好几年了,遇到了一些严重的分段错误和损坏的索引问题。也许 Sphinx 不像几年前那样容易出错,但一年来我们对不同的解决方案感到非常满意:

http://www.elasticsearch.org/

巨大的好处:

可扩展性 - 您可以简单地添加另一个配置几乎为零的服务器。如果你知道 mysql 复制,你会喜欢这个功能 速度 - 即使在重负载下,您也可以在不到一秒的时间内获得良好的结果 简单易学 - 只有了解 HTTP 和 JSON 才能使用。如果您是 Web 开发人员,您会感到宾至如归 易于安装 - 无需接触配置即可使用。您只需要简单的 Java(不需要 Tomcat 或其他)和防火墙来阻止来自公众的直接访问 良好的 javascript 集成 - 即使是类似 phpMyAdmin 的工具也是使用 Javascript 的简单 HTML 页面:https://github.com/mobz/elasticsearch-head 与https://github.com/ruflin/Elastica 的良好 PHP 集成 良好的社区支持 良好的文档(它不利于眼睛,但它几乎涵盖了所有功能!)

如果您需要额外的存储解决方案,您可以轻松地将搜索引擎与http://couchdb.apache.org/结合起来

【讨论】:

以上是关于搜索具有相关性的大型 mysql 数据库的主要内容,如果未能解决你的问题,请参考以下文章

如何操纵 MySQL 全文搜索相关性以使一个字段比另一个字段更“有价值”?

大型网站Mysql分布式集群架构技术详解教程

Mysql全文搜索跨多个表的相关性

mysql全文搜索相关性得分不正确

MySQL全文搜索结果相关性

假期(MySQL相关)