Hlavní navigace

PHP okénko: Využití unikátních klíčů v databázi

Jakub Vrána

Začíná PHP okénko, které vám bude přinášet drobné postřehy z programování v PHP, vždy pokud možno s uvedením konkrétních ukázek kódu. První okénko ukazuje, jak lze s využitím unikátních indexů elegantně a korektně ukládat do databáze uživatelská jména.

Pokud má webová aplikace možnost registrace uživatelů, obvykle jim dovoluje zvolit uživatelské jméno, pod kterým se k aplikaci budou přihlašovat. Obvykle také chceme, aby toto uživatelské jméno bylo jednoznačné (obzvláště pokud jim na jeho základě vytvoříme e-mailovou adresu nebo URL). Dá se to jako obvykle řešit několika způsoby.

<?php
// špatný kód
if (mysql_result(mysql_query("SELECT COUNT(*) FROM uzivatele WHERE login = '$_GET[login]'"), 0)) {
    echo "Uživatel již existuje.\n";
} else {
    mysql_query("INSERT INTO uzivatele (login) VALUES ('$_GET[login]')");
}

// opravený kód
mysql_query("LOCK TABLES uzivatele WRITE");
if (mysql_result(mysql_query("SELECT COUNT(*) FROM uzivatele WHERE login = '$_GET[login]'"), 0)) {
    echo "Uživatel již existuje.\n";
} else {
    mysql_query("INSERT INTO uzivatele (login) VALUES ('$_GET[login]')");
}
mysql_query("UNLOCK TABLES");

// elegantní kód, předpokládá existenci unikátního klíče nad sloupcem login
mysql_query("INSERT INTO uzivatele (login) VALUES ('$_GET[login]')");
if (mysql_errno() == 1062) {
    echo "Uživatel již existuje.\n";
}
?> 

Proč je první kód špatně? Důvod je ten, že k webové aplikaci může přistupovat více uživatelů zároveň, tím pádem mezi provedením SELECT a INSERT může tyto dvě operace provést někdo jiný, a uživatel se tak vloží dvakrát. Je to sice spíše teoretická možnost, ale je lepší si ušetřit bezesné noci hledáním takovýchto problémů. Ve většině databází se tento problém řeší transakcemi, MySQL je však umožňuje pouze za určitých okolností a jako náhradu nabízí zamykání tabulek.

To používá druhý příklad, který je už po formální stránce správně, je ale poněkud krkolomný a skrývá riziko neodemčení tabulky (pokud by uživatel přerušil provádění skriptu před odemčením tabulky a pokud se používá persistentní připojení k databázi, tak už si z tabulky uzivatele nikdo nic nepřečte). Toto riziko se dá řešit doplněním kódu register_shutdown_function(create_function('', 'mysql_query("UNLOCK TABLES");')); před zamknutím tabulky, to se už ale krkolomnost dostává na kritickou hranici.

Třetí způsob je naproti tomu elegantní. Předpokládá pouze existenci unikátního indexu nad sloupcem login, ten by ale měl existovat tak jako tak, protože výrazně urychluje dotazy do tabulky omezené tímto sloupcem. MySQL chyba č. 1062 nastane v případě duplicity unikátního klíče.


Podobně laděné texty můžete najít i na autorově weblogu PHP triky.

Našli jste v článku chybu?