whine: Zeichensatz-Problem

Hallo Forum!

Ich schreibs mal bei Datenbank rein, weil ich den Fehler in dieser Ecke vermute.

Ich habe in meinem Webprojekt sämtliche php-Dateien in utf-8 (ohne BOM) angelegt, den  Charset im head auf utf-8 und die MySql-Datenbank in utf-8 general-ci (die Tabellen auch) gesetzt. Trotzdem bekomme ich bei der Ausgabe eines DB-Selects mit Umlauten Kauderwelsch angezeigt. Wenn ich direkt in der Datei bspw. Umlaute ausgebe <? echo "ÖÜÄ"; ?> , gibts keine Probleme. Nur mit Daten aus der DB.
Woran kann das liegen?

whine

  1. him

    .. Nur mit Daten aus der DB.
    Woran kann das liegen?

    Dein DB-Handle liefert die Zeichen (als Bitfolge) in einer anderen Codierung, als die Codierung, die der Browser verwendet, um die Bitfolge als Zeichen darzustellen.

    Hotti

    --
    Wenn der Kommentar nicht zum Code passt, kann auch der Code falsch sein.
  2. Hi!

    Trotzdem bekomme ich bei der Ausgabe eines DB-Selects mit Umlauten Kauderwelsch angezeigt.
    Woran kann das liegen?

    Vielleicht an den üblichen schon oft genannten Verdächtigen.

    Generell gilt:

    • Jedes System muss mit der gewünschten Kodierung umgehen können (wenn es nicht nur unverändernder Durchreicher ist.
    • Zwischen zwei Systemen muss Klarheit über die zu verwendende Kodierung herrschen.

    Hast du an jeder Stelle deiner Verarbeitungskette beides berücksichtigt? Konkret: auch bei der Verbindung zwischen PHP und MySQL? Also nach (je)dem Verbindungsaufbau die Zeichenkodierung ausgehandelt?

    Lo!

    1. Hast du an jeder Stelle deiner Verarbeitungskette beides berücksichtigt? Konkret: auch bei der Verbindung zwischen PHP und MySQL? Also nach (je)dem Verbindungsaufbau die Zeichenkodierung ausgehandelt?

      Hmm, die eingebundene db_connect.php mit

      mysql_connect (...) or die ("Keine Verbindung zum Server");  
      mysql_select_db ("test_db") or die ("Keine Verbindung zur Datenbank"); 
      

      ist auch in utf. Muss ich denn am select noch etwas dekodieren?

      Gruß
      whine

      1. Hi!

        Hast du an jeder Stelle deiner Verarbeitungskette beides berücksichtigt? Konkret: auch bei der Verbindung zwischen PHP und MySQL? Also nach (je)dem Verbindungsaufbau die Zeichenkodierung ausgehandelt?

        mysql_connect (...) or die ("Keine Verbindung zum Server");

        mysql_select_db ("test_db") or die ("Keine Verbindung zur Datenbank");

        
        > ist auch in utf. Muss ich denn am select noch etwas dekodieren?  
          
        Nein, in aller Regel nicht. Ich wiederhole mich mal:  
          
        
        > > Also nach (je)dem Verbindungsaufbau die Zeichenkodierung ausgehandelt?  
          
        Du baust die Verbindung nur auf. Da ist nichts von mysql\_set\_charset() oder ein SET NAMES-Statment zu sehen. Du darfst dich dann nicht wundern, wenn MySQL eine Default-Einstellung für die auf der Verbindung verwendete Kodierung annimmt und die Daten zwischen der beim Speichern und der zur Kommunikation konfigurierten Kodierung umkodiert, wenn beide unterschiedlich sind.  
          
          
        Lo!
        
        1. Du baust die Verbindung nur auf. Da ist nichts von mysql_set_charset() oder ein SET NAMES-Statment zu sehen. Du darfst dich dann nicht wundern, wenn MySQL eine Default-Einstellung für die auf der Verbindung verwendete Kodierung annimmt und die Daten zwischen der beim Speichern und der zur Kommunikation konfigurierten Kodierung umkodiert, wenn beide unterschiedlich sind.

          Lo!

          jippijajey! Danke :)

        2. hi,

          [..] und die Daten zwischen der beim Speichern und der zur Kommunikation konfigurierten Kodierung umkodiert, wenn beide unterschiedlich sind.

          Das verstehe ich nicht so richtig. Wenn ich utf8-codierte Zeichen in der DB habe und ein DB-Handle soll mir die Zeichen da rausholen... also ich verstehe das nicht. Ein DB-Handle stellt mir die Verbindung zwischen PHP und DB her. Was hat das mit der Zeichenkoderung zu tun, die ich dem handle mitteilen muss?

          Der handle soll mir nur ein paar Bits von der DB zu PHP übertragen, warum muss der da wissen, was diese Bits darstellen?

          Hotti

          1. Hi!

            [..] und die Daten zwischen der beim Speichern und der zur Kommunikation konfigurierten Kodierung umkodiert, wenn beide unterschiedlich sind.

            Das verstehe ich nicht so richtig. Wenn ich utf8-codierte Zeichen in der DB habe und ein DB-Handle soll mir die Zeichen da rausholen... also ich verstehe das nicht. Ein DB-Handle stellt mir die Verbindung zwischen PHP und DB her. Was hat das mit der Zeichenkoderung zu tun, die ich dem handle mitteilen muss?

            Es geht hier speziell um MySQL (ab Version 4.1). Und MySQL legt Daten in den Feldern einer Tabelle in Kodierung X ab. Wenn ein Client sich mit einem MySQL-Server verbindet, und dieser Client die Kodierung Y zum Senden umd Empfangen von Daten verwenden möchte, dann kodiert MySQL die Daten in den Feldern mit der Kodierung X um in die Kodierung Y, damit der Client trotz der X-Kodierung des Feldes seine Daten Y-kodiert bekommen kann. Und umgekehrt in der Senderichtung.

            Dieses Verhalten will man in aller Regel nicht haben, weil beim Umkodieren zwischen zwei Kodierungen prinzipbedingt Verluste auftreten können. Man bekommt es aber, wenn man nur die Felder vom Defaultwert abweichend konfiguriert, die Verbindungskodierung aber nicht. Und dann wundert man sich, wenn man X annimmt, aber aufgrund des ungeänderten Defaultwertes Y bekommt.

            Wenn man alles richtig macht, ist der Ablauf wie folgt - mit einer kleinen Abschweifung vorab:

            Es gibt eine Menge Konfigurationsoptionen und -variablen. Der Unterschied zwischen Option und Variable ist hier nicht weiter relevant. Einige dieser Variablen lassen sich jedenfalls individuell für die Session zwischen Client und Server umstellen. Es gibt dann neben dem globalen Wert den sessionspezifischen Wert. Man sieht das zum Beispiel im phpMyAdmin unter "Show MySQL system variables" auf der Startseite. Wenn keine Felder orange hinterlegt sind, kann man (ebenfalls auf der Startseite) mal den Wert "MySQL connection collation" umstellen. (Man macht sich dabei nichts grundlegendes kaputt, denn das ist ein session-individueller Wert für den PMA.) Nun sollten einige Werte mit einer zweiten Zeile "(Global value)" zu sehen sein. Der obere Wert ist der session-individuelle. Nach dem Test sollte man "MySQL connection collation" wieder zurückstellen (Vergessen, was es war? utf8_general_ci passt meistens).

            Für die Zeichensatz/-kodierungsproblematik sind die Werte character_set_... relevant.

            Ein Client sendet ein SQL-Statement, von dem der MySQL-Server annimmt, es sei gemäß character_set_client kodiert. (Innerhalb eines SQL-Statements können einzelne Werte anders kodiert sein, wenn man sie besonders kennzeichnet. Das braucht man im Normalfall aber nicht.) Das SQL-Statement wird umkodiert nach character_set_connection (außer den besonders gekennzeichneten Einzelwerten). Bis hier hin passiert das mit allen SQL-Statements, egal ob Daten gelesen oder geändert werden oder auch nicht. Die Ergebnisse, die in Richtung Client gesendet werden sollen, werden gemäß character_set_results kodiert.

            Die drei Variablen character_set_client, character_set_connection und character_set_results lassen sich mit der MySQL-C-API-Funktion mysql_set_character_set() (oder einem Pendant in einer sprachspezifischen API (PHP: mysql(i)_set_charset())) oder einem "SET NAMES"-Statement einstellen. (SET CHARACTER SET sieht man auch gelegentlich, das arbeitet jedoch etwas anders, wie im nachfolgend verlinkten Kapitel nachgelesen werden kann.)

            Soweit lässt sich das alles im Kapitel Character Set Support, speziell Connection Character Sets ... nachlesen. Nun fehlt aber noch der Teil wie Daten in und aus Feldern geschrieben und gelesen werden. Dazu konnte ich nicht explizit etwas im Handbuch finden. Meine Erfahrung, gesammelt und bestätigt auch durch einige Experimente, besagen, dass die Daten noch einmal umkodiert werden, dieses Mal von character_set_connection in die Feldkodierung. Und für den Rückweg braucht es nur noch eine Umkodierung des Ergebnissets nach character_set_results.

            Das war eine Menge Umkodierungen. Idealerweise muss aber nichts umkodiert werden, wenn alle beteiligten Variablen und die Felder-Konfigurationen auf die gleiche Kodierung eingestellt wurden.

            Neben dem "Character Set" gibt es auch noch den Begriff "Collation", der oft in unmittelbarer Nähe zu "Character Set" zu finden ist. Die Konfigurationsvariablenorgie ist für beides nahezu gleich. Collation hat aber zur Aufgabe Regeln für das Vergleichen von Zeichen (zum Sortieren beispielsweise) zu definieren. Für Probleme mit der Kodierung kann man den Collation-Wert unberücksichtigt lassen.

            Lo!

            1. h1,

              [..] und die Daten zwischen der beim Speichern und der zur Kommunikation konfigurierten Kodierung umkodiert, wenn beide unterschiedlich sind.

              Das verstehe ich nicht so richtig. Wenn ich utf8-codierte Zeichen in der DB habe und ein DB-Handle soll mir die Zeichen da rausholen... also ich verstehe das nicht. Ein DB-Handle stellt mir die Verbindung zwischen PHP und DB her. Was hat das mit der Zeichenkoderung zu tun, die ich dem handle mitteilen muss?

              Es geht hier speziell um MySQL (ab Version 4.1). Und MySQL legt Daten in den Feldern einer Tabelle in Kodierung X ab. Wenn ein Client sich mit einem MySQL-Server verbindet, und dieser Client die Kodierung Y zum Senden umd Empfangen von Daten verwenden möchte, dann kodiert MySQL die Daten in den Feldern mit der Kodierung X um in die Kodierung Y, damit der Client trotz der X-Kodierung des Feldes seine Daten Y-kodiert bekommen kann. Und umgekehrt in der Senderichtung.

              Ok, ich danke Dir schon mal für diese Infos und auch für die Weiteren. Immerhin hatte ich ja auch schonmal das Propblem, dass mir PMA die Zeichen in meinem Dump schon beim Einspielen umkodiert und dann noch Collationen angezeigt hat, die ich in MySQL gar nicht konfigueriert hatte.

              Ansonsten hats mich nur ein bischen gewundert, denn normalerweise ist ein Handle, egal ob Filehandle, socket oder DB-Handle nur ein Stück Draht wo am Ende das rauskommt, was am Anfang reingesteckt wurde.

              Viele Grüße aus der Waldstadt,
              Horst Haselhuhn

              1. Hi!

                Immerhin hatte ich ja auch schonmal das Propblem, dass mir PMA die Zeichen in meinem Dump schon beim Einspielen umkodiert und dann noch Collationen angezeigt hat, die ich in MySQL gar nicht konfigueriert hatte.

                Der PMA zeigt eine ganze Menge Variablen und andere Dinge vom MySQL-Server an, das ist eine seiner Aufgaben. Darunter sind auch viele Default-Werte zu sehen, die man (logischerweise) nicht selbst eingestellt hat. Sie sind aber vorhanden, auch wenn man sich ihrer nicht bewusst ist.

                Beim Arbeiten mit dem PMA kommt beim Thema Kodierungen noch zwei weitere Ebenen hinzu. Außerdem kann er bei Dumps auch noch Umkodierungen vornehmen. Die eine Ebene ist die Kommunikation zwischen PMA und dem Browser. Das läuft in der Regel problemlos über UTF-8. Die andere ist die Kommunikation zwischen ihm und MySQL.

                Es ist schon eine Weile her, als ich ihn genauer untersuchte, aber meine Beobachtungen sollte noch gelten. Beim Arbeiten mit Tabellen handelt der PMA mit dem MySQL-Server immer UTF-8 für Lesevorgänge aus (setzt also effektiv den Session-Wert für character_set_results auf utf8). Beim Schreiben handelt er den auf der Startseite bei "MySQL connection collation" (deutsch: "Zeichensatz / Kollation der MySQL-Verbindung") eingestellten Wert aus (betrifft die Session-Werte für character_set_client und character_set_connection). Die eingegebenen Daten schickt er in dieser Kodierung zum Server. Wenn man also Latin1 eingestellt hat und Nicht-Latin1-Zeichen sendet, kann der PMA nur "?" (0x3F) zum Server senden. In dem Fall tritt der Verlust schon im PMA auf. Hat man hingegen utf8 eingestellt und gibt ein Nicht-Latin1-Zeichen für ein Feld ein, das "nur" auf Latin1 steht, dann findet der Verlust im MySQL-Server statt. Dabei kommt auch wieder ein "?" raus, weil es kein (besseres) Zeichen für "Zeichen nicht repräsentierbar" in Latin1 gibt. Am besten lässt man diese Einstellung auf utf8(_general_ci) stehen, auch wenn man selbst nur Latin1 für seine Felder verwendet.

                Beim Dump mit dem PMA gilt, dass der Export immer UTF-8-kodiert ist. Es sei denn, man hat die iconv-Extension in PHP eingebunden und dem PMA als RecondingEngine 'iconv' konfiguriert (und AllowAnywhereRecording auf true gestellt). Dann kann man die Kodierung wählen und der PMA nimmt die Umkodierung aus den gelesenen UTF-8-kodierten Daten vor. Beim Import kann man stets eine Zeichenkodierung angeben. Mit AllowAnywhereRecording=true und iconv als RecondingEngine nimmt der PMA die Umkodierung vor. Mit AllowAnywhereRecording=false stellt er nur SET NAMES auf den gewählten Wert. AllowAnywhereRecording beeinflusst auch, welche Kodierungen beim Import zur Auswahl stehen.

                Ansonsten hats mich nur ein bischen gewundert, denn normalerweise ist ein Handle, egal ob Filehandle, socket oder DB-Handle nur ein Stück Draht wo am Ende das rauskommt, was am Anfang reingesteckt wurde.

                Handles sind nicht das Problem, sondern, wie ich auch in der ersten Antwort schon schrieb:

                • Zwischen zwei Systemen muss Klarheit über die zu verwendende Kodierung herrschen.

                Nicht der Draht ist ausschlaggebend, sondern dass Sender und Empfänger die gleiche Sprache sprechen. Das erreicht man, indem stillschweigend vom Gleichen ausgegangen wird oder indem explizit festgelegt wird, was man übertragen will.

                Lo!

                1. h1,

                  • Zwischen zwei Systemen muss Klarheit über die zu verwendende Kodierung herrschen.

                  Ein handle hat hinsichtlich der Zeichenübertragung transparent zu sein. D.h., ich übergebe einen bitstream C3.A4 und erwarte, dass am anderen Ende ebenfalls C3.A4 wieder rauskommt. Es mag sein, dass PHP oder Du das anders sieht, aber ich sehe das so: Ein handle hat die Inhalte nicht zu verändern, hat also mit der Codierung nichts zu schaffen. Und auch nicht mit dem Content-Type (MIME). Einem handle ist es egal, was da übertragen wird. Lediglich die Endsysteme müssen sich verständigen. Wenn ich ein GIF mit dem Texteditor anschaue, sehe ich Fratzen, nehme ich jedoch ACDC, sehe ich ne nackte Frau ;-)

                  Wie auch immer, meine PHP Scripts mit MySQL Anbindung funktionieren einwandfrei, auch mit verschiedenen Charset-Encoding's und auf unterschiedlichen Plattformen/Serverkonfigurationen, solange ich das DB-Management nicht mit PMA mache.

                  Viele Grüße,
                  Hotti

                  1. Hi!

                    • Zwischen zwei Systemen muss Klarheit über die zu verwendende Kodierung herrschen.
                      Ein handle hat hinsichtlich der Zeichenübertragung transparent zu sein. D.h., ich übergebe einen bitstream C3.A4 und erwarte, dass am anderen Ende ebenfalls C3.A4 wieder rauskommt. Es mag sein, dass PHP oder Du das anders sieht, aber ich sehe das so: Ein handle hat die Inhalte nicht zu verändern, hat also mit der Codierung nichts zu schaffen. Und auch nicht mit dem Content-Type (MIME). Einem handle ist es egal, was da übertragen wird. Lediglich die Endsysteme müssen sich verständigen.

                    Ja, genau so ist das auch. Weder ich noch PHP noch MySQL sehen das anders. Die Daten werden über die Verbindung lediglich übertragen (auch wenn du das als Handle bezeichnest). Sie werden dabei nicht geändert. Erst der empfangende MySQL-Server wertet sie aus und macht intern irgendwas damit - für den Client ist das ja nur eine Blackbox. MySQL muss dazu wissen, welche Zeichenkodierung der Client verwendet hat. Genau das wird mit mysql_set_charset() oder SET NAMES mitgeteilt. Auch auf dem Rückweg will der Client die Daten in einer definiert kodierten Form haben. Und zwar jeder Client, wie er es gern hätte. Es können ja schließlich mehrere Anwendungen auf den gleichen Datenbestand zugreifen wollen (mit PMA und der selbst geschriebenen hat man schonmal zwei). Auch das muss in der erwähnten Form ausgehandelt werden - die Anwendungen könnten ja schließlich mut unterschiedlichen Zeichenkodierungen arbeiten wollen. Es nützt nichts, wenn die Blackbox MySQL den Datenstrom lediglich haargenauso wieder ausgeben könnte, wie er reinlief. Denn dann müssten alle Clients die selbe Kodierung verwenden. MySQL könnte die Daten nicht richtig interpretieren, was eine korrekte Stringverarbeitung unmöglich machte.

                    Wie auch immer, meine PHP Scripts mit MySQL Anbindung funktionieren einwandfrei, auch mit verschiedenen Charset-Encoding's und auf unterschiedlichen Plattformen/Serverkonfigurationen, solange ich das DB-Management nicht mit PMA mache.

                    Dann machst du irgendwas nicht richtig. Solange du nur die einzige Anwendung bist und MySQL von einer 1-Byte-Kodierung ausgeht, bekommst du die Daten auch wieder so raus, wie du sie hingegeben hast. Clients die aber gern eine andere Kodierung hätten, und dies ordentlich mitteilen, bekommen falsche Ergebnisse geliefert, weil das was deine Anwendung spricht und das was MySQL annimmt, vermutlich nicht das selbe ist (zum Beispiel: du schickst UTF-8, MySQLs Default-Konfiguration steht aber auf Latin1), und so MySQL deinen Datenstrom nicht korrekt interpretieren kann, sowie in der Folge nicht korrekt umkodieren kann. Auch intern wirst du einige Probleme haben. Vielleicht sind sie dir nur noch nicht aufgefallen.

                    Frag doch mal von einem Inhalt mit der Länge x, der einen Umlaut enthält, und den du deiner Meinung nach UTF-8-kodiert an MySQL gesendet hast, und der außerdem im PMA nicht richtig angezeigt wird, mit SELECT CHARACTER_LENGTH(ebendieser_wert) FROM tabelle WHERE ... ab, wieviele Zeichen MySQL zählt. Bei x+1 hast du MySQL nicht mitgeteilt, dass er UTF-8 sei. Wundere dich dann nicht, dass Sortierungen und Vergleiche nicht richtig arbeiten.

                    Lo!

                    1. moin,

                      .. Es nützt nichts, wenn die Blackbox MySQL den Datenstrom lediglich haargenauso wieder ausgeben könnte, wie er reinlief.

                      In dieser Hinsicht hat mich MySQL bisher (noch) nicht enttäuscht ;-)

                      Schönen Tag,
                      Hotti

                    2. h1,

                      ..MySQLs Default-Konfiguration steht aber auf Latin1), und so MySQL deinen Datenstrom nicht korrekt interpretieren kann,

                      Also nunmal Klartext mein Guter ;-)
                      (Kaffee ist fertig und dampft)

                      Was MySQL interpretiert, ist (mir) schei?egal. Du redest von disziplinarischen Dingen, ich jedoch rede von Technik. Du kannst in einer Tabelle, die als latin1 deklariert ist, problemlos auch Zeichen mit einer utf8-Codierung reintun, die brauchen nur ein bischen mehr Platz. Ein in latin1 codiertes 'ä' braucht 1 byte, codiere ich es in utf8, braucht es 2 byte. Das ist das ganze Geheimnis. Der Rest ist Disziplin. Klappe zu, Affe tot.

                      Hotte, setzen, Betragen "5" ;-)

                      1. Hi!

                        Du kannst in einer Tabelle, die als latin1 deklariert ist, problemlos auch Zeichen mit einer utf8-Codierung reintun, die brauchen nur ein bischen mehr Platz. Ein in latin1 codiertes 'ä' braucht 1 byte, codiere ich es in utf8, braucht es 2 byte. Das ist das ganze Geheimnis. Der Rest ist Disziplin. Klappe zu, Affe tot.

                        Soweit klappt das auch. (Und wenn dir nur MySQL kleiner Version 4.1 oder ein anderes DBMS ohne UTF-8-Support zur Verfügung steht, musst du so vorgehen, wenn du UTF-8-Kodiertes speichern willst.) Aber du bekommst Probleme beim Weiterverarbeiten. Eines ist, wie du schon lapidar festgestellt hast, "die brauchen nur ein bischen mehr Platz". Beispielsweise kann ein Latin1-Feld mit der Länge 10 nur 5 2-Byte-Zeichen aufnehmen. Oder wenn du einen UTF-8-kodierten 10-Zeichen-String mit einem Umlaut drin speichern willst, benötigst du 11 Byte und ein Zeichen geht verloren. Da kannst du mit Disziplin wenig dagegen ausrichten. Natürlich kannst du das Feld gleich um den Faktor drei größer anlegen (nur 3 und nicht 4, weil MySQL nur die BMP unterstüzt). Damit hast du dieses Problem erst einmal gelöst. MySQL würde es bei UTF-8-deklarierten Feldern auch nicht anders tun. Du hast nur eine falsche Anzeige, die du und andere Projektmitarbeiter richtig deuten müssen.

                        foo char(3) character set latin1
                          bar char(1) character set utf8

                        Beide Felddefinitionen reservieren intern gleich viel Speicher, nämlich jeweils 3 Byte. In beide Felder kann man maximal ein UTF-8-Zeichen ablegen. Beim ersten musst du jedoch stets berücksichtigen, dass 3 eigentlich nur 1 bedeutet.

                        Weiterhin hast du, dass MySQL den Inhalt nach wie vor als Latin1 interpretieren wird. Abfragen wie "finde alle Einträge mit x Zeichen Länge" kann MySQL nicht richtig beantworten, weil dein ä als ä angesehen wird und bei CHARACTER_LENGTH() als 2 zählt.

                        Vegleiche mit Nicht-ASCII-Zeichen liefern keine sinnvollen Ergebnisse, wie sie auch für eine Sortierung benötigt werden. Es gibt ja Regeln, die bei Vergleichsvorgängen ä=a, ß=s oder auch ä=ae, ß=ss und so weiter setzen. Diese Regeln kannst du vergessen und damit die ORDER BY-Klausel.

                        Geht alles mit "Disziplin" zu lösen. Man frage sämtliche Daten ab und zähle deren Länge im abfragenden Programm, wenn einen die unnötige Übertragung von eigentlich gar nicht benötigten Daten nicht stört. Sortieren kann man im abfragenden Programm auch wunderbar selbst, "ORDER-BY-Klausel, ohne dich geht's auch". Die Frage ist nur, warum nimmst du diese Einbußen an Komfort und Leistung in Kauf, wenn du mit einem simplen SET NAMES all solche Probleme nicht (ständig zu berücksichtigen) hast.

                        Lo!