20

我正在为我们公司的 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

4

10 回答 10

19

您应该尝试 sphinx 搜索解决方案,它是全文搜索引擎,它将为您提供非常好的性能以及许多设置相关性的选项。

点击这里了解更多详情。

于 2013-01-28T11:52:51.007 回答
6

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

除了已经建议的狮身人面像,我推荐两个很棒的搜索引擎:

  1. Solrhttp://pecl.php.net/package/solr - 非常流行的搜索引擎。用于像 NetFlix 这样的大规模服务。

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

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

于 2013-02-07T15:41:36.410 回答
6

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

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

于 2013-01-28T11:52:07.060 回答
2

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

步骤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.netdevzone.zend.comsqlmag.comcolorado.eduen.wikipedia.org

于 2013-02-04T07:35:20.850 回答
2

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

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

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

不过,我总是确保我的 SELECT 是脏读(没有事务性应用于选择)。

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

于 2013-02-05T23:39:39.480 回答
1

它可以是全文(http://dev.mysql.com/doc/refman/5.0/en/fulltext-search.html)或模式匹配(http://dev.mysql.com/doc/refman/5.0 /en/pattern-matching.html)来自 php 和 mysql 方面。

从经验和理论:

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

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

于 2013-02-16T05:00:48.500 回答
1

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

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

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

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

过滤查询后,对结果进行排序,并以某种奇怪的方式进行排序。
起初它按字段排序businessunitPremium- 这是正常的。
但是,带有 CASE 的下一个语句也没有用。
这就是为什么。
零分配给 Name = 'tanto' (完全正确)。
下一行是在 'tanto' 之后有空格的行 - 在任何情况下(特殊符号除外)这些将在 'tanto' 之后,因为空格小于任何字母。
接下来的两行是在“tanto”之后带有一些字母的行(包括空格!)。根据定义,这些行也将按此顺序排列。
这三个是为“其他”行“保留”的,但您不会得到“其他”行 - 请记住 [WHERE businessunitName喜欢 '
所以这部分 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

不要忘记 field 上的索引businessunit-postaddressBusinessUnit

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

PS 我不确定你真的需要使用 SQL_CALC_FOUND_ROWS MySQL:分页 - SQL_CALC_FOUND_ROWS vs COUNT()-Query

于 2013-02-09T11:51:39.243 回答
0

如果 LIKE 的参数不以通配符开头,就像在您的示例中一样, LIKE 运算符应该能够利用 index

在这种情况下,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 字段已编入索引。

希望这可以帮助。

于 2013-02-04T11:25:54.050 回答
0

我已经阅读了使用 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/结合起来

于 2013-02-08T17:39:09.283 回答
0

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

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保留了查询左侧的键顺序。

于 2013-02-07T06:08:57.617 回答