针对大型数据集的流程优化

时间:2010-08-27 16:07:09

标签: php sql perl oracle optimization

我目前有一个项目,我们正在为PPC广告处理3000万+关键字。我们在Oracle中维护这些列表。有时我们需要从列表中删除某些关键字。该流程包括各种匹配类型策略,以确定是否应删除关键字:

  • 确认WHERE keyword = '{term}'
  • 包含WHERE keyword LIKE '%{term}%'
  • TOKEN WHERE keyword LIKE '% {term} %' OR keyword LIKE '{term} %' OR keyword LIKE '% {term}'

现在,处理列表时,它只能使用上面列出的匹配类型之一。但是,必须扫描所有30mil +关键字以进行匹配,并返回匹配的结果。目前,此流程可能需要数小时/天才能处理,具体取决于要搜索的关键字列表中的关键字数量。

您对如何优化流程有任何建议,以便更快地运行吗?

更新 以下是搜索Holiday Inn的示例查询:

SELECT * FROM keyword_list 
WHERE
(
lower(text) LIKE 'holiday inn' OR
lower(text) LIKE '% holiday inn %' OR
lower(text) LIKE 'holiday inn %'
);

以下是EXPLAIN输出的pastebin:http://pastebin.com/tk74uhP4

可能有用的一些其他信息。关键字可以包含多个单词,如:

  • 这是一个示例关键字
  • 我喜欢我的关键字
  • 关键字很棒

7 个答案:

答案 0 :(得分:5)

永远不要使用以“%”开头的LIKE匹配大数据集 - 它不能在该字段上使用表索引并执行表扫描。这是你缓慢的原因。

可以使用索引的唯一匹配是以硬编码字符串开头的匹配(例如keyword LIKE '{term} %')。

要解决此问题,请创建一个新的索引表(不要与数据库的表索引混淆)将单个术语映射到包含这些术语的关键字字符串;然后您的keyword LIKE '% {term} %'变为t1.keyword = index_table.keyword and index_table.term="{term}"

答案 1 :(得分:3)

我知道我的方法看起来像RDBMS家伙的异端,但我在实践中多次验证并且没有魔法。人们应该对可能的IO和处理速率以及一些简单的计算有所了解。简而言之,RDBMS不适合进行此类处理。

从我的经验来看,perl能够以大约每秒百万的速度进行正则表达式扫描。我不知道你有多快能从数据库中转储它(MySQL可以达到200krows / s所以你可以在2.5分钟内转储所有关键字,我知道Oracle在这里要糟糕得多,但我希望它不超过10时间即25分钟)。如果您的数据平均为20个字符,则转储将为600MB,对于100个字符,则为3GB。这意味着,对于速度为100MB / s的HD,您的IO将需要6s到30s。 (所有涉及的IO都是顺序的!)与perl中的转储和处理时间相比几乎没有任何意义。您的扫描速度可能会降低到100k / s,具体取决于您要删除的关键字数量(我已经体验过具有此速度的500分支模式的正则表达式),因此您可以在不到5分钟的时间内处理结果数据。如果产生的基数不会很大(数百个)输出IO应该不是问题。无论如何,你的处理应该是几分钟,而不是几小时。如果您为删除生成整个关键字值,则可以在删除操作中使用索引,因此您将生成一系列填充了关键字的DELETE FROM <table> WHERE keyword IN (...),以便删除最多SQL语句的最大长度。您还可以尝试使用variant将上传此数据到临时表,然后使用join。我不知道甲骨文会更快。在MySQL中大约需要10分钟。你不幸的是你必须处理Oracle,但是你应该能在不到一小时的时间内删除数百个{term}

PS:我建议你使用更好的正则表达式,如http://code.google.com/p/re2/(包含在V8,又名node.js)或Erlang R14A中的新二进制模块,但perl中的弱regexp引擎不会是弱点这个任务,它将是RDBMS。

答案 2 :(得分:2)

该信息不足以提供任何具体建议。如果昂贵的LIKE匹配是不可避免的,那么我目前唯一看到的是:

  

目前,此流程可能需要数小时/天才能处理,具体取决于要搜索的关键字列表中的关键字数量。

您是否尝试在表格中缓存查询结果?由input关键字键入?

因为我不相信整个数据集,所有关键字都可以在一夜之间改变。由于它们不经常更改,因此将结果简单地保存在预先计算的额外表中是有意义的,以便关键字的未来查询可以通过缓存解决,而不是再次通过30Mil条目。显然,必须在缓存表上进行某种定期维护:当修改/删除关键字时以及修改列表时,必须重新计算缓存条目的更新。为了简化更新,我们还会在缓存表中保留keyword_list表中原始行的ID,这会对结果产生影响。


UPDATE :将数据插入已经低级的keyword_list表中。如果以后需要原始案例,请使用额外的行。


过去我参与了一个广告系统的设计。我不记得所有的细节,但最显着的区别是我们对所有内容进行了标记,并将每个独特的单词都赋予了id。关键字不是自由格式 - 它们也在数据库表中,也被标记化。所以我们实际上从未将关键字与字符串匹配:查询类似于:

select AD.id
from DICT, AD
where 
  DICT.word = :input_word and
  DICT.word_id = AD.word_id

DICT是一个包含字词AD(与keyword_list类似)的表格,其中包含广告中的字词。

基本上可以将您遇到的问题概括为"full table scan"。这是非常常见的问题,通常突出显示数据布局设计不佳。搜索网络以获取有关可以执行的操作的更多信息。 SO has many entries too

答案 3 :(得分:2)

我认为问题在于如何存储关键字。如果我正确地解释您的代码,KEYWORD列由一串空白分隔的关键字值组成,例如

KEYWORD1 KEYWORD2 KEYWORD3

因此你被迫使用LIKE进行搜索,这可能是缓慢的原因。

虽然我意识到这可能有些痛苦,但最好创建第二个表,可能称为KEYWORDS,它将包含与给定基表记录相关的各个关键字(我将引用基表作为PPC,因为我不知道它真正叫什么)。假设您当前的基表如下所示:

CREATE TABLE PPC
 (ID_PPC       NUMBER PRIMARY KEY,
  KEYWORD      VARCHAR2(1000),
  <other fields>...);

您可以做的是重建表格如下:

CREATE TABLE NEW_PPC
 (ID_PPC       NUMBER PRIMARY KEY,
  <other fields>...);

CREATE TABLE NEW_PPC_KEYWORD
 (ID_NEW_PPC       NUMBER,
  KEYWORD      VARCHAR2(25),  -- or whatever is appropriate for a single keyword
  PRIMARY KEY (ID_NEW_PPC, KEYWORD));

CREATE INDEX NEW_PPC_KEYWORD_1
  ON NEW_PPC_KEYWORD(KEYWORD);

您可以通过从旧的PPC.KEYWORD字段中提取单个关键字来填充NEW_PPC_KEYWORD表,并将它们放入NEW_PPC_KEYWORD表中。在NEW_PPC_KEYWORD中每个记录中只有一个关键字,您现在可以使用一个简单的连接来拉取NEW_PPC中的所有记录,这些记录通过执行类似

的操作来获取关键字
SELECT P.*
  FROM NEW_PPC P
INNER JOIN NEW_PPC_KEYWORD K
  ON (K.ID_NEW_PPC = P.ID_NEW_PPC)
WHERE K.KEYWORD = '<whatever>';

分享并享受。

答案 4 :(得分:2)

你的解释计划说这个查询应该花一点时间,但它实际上需要几个小时?在我的家用PC上进行简单的测试就可以验证该查询的分钟是否合理。在具有一些不错的IO的服务器上,这可能只需要几秒钟。

对于不同的关键字,问题是您是否按顺序运行相同的查询数十次?如果是这样,您需要将所有搜索组合在一起,这样您只需扫描一次表。

答案 5 :(得分:1)

您可以查看Oracle Text索引。它旨在支持您正在谈论的那种文本内搜索。

答案 6 :(得分:1)

我的建议是将缓存大小提高到数百GB。扔硬件。如果您无法构建Beowulf集群或构建binAry空间搜索引擎。