MySQL・質疑応答・set namesは何をしましたか

16098 ワード

背景
最近同僚からset namesに3つのセッション変数が同時に設定されていると聞かれました
SET character_set_client = charset_name;
SET character_set_results = charset_name;
SET character_set_connection = charset_name;

変数名から見るとcharacter_set_クライアントはクライアント関連の文字セットを設定し、character_set_resultsは、結果を返す文字セットを設定します.character_set_connectionこれはちょっとわかりませんが、これは何の役に立ちますか?
概念の説明
公式文書で見ると:
  • character_set_Clientとは、クライアントから送信された文の符号化を指す.
  • character_set_connectionとはmysqldがクライアントの文を受信した後、変換する符号化を指す.
  • そしてcharacter_set_resultsとは、serverが文を実行した後、クライアントに返されるデータの符号化を指す.

  • 人間にとって理解できるのはいろいろな記号ですが、コンピュータにとってはバイナリしか理解できません.バイナリと記号の対応関係は符号化です.異なる地域の国には独自のシンボルセットがあり、それぞれがそれぞれバイナリ数字で表され、異なる符号化が形成され、文字セットは符号化とシンボルの対応関係の集合と見なすことができる.同じバイナリ数は異なる文字セットの下で全く異なる文字に対応する可能性があります.例えば、GBK文字セットではC4E3 に対応し、big 5文字セットでは に対応し、 のunicodeでの符号化は4F60で、Collation-Chartsというサイトでは文字セットと符号化対応関係図があります.異なる符号化におけるバイナリ数とシンボルの対応関係を非常に直感的に見ることができる.
    set names設定の3つの変数はmysqldとクライアントの通信を設定するとき、mysqldはclientから送られてきた文字をどのように解読し、クライアントにどのような符号化を返すべきかです.
    じっけんしけん
    環境は次のとおりです.
    mysql> show variables like 'character%';
    +--------------------------+-------------------------------------+
    | Variable_name            | Value                               |
    +--------------------------+-------------------------------------+
    | character_set_client     | utf8                                |
    | character_set_connection | utf8                                |
    | character_set_database   | utf8                                |
    | character_set_filesystem | binary                              |
    | character_set_results    | utf8                                |
    | character_set_server     | utf8                                |
    | character_set_system     | utf8                                |
    

    サーバ側の3つの符号化設定はutf 8である.また、クライアントは標準mysql clientであり、使用する符号化はutf 8であり、sever側符号化と一致する.
    テストとしてテーブルを作成
    CREATE TABLE t1(id INT, name VARCHAR(200) CHARSET utf8) engine=InnoDB;
    
    INSERT INTO t1 VALUES(0, '  ');
    mysql> SELECT id, name, hex(name) FROM t1;
    +------+--------+--------------+
    | id   | name   | hex(name)    |
    +------+--------+--------------+
    |    0 |      | E4BDA0E5A5BD |
    +------+--------+--------------+
    

    次に、この3つの値をそれぞれ変更して、結果がどのように変化するかを見てみましょう.
    Case 1はcharacterだけを変更set_client
    SET character_set_client=gbk;
    INSERT INTO t1 VALUES(1, '  ');
    mysql>  SELECT id, name, hex(name) FROM t1;
    +------+-----------+--------------------+
    | id   | name      | hex(name)          |
    +------+-----------+--------------------+
    |    0 |         | E4BDA0E5A5BD       |
    |    1 |   ソ    | E6B5A3E78AB2E382BD |
    +------+-----------+--------------------+
    2 rows in set (0.00 sec)
    

    返されたデータが文字化けしていることがわかり、データベースに保存されているのは確かに最初のレコードとは違います.
    Case 2 characterのみ変更set_connection
    SET names utf8;
    SET character_set_connection = gbk;
    INSERT INTO t1 VALUES(2, '  ');
    
    mysql>  SELECT id, name, hex(name) FROM t1;
    +------+-----------+--------------------+
    | id   | name      | hex(name)          |
    +------+-----------+--------------------+
    |    0 |         | E4BDA0E5A5BD       |
    |    1 |   ソ    | E6B5A3E78AB2E382BD |
    |    2 |         | E4BDA0E5A5BD       |
    +------+-----------+--------------------+
    3 rows in set (0.00 sec)
    

    ケース3はcharacterのみを変更するset_results
    SET names utf8;
    SET character_set_results = gbk;
    INSERT INTO t1 VALUES(3, '  ');
    
    mysql> select id, name, hex(name) from t1;
    +------+--------+--------------------+
    | id   | name   | hex(name)          |
    +------+--------+--------------------+
    |    0 |        | E4BDA0E5A5BD       |
    |    1 |      | E6B5A3E78AB2E382BD |
    |    2 |        | E4BDA0E5A5BD       |
    |    3 |        | E4BDA0E5A5BD       |
    +------+--------+--------------------+
    4 rows in set (0.00 sec)
    

    もう一度元に戻して結果を見よう
    SET names utf8;
    mysql>  SELECT id, name, hex(name) FROM t1;
    +------+-----------+--------------------+
    | id   | name      | hex(name)          |
    +------+-----------+--------------------+
    |    0 |         | E4BDA0E5A5BD       |
    |    1 |   ソ    | E6B5A3E78AB2E382BD |
    |    2 |         | E4BDA0E5A5BD       |
    |    3 |         | E4BDA0E5A5BD       |
    +------+-----------+--------------------+
    4 rows in set (0.00 sec)
    

    ぶんせき
    まず文字セットがプロセス全体でどのように変化するかを整理してから、上のcaseを分析します.
    お客様がリクエストを送信した場合:
    A1         (   utf8)------> A2 sever      ( character_set_client    )
                                                                        |
                                                                        v
    A4     mysqld    

    serverが結果を返す場合:
    B1 server    ( character_set_results     ) ----->B2         (   utf8)
    

    A 3ステップは符号化を変換する必要があるかどうか、コードの中の論理はこのようにして、sql_yacc.yyファイル:
      LEX_STRING tmp;
      THD *thd= YYTHD;
      const CHARSET_INFO *cs_con= thd->variables.collation_connection;
      const CHARSET_INFO *cs_cli= thd->variables.character_set_client;
      uint repertoire= thd->lex->text_string_is_7bit &&
                       my_charset_is_ascii_based(cs_cli) ?
                       MY_REPERTOIRE_ASCII : MY_REPERTOIRE_UNICODE30;
      if (thd->charset_is_collation_connection ||
          (repertoire == MY_REPERTOIRE_ASCII &&
           my_charset_is_ascii_based(cs_con)))
         tmp= $1;
      else
      {
        if (thd->convert_string(&tmp, cs_con, $1.str, $1.length, cs_cli))
            MYSQL_YYABORT;
      }
      $$= new (thd->mem_root) Item_string(tmp.str, tmp.length, cs_con,
                                          DERIVATION_COERCIBLE,
                                          repertoire);
      if ($$ == NULL)
         MYSQL_YYABORT;
    
    character_set_clientcharacter_set_connectionが同じである場合、または現在の文字符号化がASCIIと互換性があり、いずれもASCIIの範囲内である場合、変換されず、他の場合に移行する.
    case 1については実際にクライアントから送信されたのはUTF 8であるが、A 2ステップserverはクライアントの符号化がGBKであると判断し、GBKで解析するとともにA 3ステップの変換条件を満たすため、UTF 8の符号化を誤ってGBKとみなし、その後UTF 8に転送する. のUTF 8符号化はE4BDA0E5A5BD 6バイトで、1文字あたり3バイト、GBKで解析すると、GBKは固定2バイトなので、3文字あると思ってUTF 8に変換し、UTF 8は長くなりますが、ここの3つのGBK文字は値によって3文字の節を占め、合計9バイトになります.したがってcase 1が見た実際の格納値は合計9バイトで,従来より大きい.戻るときはUTF 8を押して返しますが、UTF 8文字が3文字保存されているのでクライアントに見えるのは3文字です.
    case 2 A 2のステップについては問題なく、問題はA 3で、変換ロジックに従って、この場合UTF 8をGBKに変換する必要があります.ここではcharacter_set_clientが正しいので、変換のソースが間違っていないことを認識し、GBKに変換しても自然に間違っていません.後にUTF 8に格納した場合、GBKからUTF 8に変換しても間違いありません.UTF 8とGBK文字セットには「あなた」と「良い」が含まれているので、だからお互いに変換しても間違いはなく、2回以上変換しただけです.
    case 3エラーは、返された文字セットの設定がクライアントと一致しない場合、返されたときにserverがすべての文字をGBKに変換した結果、クライアントがUTF 8と一本筋で認識したため、解析エラーとなった.興味深いのは2番目の記録、すなわちcase 1が誤って挿入され、正しいことを示していることだ.なぜなら、case 1に格納されているときは、UTF8-> GBK-> UTF8という論理で格納されているから、戻るときは、serverが格納されているUTF 8をGBKに戻し、クライアントがこのGBKを持ってUTF 8解析だと勘違いしていたため、実際にはcase 1の逆方向の過程であり、2つの方向は間違っていたが、最終的には表示が良い、いわゆる負の正負であろう、は.
    case 2の場合、クライアントからserverにデータが入ると、2回以上変換され、最終的には正しいと表示されますが、すべてのシーンがそうではありません.以下のように
    set names utf8;
    set character_set_connection  = latin1;
    INSERT INTO t1 VALUES(4, '  ');
    set names utf8;
    mysql>  SELECT id, name, hex(name) FROM t1;
    +------+-----------+--------------------+
    | id   | name      | hex(name)          |
    +------+-----------+--------------------+
    |    0 |         | E4BDA0E5A5BD       |
    |    1 |   ソ    | E6B5A3E78AB2E382BD |
    |    2 |         | E4BDA0E5A5BD       |
    |    3 |         | E4BDA0E5A5BD       |
    |    4 | ??        | 3F3F               |
    +------+-----------+--------------------+
    5 rows in set (0.00 sec)
    

    なぜなら、UTF 8がlatin 1を回転すると、情報が失われるため、latin 1文字符号化で表現できる文字セットはutf 8よりはるかに小さく、 はその中にはなく、この2文字は変換中に??に変換され、その後UTF 8に変換されたとき、?は1バイト3Fしか記憶されず、還元は3Fです.
    まとめcharacter_set_clientcharacter_set_resultsは必ずクライアントと一致しなければならない.負に依存しないで、character_set_connectionの設定とcharacter_set_clientの設定が一致しない.データを失うリスクがあるので、できるだけ一致しなければならない.つまり、この3つの値は同じで、クライアントと一致しなければならないので、set namesというショートカットコマンドがある.なぜcharacter_set_connectionがあるのかについては、筆者はまだ見ていませんが、後で理解してから更新します.読者の皆さんが知っていれば、教えてください.