对于读取和写入密集型应用程序,有效地在mysql中存储url的最佳方法是什么?
我将存储超过500,000个网址(全部以http://或https://开头,没有其他协议)并保存整个网址(http://example.com/path/?variable=a )进入一列似乎很大程度上是多余的,因为相同的域名和路径将多次保存到mysql。
所以,最初,我想要将它们分解(即域,路径和变量等)以消除冗余。但我看到一些帖子说它不推荐。对此有何想法?
此外,应用程序通常必须检索没有主键的URL,这意味着它必须搜索文本以检索URL。 URL可以被编入索引,但是我想知道如果它们都是在innodb(没有全文索引)下编制索引,那么存储整个url和broken-down-url之间会有多大的性能差异。
细分网址必须经过额外的步骤才能合并它们。此外,这意味着我必须从不同的表(协议,域,路径,变量)中检索数据4次,但它也会使每行中的存储数据更短,并且每个表中的行数会更少。这可能会加快这个过程吗?
答案 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 - 这也可能会给你一些想法: