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

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次,但它也会使每行中的存储数据更短,并且每个表中的行数会更少.这可能会加快这个过程吗?

raz*_*zed 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;
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)

但我离题了.