【问题标题】:Optimal LIKE search in SQLSQL 中的最佳 LIKE 搜索
【发布时间】:2010-09-29 12:43:20
【问题描述】:

我有一个零件数据库,我将不断查询报价系统。零件数据库中有 1,400,000 多条记录。用户将开始输入零件编号,他们希望系统能够在几个字符后找到,因此我需要能够进行通配符搜索,例如:

SELECT NeededFields FROM Parts WHERE PartNumber LIKE 'ML%'

我是否可以执行任何优化来尝试从此类查询中获得最高性能?我已经索引了 PartNumber 字段,但我不确定这是否是我能得到的最好的。我也愿意考虑独立于 SQL 索引的数据库中内置的替代索引结构。主键是 Guid,但由于我使用特定的数据结构,我需要它来进行复制。

【问题讨论】:

    标签: sql optimization search


    【解决方案1】:

    我猜你的主键(GUID)可能有一个聚集索引。您可能需要考虑使主键不被聚集。相反,您可以聚集为 PartNumber 创建的索引。 (每个表只能有一个聚集索引)

    您还应该考虑在查询中添加一个 TOP 谓词,以便只返回前 100(左右)行。我在想……如果用户首先输入一个 M,可能会有几十万个匹配项,加载速度会很慢。通过限制行数,您应该会获得更好的性能。

    【讨论】:

    • 查看执行计划,似乎没有集群。 (ShowPlan XML Statistics 配置文件显示“索引搜索”而不是“集群...”。)您知道是什么决定了这一点吗? SQL是根据记录数还是其他什么来确定的?我正在使用 TOP,但它是计划中的最后一个。
    • 哦,等等……那是 PartNumber 索引,上面写着“索引搜索”我实际上并没有在该表上看到任何索引搜索 - 而是显示“键查找”。
    • 主键通过索引强制执行。默认情况下,SQL Server 会聚集主键索引。通过“取消聚类”,并在 PartNumber 列上对索引进行聚类,您可能会在性能上获得一些改进。没有实际尝试,不可能猜到多少。
    【解决方案2】:

    大多数(好的)优化器都会尝试为没有首先出现通配符的 LIKE 子句使用索引。如果模式以通配符开头,那么他们能做的就更少了。

    如果索引是 B-Tree 索引,而不是哈希索引(ISAM 系统通常使用 B-Trees),那么子句的前导字符可以用来约束索引搜索。如果系统使用哈希索引,那么您将无法轻松处理部分字符串,除非您在第一个字符上创建单独的索引,然后在前两个字符上创建单独的索引,然后是前三个字符,...。 ISAM 系统可能会为您提供这种灵活性;大多数 SQL 系统不这样做,您必须创建包含零件编号字段的前 1、2、3 ... 个字符的 1、2、3、... 个字符的列。

    添加:评论询问“哪个 DBMS?”,这是公平的。我可以保证您可以使用的任何版本的 IBM Informix Dynamic Server (IDS) 和 Standard Engine (SE)。我希望 IBM DB2(LUW 或 z/OS)都能做到。我希望甲骨文能够做到这一点。评论表明 PostgreSQL 8.0 及更高版本可以做到这一点 - 需要注意。对于 Sybase、Ingres、MS SQL Server、Firebird 或 MySQL,我无法回答我自己的知识。每个 DBMS 可能都有关于何时可以使用索引的警告。

    请注意,如果存在另一个提供选择性的索引,则可以优先使用该索引,而不是提供通配符搜索访问权限的索引。

    【讨论】:

    • 您能提到具体的 DBMS 引擎吗?我用 PostgreSQL 试过了,它似乎没有做这个优化。
    • PostgreSQL 从 8.0 开始会这样做,但仅限于 btree 索引、锚定正则表达式(以 ^ 开头)或开头没有通配符的 LIKE 子句,以及“C”语言环境。
    • 在我用过的几乎所有 SQL RDBMS(大约 8 个不同的数据库,包括最流行的数据库)中,仅在 PartNumber 列上的索引将用于像这样的部分匹配。
    • 不确定其他数据库,但是MS SQL即使在字段中间搜索时也可以优化LIKE。 sqlservercentral.com/blogs/dwainsql/2014/03/26/…
    【解决方案3】:

    这个查询看起来不错!如果该字段已编入索引并且您正在执行LIKE 'term%' 查询(通配符位于末尾),您应该获得优化的执行计划。

    根据您的 DBMS,您可以检查优化器对 EXPLAIN 关键字的实际作用。

    【讨论】:

      【解决方案4】:

      如何在 partnumber 字段上对表进行分区。您可以将表拆分为不同的卷。

      卷 A 持有 a-m
      卷 B 持有 n-z

      EDIT 顺便说一句,从来没有这样做过。

      理论见这个http://msdn.microsoft.com/en-us/library/ms345146.aspx

      【讨论】:

      • 听起来很有趣,但我对此了解不多……究竟有什么好处?它会运行两个并行查询吗?这需要一个多处理器盒吗?在它变得低效之前我可以拆分多少个分区?任何指向我可以阅读的网络资源的指针?
      • 方式过早的优化。首先使用硬件解决方案 - 并推迟这些解决方案,直到您对实际性能有更多了解。
      • le dorfier - 我同意这可能为时过早。
      • 如果您稍微考虑一下您的结构(确定一个合适的数字或日期字段),您可以稍后改进分区。
      • 在这种情况下,零件清单似乎是一个不错的选择 - 假设它取决于表中的值
      【解决方案5】:

      通过使用零件编号的前 2 或 3 个字符对表进行分区来进行实验。 尝试分区本地索引与全局索引。

      【讨论】:

        【解决方案6】:

        我会使用全文搜索。使用这种查询,您的结果几乎是即时的。

        【讨论】:

          【解决方案7】:

          我很好奇,

          您能否扩展您的问题以包括以下 4 个查询的持续时间:

          SELECT top 100 NeededFields FROM Parts WHERE PartNumber LIKE '%'
          SELECT top 100 NeededFields FROM Parts WHERE PartNumber LIKE 'M%'
          SELECT top 100 NeededFields FROM Parts WHERE PartNumber LIKE 'ML%'
          SELECT top 100 NeededFields FROM Parts WHERE PartNumber LIKE 'ML0833%'
          

          如果结果是第一个/第二个查询比最后一个慢很多,您可以考虑引入一个缓存表来维护这些情况(使用触发器或作业更新它)

          另外,我想我刚刚注意到一些事情,您的索引是否已完全覆盖?如果不是,您可能会在结果计数达到阈值时进行表扫描。

          【讨论】:

            【解决方案8】:

            PostgreSQL 在通配符位于末尾时似乎无法优化 LIKE 查询。

            这里,Zones 表是根据列名索引的:

            CREATE INDEX index_zones_name ON Zones(name);
            

            并且索引用于=查询:

            EXPLAIN SELECT id FROM Zones WHERE name = 'toto.fr';
                                          QUERY PLAN                                   
            -------------------------------------------------------------------------------
             Index Scan using index_zones_name on zones  (cost=0.00..21.06 rows=4 width=4)
               Index Cond: (name = 'toto.fr'::text)
            

            查询是即时的,尽管有 300 万行。

            但是,对于 LIKE 查询:

            EXPLAIN SELECT id FROM Zones WHERE name LIKE 'toto%';
                               QUERY PLAN                        
            ---------------------------------------------------------
             Seq Scan on zones  (cost=0.00..75991.43 rows=1 width=4)
               Filter: (name ~~ 'toto%'::text)
            

            而且查询需要更多更长的时间。

            【讨论】:

              【解决方案9】:

              考虑不为此使用 SQL。

              创建一些用户界面所需的数据转储。一百万条记录不是很多-考虑制作26文件-每个“首字母”一个并存储信息。让它包含最受欢迎的条目。

              如果您想要更多的特异性(或用户开始滚动),您可以为每个剩余记录制作26*26 更多文件(ABACAD 等)。

              如果您坚持为此使用 SQL,请对其进行分析。尝试为只为前导字符创建一个索引,例如

              CREATE INDEX partno_idx ON parts (SUBSTRING(partnumber,0,1))
              

              (或任何您的本地 SQL 方言)然后使用类似 WHERE SUBSTRING(partnumber,0,1)='M' 的查询。结果将避免前缀搜索很少比全字符串(或整数)索引(可能使用哈希表或跳过列表)快。

              分析这一点很重要:也许将字符转换为其 ASCII 代码会更快。您的数据集以及您的 SQL Server 软件和版本将高度相关。

              【讨论】:

                【解决方案10】:

                如果您使用的是mysql,请考虑创建部分索引:

                mysql> CREATE INDEX part_of_name ON customer (name(10));
                

                索引只会匹配前 10 个字符

                【讨论】:

                  猜你喜欢
                  • 2013-09-08
                  • 1970-01-01
                  • 1970-01-01
                  • 2010-10-03
                  • 1970-01-01
                  • 2011-10-03
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多