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.