Perl搜索查找效率

时间:2013-04-30 06:53:34

标签: mysql perl

我有一批网址,我必须在数据库中搜索匹配项,或者更确切地说,如果网址包含数据库中的网址。

网址的一个例子是

http://www.foodandnuts.com/login.html

数据库有一个用url填充的表

目前我的脚本在开头创建了一个包含数据库中所有网址的数组

my $results = $dbh->selectall_hashref('SELECT * FROM urltable;', 'url');
foreach my $j (keys %$results) {
push(@urldb, $j);
}

然后它将遍历数组以查看url是否包含数据库中的url

    foreach(@urldb){
            if($searchedurl=~ /$_/){
#do things here
}}

问题是,由于数组的网址超过10000个,因此非常慢,因此每个搜索到的网址都必须通过该数组。有没有办法让这更快?

3 个答案:

答案 0 :(得分:3)

根据您希望的3种URL匹配中的哪种匹配,可以对问题进行不同的回答:

  1. 完全匹配(字符串相等)。例如。如果数据库网址是" google.com",则搜索字符串" http://google.com"不会匹配,也不会#34; google.com/q=a"。

    在这种情况下,使用regexp删除,只需执行SELECT * FROM urls WHERE url="$search",或者执行哈希查找作为Andreas'回答细节。

  2. DB中的搜索URL和URL都是有效的URL(例如以http://开头),因此必须从字符串的开头开始匹配,但搜索URL可以包含要匹配的DB URL +后缀。例如。如果数据库网址为" http://google.com",则搜索字符串" http://google.com" AND" http://google.com/q=a"匹配。

    在这种情况下,要么做一个开始锚定的RegEx,要么开始锚定" LIKE"数据库匹配 - 请参阅答案的下一部分详细信息。

  3. 任何子字符串匹配。例如。如果数据库网址是"谷歌",那么任何包含" google"的网址字符串在任何地方

    在这种情况下,要么进行单词查找表,要么更智能的子字符串查找algoritrhms;或使用" |"进行批量正则表达式匹配加入多个DB网址。请参阅答案最后部分的详细信息。




  4. 这部分答案假设您在DB中的URL可以是搜索URL的子字符串,但它们都以" http"开头,这意味着它们始终在字符串的开头匹配;但不是完全匹配。


    解决方案1开始锚定匹配(Perl):

    修复您的RegExes锚定在开头:if($searchedurl=~ /^$_/){


    开始锚定匹配(DB)的解决方案2:

    按URL字段索引URL表,并执行(Sybase语法)

    $query = qq[SELECT * FROM urls WHERE url LIKE "$searchurl\%"];
    

    这将对开始锚定的子串进行非常有效的数据库搜索。


    注意:在DB与Perl中进行匹配之间的权衡是:

    • 如果你有1个DB和100个客户端,你不想重做数据库进行字符串匹配。将CPU负载分配到客户端。

    • 如果您只有1-2个客户端,那么数据库可能会更好,因为您将从数据库中的磁盘IO(表中的索引将有所帮助)和通过网络传输更少的数据。




    这部分答案假设您在数据库中的网址可以是搜索网址的完整子字符串,不一定是精确匹配甚至是锚定匹配。


    随机子串匹配(Perl)的解决方案1:

    一种纯粹的Perl方式可以让你更快地将搜索字符串组合成批次:

    • @urldb中的前N个元素拆分为循环

      my $N = 10;
      my $start = 0;
      my $end = $N - 1;
      while ($start < @urldb ) {
          search_with($searchedurl, @urldb[$start..$end]); # see next bullet
          $start += $N;
          $end += $N;
          $end = @urldb if $end > @urldb;
      }
      
    • 对于每个长度为N的数组,使用&#34; |&#34;加入元素。并创建一个正则表达式

      sub search_with {
          my $searchedurl = shift;
          my $regex_string = join("|", @_);
          if ($searchedurl =~ /($regex_string)/) {
              # Do stuff, $1 will contain what matched.
          }
      }
      

    随机子串匹配(DB)的解决方案2:

    另一种算法方法是建立一个&#34;字查找&#34; table(又名索引,但我不想使用术语索引来避免与数据库索引混淆)。

    • 将每个网址拆分为单词。
    • 在数据库中,为URL表添加唯一ID
    • 在数据库中,添加&#34;单词查找&#34;表映射(1到N)URL ID到该URL中的每个单词(每行1个)
    • 使用&#34;字词查找&#34;用于缩小要查询的URL列表的表。
      • 您可以在&#34;字词查找&#34;上使用数据库索引表格非常快。
      • 您当然也需要将搜索网址拆分为单词。
      • 通过从路径单独索引域名字来进一步加快/缩小范围。

    注意:使用简单的&#34; WHERE&#34;如果URLS可以是与第一个字符不匹配的子字符串,那么在数据库中搜索您的URL表是一个非常糟糕的主意 - 这样,您就无法使用和编制索引,只会扫描表格

    NOTE2 :为了更有效地对字符串数组进行子串匹配,有更多基于子串图的高级算法。

    注3:在Perl和DB中进行匹配之间的权衡与答案的前半部分相同。

答案 1 :(得分:1)

@DVK是正确的,如果你可以在开始时锚定比赛通常会更有效率。这样你可以使用标准的btree索引来搜索(MySQL没有PostgreSQL更丰富的索引类型范围)。

我不同意他/她关于在哪里进行匹配。在数据库本身中执行此操作几乎总是有意义的。这就是数据库的用途。

最有效的方式可能是:

  1. 创建一个TEMPORARY TABLE来保存目标网址
  2. 批量将目标插入该临时表
  3. 为它们创建索引(假设索引在这里有帮助)
  4. 使用LIKE匹配从您的主网址表加入目标。
  5. 即使你不能使用索引,数据库也应该比你的perl更快。您正在读取整个表,将原始数据打包到传输协议中,传输它,将其解析为perl值,组装散列然后检查它。假设您的目标网址列表比数据库中的完整列表小得多,那么只需不传输如此多的数据就可以获胜。

答案 2 :(得分:0)

注意:OP要求提供搜索字符串应包含网址的解决方案。我已经改变了我的解决方案,试图规范化网址,以便哈希匹配是完全查找之后 得到这个评论。

此代码未经过测试,它应该作为可能工作的某种形式的伪代码

创建哈希而不是数组。哈希是有序的,更适合作为查找。

my $results = $dbh->selectall_hashref('SELECT * FROM urltable;', 'url');
my %urldb = map { normalize($_) => 1 } keys %$results;

sub normalize {
  my $url = shift;
  $url =~ s|http://||; # strip away http:// if present
  $url =~ s|www\.||;   # strip away www if present
  $url =~ s|/.*||;     # strip away anything after and including /
  return $url;
}

然后你会用

搜索
if (exists($urldb{normalize($searchedurl)})) {
  #do things here
}