sql >> Base de Datos >  >> RDS >> Mysql

la mejor manera de almacenar url en mysql para una aplicación intensiva de lectura y escritura

He tratado esto extensamente, y mi filosofía general es usar el método de frecuencia de uso. Es engorroso, pero te permite ejecutar excelentes análisis de los datos:

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;

Como regla general, tendrá rutas similares en un solo dominio, pero cadenas de consulta diferentes para cada ruta.

Originalmente diseñé esto para tener todas las partes indexadas en una sola tabla (Protocolo, Dominio, Ruta, Cadena de consulta), pero creo que lo anterior ocupa menos espacio y se presta mejor para obtener mejores datos.

text tiende a ser lento, por lo que puede cambiar "Ruta" a un varchar después de algún uso. La mayoría de los servidores mueren después de aproximadamente 1K para una URL, pero he visto algunos grandes y cometería el error de no perder datos.

Su consulta de recuperación es engorrosa, pero si la abstrae en su código, no hay problema:

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;

Almacene un número de puerto si no es estándar (no 80 para http, no 443 para https); de lo contrario, guárdelo como NULL para indicar que no debe incluirse. (Puede agregar la lógica a MySQL, pero se vuelve mucho más feo).

Siempre (o nunca) quitaría el "/" de la ruta, así como el "?" de QueryString para ahorrar espacio. Sólo la pérdida sería capaz de distinguir entre

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

Lo cual, si es importante, entonces cambiaría su táctica para nunca quitarlo y simplemente incluirlo. Técnicamente,

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

Son iguales, por lo que quitar la barra de ruta siempre está bien.

Entonces, para analizar:

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

Usaríamos algo como parse_url en PHP para producir:

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

Luego verificaría/insertaría (con los candados apropiados, no se muestra):

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

(si no existe)

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

Luego tenemos nuestro $DomainID adelante...

Luego inserte en DomainPath:

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

(si no existe, insértelo de manera similar)

Entonces tenemos nuestro $DomainPathID adelante...

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

e inserte si es necesario.

Ahora, permítanme señalar importantemente , que el esquema anterior será lento para sitios de alto rendimiento. Debe modificar todo para usar un hash de algún tipo para acelerar SELECT s. En resumen, la técnica es como:

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'));

Lo eliminé deliberadamente de lo anterior para mantenerlo simple, pero comparar un TEXTO con otro TEXTO para selecciones es lento y se rompe para cadenas de consulta realmente largas. Tampoco use un índice de longitud fija porque eso también se romperá. Para cadenas de longitud arbitraria donde la precisión es importante, una tasa de error de hash es aceptable.

Finalmente, si puede, haga el lado del cliente hash MD5 para evitar enviar grandes blobs al servidor para realizar la operación MD5. La mayoría de los lenguajes modernos admiten MD5 integrado:

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

Pero estoy divagando.