Mysql
 sql >> Database >  >> RDS >> Mysql

il modo migliore per archiviare l'URL in mysql per un'applicazione ad alta intensità di lettura e scrittura

Mi sono occupato di questo ampiamente e la mia filosofia generale è quella di utilizzare il metodo della frequenza d'uso. È ingombrante, ma ti consente di eseguire ottime analisi sui dati:

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;

Come regola generale, avrai percorsi simili su un singolo dominio, ma QueryStrings differenti per ogni percorso.

Inizialmente l'ho progettato per avere tutte le parti indicizzate in un'unica tabella (protocollo, dominio, percorso, stringa di query), ma penso che quanto sopra sia meno dispendioso in termini di spazio e si presta meglio a ottenere dati migliori da esso.

text tende ad essere lento, quindi puoi cambiare "Percorso" in un varchar dopo un po' di utilizzo. La maggior parte dei server muore dopo circa 1K per un URL, ma ne ho visti alcuni di grandi dimensioni e sbaglierei per non perdere dati.

La tua query di recupero è ingombrante, ma se la astrai nel tuo codice, nessun 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;

Memorizza un numero di porta se non è standard (non 80 per http, non 443 per https), altrimenti memorizzalo come NULL per indicare che non dovrebbe essere incluso. (Puoi aggiungere la logica a MySQL ma diventa molto più brutto.)

Toglierei sempre (o mai) la "/" dal Percorso così come la "?" da QueryString per risparmiare spazio. Solo la perdita sarebbe in grado di distinguere tra

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

Il che, se importante, cambierei il tuo approccio per non rimuoverlo mai e includerlo. Tecnicamente,

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

Sono gli stessi, quindi rimuovere la barra del percorso è sempre OK.

Quindi, per analizzare:

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

Useremmo qualcosa come parse_url in PHP per produrre:

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

Dovresti quindi controllare/inserire (con i blocchi appropriati, non mostrati):

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

(se non esiste)

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

Abbiamo quindi il nostro $DomainID andando avanti...

Quindi inserisci in DomainPath:

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

(se non esiste, inserirlo allo stesso modo)

Abbiamo quindi il nostro $DomainPathID andando avanti...

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

e inserirlo se necessario.

Ora, lasciatemi notare importante , che lo schema di cui sopra sarà lento per i siti ad alte prestazioni. Dovresti modificare tutto per usare un hash di qualche tipo per velocizzare SELECT S. In breve, la tecnica è come:

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

L'ho deliberatamente eliminato da quanto sopra per mantenerlo semplice, ma confrontare un TESTO con un altro TESTO per le selezioni è lento e si interrompe per stringhe di query molto lunghe. Non utilizzare nemmeno un indice a lunghezza fissa perché anche quello si romperà. Per stringhe di lunghezza arbitraria in cui la precisione è importante, è accettabile un tasso di errore dell'hash.

Infine, se puoi, esegui il lato client hash MD5 per salvare l'invio di BLOB di grandi dimensioni al server per eseguire l'operazione MD5. La maggior parte delle lingue moderne supporta MD5 integrato:

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

Ma sto divagando.