web-dev-qa-db-de.com

Was ist die beste Kollatierung für MySQL mit PHP?

Ich frage mich, ob es in MySQL für eine allgemeine Website, auf der Sie sich nicht hundertprozentig sicher sind, was eingegeben wird, die beste Auswahl für die Sortierung gibt. Ich verstehe, dass alle Kodierungen gleich sein sollten, wie MySQL, Apache, HTML und alles in PHP.

In der Vergangenheit habe ich PHP so eingestellt, dass es in "UTF-8" ausgegeben wird. Doch mit welcher Kollatierung stimmt dies in MySQL überein? Ich denke, es ist einer der UTF-8, aber ich habe schon einmal utf8_unicode_ci, utf8_general_ci und utf8_bin verwendet.

700
Darryl Hein

Der Hauptunterschied besteht in der Sortiergenauigkeit (beim Vergleichen von Zeichen in der Sprache) und der Leistung. Die einzige Besonderheit ist utf8_bin, mit der Zeichen im Binärformat verglichen werden können.

utf8_general_ci ist etwas schneller als utf8_unicode_ci, aber weniger genau (zum Sortieren). Die spezifische Sprach-UTF8-Codierung (wie z. B. utf8_swedish_ci) enthält zusätzliche Sprachregeln, mit denen sie für diese Sprachen am genauesten sortiert werden können. Meistens verwende ich utf8_unicode_ci (ich bevorzuge Genauigkeit gegenüber kleinen Leistungsverbesserungen), es sei denn, ich habe einen guten Grund, eine bestimmte Sprache zu bevorzugen.

Weitere Informationen zu bestimmten Unicode-Zeichensätzen finden Sie im MySQL-Handbuch - http://dev.mysql.com/doc/refman/5.0/en/charset-unicode-sets.html

588
Eran Galperin

Seien Sie sich dieses Problems, das bei der Verwendung von utf8_general_ci auftreten kann, sehr bewusst.

MySQL unterscheidet in select-Anweisungen nicht zwischen einigen Zeichen, wenn die Sortierung utf8_general_ci verwendet wird. Dies kann zu sehr bösen Fehlern führen - insbesondere, wenn es sich um Benutzernamen handelt. Abhängig von der Implementierung, die die Datenbanktabellen verwendet, kann dieses Problem dazu führen, dass böswillige Benutzer einen Benutzernamen erstellen, der mit einem Administratorkonto übereinstimmt.

Dieses Problem tritt zumindest in früheren 5.x-Versionen auf - ich bin mir nicht sicher, ob sich dieses Verhalten später geändert hat.

Ich bin kein Datenbankadministrator, aber um dieses Problem zu vermeiden, gehe ich immer mit utf8-bin vor, anstatt die Groß- und Kleinschreibung zu berücksichtigen.

Das folgende Skript beschreibt das Problem anhand eines Beispiels.

-- first, create a sandbox to play in
CREATE DATABASE `sandbox`;
use `sandbox`;

-- next, make sure that your client connection is of the same 
-- character/collate type as the one we're going to test next:
charset utf8 collate utf8_general_ci

-- now, create the table and fill it with values
CREATE TABLE `test` (`key` VARCHAR(16), `value` VARCHAR(16) )
    CHARACTER SET utf8 COLLATE utf8_general_ci;

INSERT INTO `test` VALUES ('Key ONE', 'value'), ('Key TWO', 'valúe');

-- (verify)
SELECT * FROM `test`;

-- now, expose the problem/bug:
SELECT * FROM test WHERE `value` = 'value';

--
-- Note that we get BOTH keys here! MySQLs UTF8 collates that are 
-- case insensitive (ending with _ci) do not distinguish between 
-- both values!
--
-- collate 'utf8_bin' doesn't have this problem, as I'll show next:
--

-- first, reset the client connection charset/collate type
charset utf8 collate utf8_bin

-- next, convert the values that we've previously inserted in the table
ALTER TABLE `test` CONVERT TO CHARACTER SET utf8 COLLATE utf8_bin;

-- now, re-check for the bug
SELECT * FROM test WHERE `value` = 'value';

--
-- Note that we get just one key now, as you'd expect.
--
-- This problem appears to be specific to utf8. Next, I'll try to 
-- do the same with the 'latin1' charset:
--

-- first, reset the client connection charset/collate type
charset latin1 collate latin1_general_ci

-- next, convert the values that we've previously inserted
-- in the table
ALTER TABLE `test` CONVERT TO CHARACTER SET latin1 COLLATE latin1_general_ci;

-- now, re-check for the bug
SELECT * FROM test WHERE `value` = 'value';

--
-- Again, only one key is returned (expected). This shows 
-- that the problem with utf8/utf8_generic_ci isn't present 
-- in latin1/latin1_general_ci
--
-- To complete the example, I'll check with the binary collate
-- of latin1 as well:

-- first, reset the client connection charset/collate type
charset latin1 collate latin1_bin

-- next, convert the values that we've previously inserted in the table
ALTER TABLE `test` CONVERT TO CHARACTER SET latin1 COLLATE latin1_bin;

-- now, re-check for the bug
SELECT * FROM test WHERE `value` = 'value';

--
-- Again, only one key is returned (expected).
--
-- Finally, I'll re-introduce the problem in the exact same 
-- way (for any sceptics out there):

-- first, reset the client connection charset/collate type
charset utf8 collate utf8_generic_ci

-- next, convert the values that we've previously inserted in the table
ALTER TABLE `test` CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;

-- now, re-check for the problem/bug
SELECT * FROM test WHERE `value` = 'value';

--
-- Two keys.
--

DROP DATABASE sandbox;
117
Guus

Eigentlich möchten Sie wahrscheinlich utf8_unicode_ci oder utf8_general_ci verwenden.

  • utf8_general_ci sortiert, indem alle Akzente entfernt und sortiert werden, als wäre es ASCII
  • utf8_unicode_ci verwendet die Unicode-Sortierreihenfolge, sodass mehr Sprachen korrekt sortiert werden

Wenn Sie dies jedoch nur zum Speichern von englischem Text verwenden, sollten sich diese nicht unterscheiden.

113
Vegard Larsen

Verwenden Sie am besten den Zeichensatz utf8mb4 mit der Kollatierung utf8mb4_unicode_ci.

Der Zeichensatz utf8 unterstützt nur eine geringe Anzahl von UTF-8-Codepunkten, etwa 6% der möglichen Zeichen. utf8 unterstützt nur das Basic Multilingual Plane (BMP). Es gibt 16 andere Flugzeuge. Jede Ebene enthält 65.536 Zeichen. utf8mb4 unterstützt alle 17 Flugzeuge.

MySQL schneidet 4-Byte-UTF-8-Zeichen ab, was zu fehlerhaften Daten führt.

Der Zeichensatz utf8mb4 wurde am 24.03.2010 in MySQL 5.5.3 eingeführt.

Einige der erforderlichen Änderungen zur Verwendung des neuen Zeichensatzes sind nicht trivial:

  • Möglicherweise müssen Änderungen an Ihrem Anwendungsdatenbankadapter vorgenommen werden.
  • Es müssen Änderungen an my.cnf vorgenommen werden, einschließlich der Einstellung des Zeichensatzes, der Sortierung und der Umstellung von innodb_file_format auf Barracuda
  • SQL CREATE-Anweisungen müssen möglicherweise Folgendes enthalten: ROW_FORMAT=DYNAMIC
    • DYNAMIC wird für Indizes für VARCHAR (192) und höher benötigt.

HINWEIS: Wenn Sie von Barracuda zu Antelope wechseln, muss der MySQL-Dienst möglicherweise mehrmals neu gestartet werden. innodb_file_format_max ändert sich erst nach dem Neustart des MySQL-Dienstes zu: innodb_file_format = barracuda.

MySQL verwendet das alte InnoDB-Dateiformat Antelope. Barracuda unterstützt dynamische Zeilenformate, die Sie benötigen, wenn Sie die SQL-Fehler beim Erstellen von Indizes und Schlüsseln nach dem Wechseln zum Zeichensatz nicht überschreiten möchten: utf8mb4

  • # 1709 - Indexspaltengröße zu groß. Die maximale Spaltengröße beträgt 767 Byte.
  • # 1071 - Angegebener Schlüssel war zu lang; Die maximale Schlüssellänge beträgt 767 Byte

Das folgende Szenario wurde unter MySQL 5.6.17 getestet: Standardmäßig ist MySQL folgendermaßen konfiguriert:

SHOW VARIABLES;

innodb_large_prefix = OFF
innodb_file_format = Antelope

Beenden Sie Ihren MySQL-Dienst und fügen Sie die Optionen zu Ihrer vorhandenen my.cnf hinzu:

[client]
default-character-set= utf8mb4

[mysqld]
explicit_defaults_for_timestamp = true
innodb_large_prefix = true
innodb_file_format = barracuda
innodb_file_format_max = barracuda
innodb_file_per_table = true

# Character collation
character_set_server=utf8mb4
collation_server=utf8mb4_unicode_ci

Beispiel für eine SQL-Anweisung CREATE:

CREATE TABLE Contacts (
 id INT AUTO_INCREMENT NOT NULL,
 ownerId INT DEFAULT NULL,
 created timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
 modified timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
 contact VARCHAR(640) NOT NULL,
 prefix VARCHAR(128) NOT NULL,
 first VARCHAR(128) NOT NULL,
 middle VARCHAR(128) NOT NULL,
 last VARCHAR(128) NOT NULL,
 suffix VARCHAR(128) NOT NULL,
 notes MEDIUMTEXT NOT NULL,
 INDEX IDX_CA367725E05EFD25 (ownerId),
 INDEX created (created),
 INDEX modified_idx (modified),
 INDEX contact_idx (contact),
 PRIMARY KEY(id)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB ROW_FORMAT=DYNAMIC;
  • Sie können den Fehler # 1709 sehen, der für INDEX contact_idx (contact) generiert wurde, wenn ROW_FORMAT=DYNAMIC aus der CREATE-Anweisung entfernt wurde.

HINWEIS: Durch Ändern des Index auf die ersten 128 Zeichen in contact wird die Verwendung von Barracuda mit ROW_FORMAT=DYNAMIC überflüssig.

INDEX contact_idx (contact(128)),

Beachten Sie auch: Wenn die Größe des Feldes VARCHAR(128) lautet, sind das nicht 128 Bytes. Sie können 128, 4-Byte-Zeichen oder 128, 1-Byte-Zeichen verwenden.

Diese INSERT -Anweisung sollte das 4-Byte-Zeichen "poo" in der 2-Zeile enthalten:

INSERT INTO `Contacts` (`id`, `ownerId`, `created`, `modified`, `contact`, `prefix`, `first`, `middle`, `last`, `suffix`, `notes`) VALUES
(1, NULL, '0000-00-00 00:00:00', '2014-08-25 03:00:36', '1234567890', '12345678901234567890', '1234567890123456789012345678901234567890', '1234567890123456789012345678901234567890', '12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678', '', ''),
(2, NULL, '0000-00-00 00:00:00', '2014-08-25 03:05:57', 'poo', '12345678901234567890', '????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????', '????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????', '????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????', '', ''),
(3, NULL, '0000-00-00 00:00:00', '2014-08-25 03:05:57', 'poo', '12345678901234567890', '????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????', '????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????', '123????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????', '', '');

Sie können den von der Spalte last belegten Speicherplatz anzeigen:

mysql> SELECT BIT_LENGTH(`last`), CHAR_LENGTH(`last`) FROM `Contacts`;
+--------------------+---------------------+
| BIT_LENGTH(`last`) | CHAR_LENGTH(`last`) |
+--------------------+---------------------+
|               1024 |                 128 | -- All characters are ASCII
|               4096 |                 128 | -- All characters are 4 bytes
|               4024 |                 128 | -- 3 characters are ASCII, 125 are 4 bytes
+--------------------+---------------------+

In Ihrem Datenbankadapter möchten Sie möglicherweise den Zeichensatz und die Sortierung für Ihre Verbindung festlegen:

SET NAMES 'utf8mb4' COLLATE 'utf8mb4_unicode_ci'

In PHP würde dies gesetzt für: \PDO::MYSQL_ATTR_INIT_COMMAND

Verweise:

81

Kollatierungen wirken sich darauf aus, wie Daten sortiert und wie Zeichenfolgen miteinander verglichen werden. Das bedeutet, dass Sie die Sortierung verwenden sollten, die die meisten Benutzer erwarten.

Beispiel aus der Dokumentation :

utf8_general_ci ist auch für Deutsch und Französisch zufriedenstellend, mit der Ausnahme, dass "ß" gleich "s" und nicht "ss" ist. Wenn dies für Ihre Anwendung akzeptabel ist, sollten Sie utf8_general_ci verwenden, da dies schneller ist. Verwenden Sie andernfalls utf8_unicode_ci, da dies genauer ist.

Also - es hängt von Ihrer erwarteten Benutzerbasis ab und davon, wie viel Sie benötigen , um richtig zu sortieren . Für eine englische Benutzerbasis sollte utf8_general_ci ausreichen, für andere Sprachen wie Schwedisch wurden spezielle Kollatierungen erstellt.

43
Tomalak

Im Wesentlichen kommt es darauf an, wie Sie sich eine Zeichenfolge vorstellen.

Ich benutze immer utf8_bin wegen des von Guus hervorgehobenen Problems. Meiner Meinung nach ist eine Zeichenfolge für die Datenbank immer noch nur eine Zeichenfolge. Eine Zeichenfolge besteht aus einer Anzahl von UTF-8-Zeichen. Ein Charakter hat eine binäre Darstellung. Warum muss er die Sprache kennen, die Sie verwenden? In der Regel werden Datenbanken für Systeme mit mehrsprachigen Websites erstellt. Dies ist der springende Punkt bei der Verwendung von UTF-8 als Zeichensatz. Ich bin ein bisschen puristisch, aber ich denke, dass das Fehlerrisiko den geringen Vorteil, den Sie bei der Indizierung haben könnten, stark überwiegt. Alle sprachbezogenen Regeln sollten auf einer viel höheren Ebene als das DBMS durchgeführt werden.

In meinen Büchern sollte "Wert" niemals in einer Million Jahren gleich "Wert" sein.

Wenn ich ein Textfeld speichern und nach Groß- und Kleinschreibung suchen möchte, verwende ich MYSQL-Zeichenfolgenfunktionen mit PHP -Funktionen wie LOWER () und der PHP-Funktion strtolower ().

22
Phil

Für UTF-8-Textinformationen sollten Sie utf8_general_ci verwenden, weil ...

  • utf8_bin: vergleicht Zeichenfolgen mit dem Binärwert jedes Zeichens in der Zeichenfolge

  • utf8_general_ci: Zeichenfolgen anhand allgemeiner Sprachregeln und unter Verwendung von Vergleichen ohne Berücksichtigung der Groß-/Kleinschreibung vergleichen

a.k.a. sollte das Durchsuchen und Indizieren der Daten schneller/effizienter/nützlicher machen.

12
mepcotterell

Die akzeptierte Antwort schlägt ziemlich definitiv die Verwendung von utf8_unicode_ci vor, und obwohl das für neue Projekte großartig ist, wollte ich meine jüngsten gegenteiligen Erfahrungen erzählen, nur für den Fall, dass es jemandem Zeit spart.

Da utf8_general_ci die Standardkollatierung für Unicode in MySQL ist, müssen Sie utf8_unicode_ci in einer lot -Anzahl von Stellen angeben, wenn Sie utf8_unicode_ci verwenden möchten.

Beispielsweise haben alle Clientverbindungen nicht nur einen Standardzeichensatz (für mich sinnvoll), sondern auch eine Standardkollatierung (d. H. Die Kollatierung wird für Unicode immer standardmäßig utf8_general_ci sein).

Wenn Sie utf8_unicode_ci für Ihre Felder verwenden, müssen Ihre Skripts, die eine Verbindung zur Datenbank herstellen, möglicherweise aktualisiert werden, um die gewünschte Kollatierung explizit zu erwähnen. Andernfalls können Abfragen mit Textzeichenfolgen fehlschlagen, wenn Ihre Verbindung die Standardkollatierung verwendet.

Das Ergebnis ist, dass Sie bei der Konvertierung eines vorhandenen Systems beliebiger Größe in Unicode/utf8 möglicherweise gezwungen sind, utf8_general_ci zu verwenden, da MySQL mit Standardeinstellungen umgeht.

11
George Lund

Für den von Guus hervorgehobenen Fall würde ich dringend empfehlen, entweder utf8_unicode_cs (Groß- und Kleinschreibung beachten, strikte Übereinstimmung, größtenteils richtige Reihenfolge) anstelle von utf8_bin (strikte Übereinstimmung, falsche Reihenfolge) zu verwenden.

Wenn das Feld durchsucht werden soll, anstatt für einen Benutzer abgeglichen zu werden, verwenden Sie utf8_general_ci oder utf8_unicode_ci. Bei beiden wird die Groß-/Kleinschreibung nicht beachtet, und es wird keine Übereinstimmung mehr gefunden ("ß" ist gleich "s" und nicht gleich "s"). Es gibt auch sprachspezifische Versionen wie utf8_german_ci, bei denen der Lose Matching besser für die angegebene Sprache geeignet ist.

[Bearbeiten - fast 6 Jahre später]

Ich empfehle den Zeichensatz "utf8" unter MySQL nicht mehr und empfehle stattdessen den Zeichensatz "utf8mb4". Sie stimmen fast vollständig überein, lassen jedoch ein wenig (viel) mehr Unicode-Zeichen zu.

Realistisch sollte MySQL den "utf8" -Zeichensatz und die entsprechenden Kollatierungen aktualisiert haben, um der "utf8" -Spezifikation zu entsprechen, stattdessen jedoch einen separaten Zeichensatz und entsprechende Kollatierungen, um die Speicherzuordnung für diejenigen, die bereits ihren unvollständigen "utf8" -Zeichensatz verwenden, nicht zu beeinträchtigen .

8
SEoF

Ich fand diese Kollatierungsdiagramme hilfreich. http://collation-charts.org/mysql60/ . Ich bin mir nicht sicher, welches utf8_general_ci verwendet wird.

Hier ist zum Beispiel das Diagramm für utf8_swedish_ci. Es zeigt, welche Zeichen es als gleich interpretiert. http://collation-charts.org/mysql60/mysql604.utf8_swedish_ci.html

5
jiv-e

Fügen Sie in Ihrer Datenbank-Upload-Datei vor jeder Zeile die folgende Zeile hinzu:

SET NAMES utf8;

Und dein Problem sollte gelöst sein.

2
tapos ghosh