ap3*_*ap3 3 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次,但它也会使每行中的存储数据更短,并且每个表中的行数会更少.这可能会加快这个过程吗?
我已经广泛地处理了这个问题,我的一般理念是使用频率使用方法.它很麻烦,但它可以让您对数据进行一些很好的分析:
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;
Run Code Online (Sandbox Code Playgroud)
作为一般规则,您将在单个域上具有类似的路径,但每个路径具有不同的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;
Run Code Online (Sandbox Code Playgroud)
存储端口号(如果它是非标准的)(对于http为非80,对于https为非443),否则将其存储为NULL以表示不应包括端口号.(您可以将逻辑添加到MySQL,但它会变得更加丑陋.)
我总是(或永远)从路径中剥离"/"以及"?" 从QueryString中节省空间.只有损失才能区分
http://www.example.com/
http://www.example.com/?
Run Code Online (Sandbox Code Playgroud)
哪个,如果重要,那么我会改变你的大头钉,永远不会剥离它,只是包括它.从技术上讲,
http://www.example.com
http://www.example.com/
Run Code Online (Sandbox Code Playgroud)
是相同的,所以剥离Path斜杠总是可以的.
所以,要解析:
http://www.example.com/my/path/to/my/file.php?id=412&crsource=google+adwords
Run Code Online (Sandbox Code Playgroud)
我们会使用像parse_urlPHP 这样的东西来生成:
array(
[scheme] => 'http',
[host] => 'www.example.com',
[path] => '/my/path/to/my/file.php',
[query] => 'id=412&crsource=google+adwords',
)
Run Code Online (Sandbox Code Playgroud)
然后检查/插入(使用适当的锁,未显示):
SELECT D.ID FROM Domain D
WHERE
D.Protocol=0
AND D.Domain='www.example.com'
AND D.Port IS NULL
Run Code Online (Sandbox Code Playgroud)
(如果不存在)
INSERT INTO Domain (
Protocol, Domain, Port
) VALUES (
0, 'www.example.com', NULL
);
Run Code Online (Sandbox Code Playgroud)
然后我们$DomainID继续前进......
然后插入DomainPath:
SELECT DP.ID FORM DomainPath DP WHERE
DP.Domain=$DomainID AND Path='/my/path/to/my/file.php';
Run Code Online (Sandbox Code Playgroud)
(如果它不存在,请以类似方式插入)
然后我们$DomainPathID继续前进......
SELECT U.ID FROM URL
WHERE
DomainPath=$DomainPathID
AND QueryString='id=412&crsource=google+adwords'
Run Code Online (Sandbox Code Playgroud)
并在必要时插入.
现在,让我重要的是,对于高性能站点,上述方案将会很慢.您应该修改所有内容以使用某种哈希来加速SELECTs.简而言之,该技术如下:
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'));
Run Code Online (Sandbox Code Playgroud)
我故意从上面删除它以保持简单,但是将TEXT与另一个TEXT进行比较选择是很慢的,并且打破了很长的查询字符串.不要使用固定长度的索引,因为它也会破坏.对于精度很重要的任意长度字符串,可以接受散列失败率.
最后,如果可以,请执行MD5哈希客户端以保存向服务器发送大blob以执行MD5操作.大多数现代语言都支持MD5内置:
SELECT ID FROM Foo WHERE Hash=UNHEX('82fd4bcf8b686cffe81e937c43b5bfeb');
Run Code Online (Sandbox Code Playgroud)
但我离题了.