Přísně vzato, váš test jedinečnosti nezaručí jedinečnost při současném zatížení. Problém je v tom, že zkontrolujete jedinečnost před (a odděleně od) místa, kam vložíte řádek, abyste „nárokovali“ svůj nově vygenerovaný přístupový kód. Jiný proces by mohl dělat stejnou věc ve stejnou dobu. Tady je návod, jak to jde...
Dva procesy generují přesně stejný přístupový kód. Každý z nich začíná kontrolou jedinečnosti. Vzhledem k tomu, že žádný proces (zatím) nevložil do tabulky řádek, oba procesy nenajdou v databázi žádný odpovídající přístupový kód, takže oba procesy budou předpokládat, že kód je jedinečný. Nyní, když procesy pokračují ve své práci, nakonec budou oba vložte řádek do files
pomocí vygenerovaného kódu -- a tím získáte duplikát.
Chcete-li to obejít, musíte provést kontrolu a provést vložení jedinou "atomovou" operací. Následuje vysvětlení tohoto přístupu:
Pokud chcete, aby byl přístupový kód jedinečný, měli byste definovat sloupec v databázi jako UNIQUE
. To zajistí jedinečnost (i když váš php kód ne) tím, že odmítnete vložit řádek, který by způsobil duplicitní přístupový kód.
CREATE TABLE files (
id int(10) unsigned NOT NULL auto_increment PRIMARY KEY,
filename varchar(255) NOT NULL,
passcode varchar(64) NOT NULL UNIQUE,
)
Nyní použijte mysql SHA1()
a NOW()
vygenerovat váš přístupový kód jako součást vložit příkaz. Zkombinujte to s INSERT IGNORE ...
(dokumenty
) a opakujte, dokud nebude řádek úspěšně vložen:
do {
$query = "INSERT IGNORE INTO files
(filename, passcode) values ('whatever', SHA1(NOW()))";
$res = mysql_query($query);
} while( $res && (0 == mysql_affected_rows()) )
if( !$res ) {
// an error occurred (eg. lost connection, insufficient permissions on table, etc)
// no passcode was generated. handle the error, and either abort or retry.
} else {
// success, unique code was generated and inserted into db.
// you can now do a select to retrieve the generated code (described below)
// or you can proceed with the rest of your program logic.
}
Poznámka: Výše uvedený příklad byl upraven tak, aby odpovídal vynikajícím postřehům zveřejněným @martinstoeckli v sekci komentářů. Byly provedeny následující změny:
- změněno
mysql_num_rows()
(dokumenty ) namysql_affected_rows()
(dokumenty ) -- num_rows se nevztahuje na vložky. Také byl odstraněn argumentmysql_affected_rows()
, protože tato funkce funguje na úrovni připojení, nikoli na úrovni výsledku (a v každém případě je výsledek vložení booleovský, nikoli číslo zdroje). - přidali kontrolu chyb ve stavu smyčky a přidali test chyby/úspěchu po ukončení smyčky. Zpracování chyb je důležité, protože bez něj způsobí chyby databáze (jako ztráta připojení nebo problémy s oprávněními) točení smyčky navždy. Výše uvedený přístup (pomocí
IGNORE
amysql_affected_rows()
a testování$res
samostatně pro chyby) nám umožňuje odlišit tyto "skutečné chyby databáze" od jedinečného porušení omezení (což je zcela platný nechybový stav v této části logiky).
Pokud potřebujete získat přístupový kód po jeho vygenerování, vyberte záznam znovu:
$res = mysql_query("SELECT * FROM files WHERE id=LAST_INSERT_ID()");
$row = mysql_fetch_assoc($res);
$passcode = $row['passcode'];
Upravit :změněn výše uvedený příklad na použití funkce mysql LAST_INSERT_ID()
, spíše než funkce PHP. Toto je efektivnější způsob, jak dosáhnout stejné věci, a výsledný kód je čistší, přehlednější a méně přeplněný.