在mysql中存储url的最佳方法,用于读取和写入密集型应用程序

时间:2011-02-28 21:39:32

标签: mysql sql database database-design

对于读取和写入密集型应用程序,有效地在mysql中存储url的最佳方法是什么?

我将存储超过500,000个网址(全部以http://或https://开头,没有其他协议)并保存整个网址(http://example.com/path/?variable=a )进入一列似乎很大程度上是多余的,因为相同的域名和路径将多次保存到mysql。

所以,最初,我想要将它们分解(即域,路径和变量等)以消除冗余。但我看到一些帖子说它不推荐。对此有何想法?

此外,应用程序通常必须检索没有主键的URL,这意味着它必须搜索文本以检索URL。 URL可以被编入索引,但是我想知道如果它们都是在innodb(没有全文索引)下编制索引,那么存储整个url和broken-down-url之间会有多大的性能差异。

细分网址必须经过额外的步骤才能合并它们。此外,这意味着我必须从不同的表(协议,域,路径,变量)中检索数据4次,但它也会使每行中的存储数据更短,并且每个表中的行数会更少。这可能会加快这个过程吗?

2 个答案:

答案 0 :(得分:8)

我已经广泛地处理了这个问题,而我的一般理念是使用频率使用方法。这很麻烦,但它可以让您对数据进行一些很好的分析:

CREATE TABLE URL (
   ID            integer unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
   DomainPath    integer unsigned NOT NULL,
   QueryString   text
) Engine=MyISAM;

CREATE TABLE DomainPath (   
   ID            integer unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
   Domain        integer unsigned NOT NULL,
   Path          text,
   UNIQUE (Domain,Path)
) Engine=MyISAM;

CREATE TABLE Domain (   
   ID            integer unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
   Protocol      tinyint NOT NULL,
   Domain        varchar(64)
   Port          smallint NULL,
   UNIQUE (Protocol,Domain,Port)
) Engine=MyISAM;

作为一般规则,您将在单个域上具有类似的路径,但每个路径都有不同的QueryStrings。

我最初将其设计为将所有部分编入索引(协议,域,路径,查询字符串),但认为上述内容占用空间较少,并且可以更好地从中获取更好的数据。

text往往很慢,因此您可以在使用一段时间后将“路径”更改为varchar。对于URL,大多数服务器在大约1K后死亡,但我看到了一些大型服务器,并且在不丢失数据方面会犯错。

您的检索查询很麻烦,但如果您在代码中将其抽象出来,则没有问题:

SELECT CONCAT(
    IF(D.Protocol=0,'http://','https://'),
    D.Domain,
    IF(D.Port IS NULL,'',CONCAT(':',D.Port)), 
    '/', DP.Path, 
    IF(U.QueryString IS NULL,'',CONCAT('?',U.QueryString))
)
FROM URL U
INNER JOIN DomainPath DP ON U.DomainPath=DP.ID
INNER JOIN Domain D on DP.Domain=D.ID
WHERE U.ID=$DesiredID;

如果端口号是非标准的,则存储端口号(对于http为非80,对于https为非443),否则将其存储为NULL以表示不应包含端口号。 (您可以将逻辑添加到MySQL,但它会变得更加丑陋。)

我总是(或从不)从路径中删除“/”以及“?”从QueryString中节省空间。只有损失才能区分

http://www.example.com/
http://www.example.com/?

哪个,如果重要的话,那么我会改变你的大头钉,永远不会剥掉它,只是包括它。从技术上讲,

http://www.example.com 
http://www.example.com/

是相同的,所以剥离路径斜杠总是可以的。

所以,解析:

http://www.example.com/my/path/to/my/file.php?id=412&crsource=google+adwords

我们会在PHP中使用类似parse_url的东西来生成:

array(
    [scheme] => 'http',
    [host] => 'www.example.com',
    [path] => '/my/path/to/my/file.php',
    [query] => 'id=412&crsource=google+adwords',
)

然后检查/插入(使用适当的锁,未显示):

SELECT D.ID FROM Domain D 
WHERE 
    D.Protocol=0 
    AND D.Domain='www.example.com' 
    AND D.Port IS NULL

(如果不存在)

INSERT INTO Domain ( 
    Protocol, Domain, Port 
) VALUES ( 
    0, 'www.example.com', NULL 
);

然后我们将$DomainID继续......

然后插入DomainPath:

SELECT DP.ID FORM DomainPath DP WHERE 
DP.Domain=$DomainID AND Path='/my/path/to/my/file.php';

(如果它不存在,请同样插入)

然后我们将$DomainPathID继续......

SELECT U.ID FROM URL 
WHERE 
    DomainPath=$DomainPathID 
    AND QueryString='id=412&crsource=google+adwords'

并在必要时插入。

现在,让我注意重要,上述方案对于高性能网站来说会很慢。您应该修改所有内容以使用某种哈希来加速SELECT。简而言之,该技术就像:

CREATE TABLE Foo (
     ID integer unsigned PRIMARY KEY NOT NULL AUTO_INCREMENT,
     Hash varbinary(16) NOT NULL,
     Content text
) Type=MyISAM;

SELECT ID FROM Foo WHERE Hash=UNHEX(MD5('id=412&crsource=google+adwords'));

我故意从上面删除它以保持简单,但是将TEXT与另一个TEXT进行比较选择是很慢的,并且打破了很长的查询字符串。不要使用固定长度的索引,因为它也会破坏。对于精度很重要的任意长度字符串,哈希失败率是可以接受的。

最后,如果可以,请执行MD5哈希客户端以保存向服务器发送大blob以执行MD5操作。大多数现代语言都支持MD5内置:

SELECT ID FROM Foo WHERE Hash=UNHEX('82fd4bcf8b686cffe81e937c43b5bfeb');

但我离题了。

答案 1 :(得分:0)

这实际上取决于您想要对数据做什么。如果您使用网址进行统计,例如要了解哪些域名最受欢迎,那么通过它来分解它是可行的。但是,如果您只是存储它们并完整地访问它们,那么就没有理由将它们分开。

我看到有些人散列长字符串(例如md5)并进行搜索,可能会对URL有性能提升,但我不确定多少(对于大量文本更好)。

无论你做什么 - 不要忘记尽可能使用int作为主键,因为那些是最快的查找。

如果你真的想要分割你的网址,你可能需要考虑保留单独的表以便不锁定你的表(在innoDB中它并不重要,因为表没有被锁定)但是使用单独的表,你可以使用foreign / primary_keys / ints来引用你需要的行。

好的阅读是friendfeed's blog entry - 这也可能会给你一些想法: