Изменить набор символов по умолчанию таблицы изменяет строки в MySQL 5.6 - PullRequest
0 голосов
/ 05 июля 2019

Я использую MySQL 5.6 и хочу изменить кодировку по умолчанию для одной таблицы (от latin1 до utf8) БЕЗ изменения существующих столбцов и строк. На основании документации я попробовал следующую команду:

ALTER TABLE mytable DEFAULT CHARACTER SET utf8;

Он изменил кодировку набора символов по умолчанию для моей таблицы и НЕ изменил параметры сортировки столбцов, как и ожидалось, НО я был очень удивлен, увидев:

Query OK, 32141 rows affected (6.31 sec)
Records: 32141 Duplicates: 0  Warnings: 0

За исключением "32141 затронутых строк", результаты соответствуют ожидаемым, как показано ниже:

MySQL> select count(*) from mytable;
+----------+
| count(*) |
+----------+
|    32141 |
+----------+
1 row in set (0.01 sec)
MySQL> show table status like 'mytable';
+-----------------------+--------+---------+------------+-------+----------------+-------------+-----------------+--------------+-----------+----------------+-------------+-------------+------------+-----------------+----------+----------------+---------+
| Name                  | Engine | Version | Row_format | Rows  | Avg_row_length | Data_length | Max_data_length | Index_length | Data_free | Auto_increment | Create_time | Update_time | Check_time | Collation       | Checksum | Create_options | Comment |
+-----------------------+--------+---------+------------+-------+----------------+-------------+-----------------+--------------+-----------+----------------+-------------+-------------+------------+-----------------+----------+----------------+---------+
| mytable               | InnoDB |      10 | Compact    | 16723 |          20798 |   347815936 |               0 |     21561344 |  15728640 |           NULL | NULL        | NULL        | NULL       | utf8_general_ci |     NULL | partitioned    |         |
+-----------------------+--------+---------+------------+-------+----------------+-------------+-----------------+--------------+-----------+----------------+-------------+-------------+------------+-----------------+----------+----------------+---------+

MySQL> show create table mytable;
CREATE TABLE `mytable` (
  `ID` varchar(255) NOT NULL,
  `COL1` double DEFAULT NULL,
  `COL2` longtext CHARACTER SET latin1,
  `COL3` datetime DEFAULT NULL,
  `COL4` varchar(255) CHARACTER SET latin1 DEFAULT NULL,
  `COL5` int(11) DEFAULT NULL,
  `COL6` datetime DEFAULT NULL,
  `COL7` varchar(255) CHARACTER SET latin1 DEFAULT NULL,
  `COL8` datetime(3) NOT NULL,
  `COL9` int(11) NOT NULL DEFAULT '-1',
  `COL10` int(11) DEFAULT '0',
  `COL11` double DEFAULT '0',
  PRIMARY KEY (`ID`,`COL9`),
  KEY `idx1` (`COL7`,`COL3`,`COL6`),
  KEY `idx2` (`COL1`,`COL4`,`COL3`,`COL6`),
  KEY `idx3` (`ID`,`COL3`,`COL6`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
/*!50100 PARTITION BY RANGE (`COL9`)
(PARTITION p0 VALUES LESS THAN (1) ENGINE = InnoDB,
 PARTITION p1 VALUES LESS THAN (2) ENGINE = InnoDB,
 PARTITION p2 VALUES LESS THAN (3) ENGINE = InnoDB,
 PARTITION p3 VALUES LESS THAN (4) ENGINE = InnoDB,
 PARTITION p4 VALUES LESS THAN (5) ENGINE = InnoDB,
 PARTITION p5 VALUES LESS THAN (6) ENGINE = InnoDB,
 PARTITION p6 VALUES LESS THAN (7) ENGINE = InnoDB,
 PARTITION p7 VALUES LESS THAN (8) ENGINE = InnoDB,
 PARTITION p8 VALUES LESS THAN (9) ENGINE = InnoDB,
 PARTITION p9 VALUES LESS THAN (10) ENGINE = InnoDB,
 PARTITION p10 VALUES LESS THAN (11) ENGINE = InnoDB,
 PARTITION p11 VALUES LESS THAN (100) ENGINE = InnoDB,
 PARTITION p12 VALUES LESS THAN (101) ENGINE = InnoDB,
 PARTITION p13 VALUES LESS THAN (102) ENGINE = InnoDB,
 PARTITION p14 VALUES LESS THAN (103) ENGINE = InnoDB,
 PARTITION p15 VALUES LESS THAN (104) ENGINE = InnoDB,
 PARTITION p16 VALUES LESS THAN (105) ENGINE = InnoDB,
 PARTITION p17 VALUES LESS THAN (106) ENGINE = InnoDB,
 PARTITION p18 VALUES LESS THAN (107) ENGINE = InnoDB,
 PARTITION p19 VALUES LESS THAN (108) ENGINE = InnoDB,
 PARTITION p20 VALUES LESS THAN (109) ENGINE = InnoDB,
 PARTITION p21 VALUES LESS THAN (110) ENGINE = InnoDB,
 PARTITION p22 VALUES LESS THAN (111) ENGINE = InnoDB,
 PARTITION p23 VALUES LESS THAN (200) ENGINE = InnoDB,
 PARTITION p24 VALUES LESS THAN (201) ENGINE = InnoDB,
 PARTITION p25 VALUES LESS THAN (202) ENGINE = InnoDB,
 PARTITION p26 VALUES LESS THAN (203) ENGINE = InnoDB,
 PARTITION p27 VALUES LESS THAN (204) ENGINE = InnoDB,
 PARTITION p28 VALUES LESS THAN (205) ENGINE = InnoDB,
 PARTITION p29 VALUES LESS THAN (206) ENGINE = InnoDB,
 PARTITION p30 VALUES LESS THAN (207) ENGINE = InnoDB,
 PARTITION p31 VALUES LESS THAN (208) ENGINE = InnoDB,
 PARTITION p32 VALUES LESS THAN (209) ENGINE = InnoDB,
 PARTITION p33 VALUES LESS THAN (210) ENGINE = InnoDB,
 PARTITION p34 VALUES LESS THAN (211) ENGINE = InnoDB,
 PARTITION p35 VALUES LESS THAN MAXVALUE ENGINE = InnoDB) */
MySQL> show full columns from mytable;
+--------------------------+--------------+-------------------+------+-----+---------+-------+---------------------------------+---------+
| Field                    | Type         | Collation         | Null | Key | Default | Extra | Privileges                      | Comment |
+--------------------------+--------------+-------------------+------+-----+---------+-------+---------------------------------+---------+
| ID                       | varchar(255) | latin1_swedish_ci | NO   | PRI | NULL    |       | select,insert,update,references |         |
| COL1                     | double       | NULL              | YES  | MUL | NULL    |       | select,insert,update,references |         |
| COL2                     | longtext     | latin1_swedish_ci | YES  |     | NULL    |       | select,insert,update,references |         |
| COL3                     | datetime     | NULL              | YES  |     | NULL    |       | select,insert,update,references |         |
| COL4                     | varchar(255) | latin1_swedish_ci | YES  |     | NULL    |       | select,insert,update,references |         |
| COL5                     | int(11)      | NULL              | YES  |     | NULL    |       | select,insert,update,references |         |
| COL6                     | datetime     | NULL              | YES  |     | NULL    |       | select,insert,update,references |         |
| COL7                     | varchar(255) | latin1_swedish_ci | YES  | MUL | NULL    |       | select,insert,update,references |         |
| COL8                     | datetime(3)  | NULL              | NO   |     | NULL    |       | select,insert,update,references |         |
| COL9                     | int(11)      | NULL              | NO   | PRI | -1      |       | select,insert,update,references |         |
| COL10                    | int(11)      | NULL              | YES  |     | 0       |       | select,insert,update,references |         |
| COL11                    | double       | NULL              | YES  |     | 0       |       | select,insert,update,references |         |
+--------------------------+--------------+-------------------+------+-----+---------+-------+---------------------------------+---------+

Мои параметры подключения следующие:

MySQL> show variables where variable_name like '%char%' or variable_name like '%collation%';
+--------------------------+--------------------------------------------------+
| Variable_name            | Value                                            |
+--------------------------+--------------------------------------------------+
| character_set_client     | utf8mb4                                          |
| character_set_connection | utf8mb4                                          |
| character_set_database   | utf8mb4                                          |
| character_set_filesystem | binary                                           |
| character_set_results    | utf8mb4                                          |
| character_set_server     | utf8mb4                                          |
| character_set_system     | utf8                                             |
| collation_connection     | utf8mb4_general_ci                               |
| collation_database       | utf8mb4_general_ci                               |
| collation_server         | utf8mb4_general_ci                               |
+--------------------------+--------------------------------------------------+

Обратите внимание, что:

  • данные были созданы из Java-приложения
  • во время создания данных параметры соединения были установлены в utf8
  • FK не связан с этой таблицей

Когда я пытаюсь воспроизвести некоторые недавно созданные таблицы, кажется, что строки не изменены. Смотрите ниже "0 строк затронуто":

MySQL> select count(*) from mytesttable;
+----------+
| count(*) |
+----------+
|        3 |
+----------+
3 row in set (0.10 sec)
MySQL> alter table mytesttable character set utf8;
Query OK, 0 rows affected (0.03 sec)
Records: 0  Duplicates: 0  Warnings: 0

Я пытался изменить параметры соединения обратно на latin1 во время создания данных, но это не изменило результат: все равно «затронуто 0 строк».

Итак, мои вопросы:

  1. Правильно ли мое понимание команды? (что не должно изменять строки)
  2. Что может объяснить, что строки затрагиваются в 1-м случае?

EDIT

Я только что узнал, что проблема не произойдет, если я удалю раздел.

  1. С разделом я получаю "XXX затронутые строки"
  2. Без раздела я получаю "0 затронутых строк"

Ожидается ли?

РЕДАКТИРОВАТЬ 2 с РЕЗЮМЕ

Первоначально:

  1. В таблице использовалась latin1 в качестве кодировки по умолчанию (то же самое для столбцов)
  2. Соединение было объявлено как utf8

Что работает:

  1. Перед любой командой ALTER TABLE символы, подобные "é", кажутся закодированными latin1 (E9)
  2. Выполнение команды ALTER TABLE mytable CHARACTER SET utf8mb4; не изменяет данные (шестнадцатеричная команда по-прежнему показывает E9) Столбец все еще объявлен latin1.
  3. Рабочая команда ALTER TABLE mytable MODIFY COL2 LONGTEXT CHARACTER SET utf8mb4 изменяет столбец на utf8mb4 (C3A9)

Пока все хорошо.

Остальные вопросы:

  1. Как убедиться, что все данные в таблице latin1? Я пробовал SELECT COL2 FROM mytable WHERE LENGTH(COL2) != CHAR_LENGTH(COL2) LIMIT 1 и получил 0 результатов. Этого достаточно?
  2. Почему команда ALTER TABLE mytable CHARACTER SET utf8mb4; показывает «32141 строк затронуто», когда кажется, что данные не изменены? (это происходит, когда таблица имеет разделы и индекс в одном столбце)
  3. Следуя предыдущему пункту, безопасно (необходимо?) Также изменить кодировку таблицы по умолчанию? Или мне просто придерживаться модификации колонок?

Большое спасибо за вашу помощь

1 Ответ

2 голосов
/ 08 июля 2019

У вас был беспорядок, и ALTER усугубил беспорядок.

Для начала, столбцы таблицы были объявлены latin1 и , соединение объявило, что клиент использовал latin1 (через SET NAMES latin1). Это было бы хорошо, если бы é на самом деле было шестнадцатеричным E9 в клиенте. Но данные в клиенте были UTF-8. Таким образом, é было двумя байтами, C3A9 было отправлено в базу данных как 2 латинских символа. Урон не был заметен, потому что он был обратным, когда вы SELECTed.

На последующем шаге все запуталось, обработав каждый этих байтов как latin1 и преобразовав их в utf8, следовательно, "двойное" кодирование.

См. «Моджибаке» и «двойное кодирование» в Проблема с символами UTF-8; то, что я вижу, не то, что я сохранил . Если вы хотите попытаться восстановить данные, см. Соответствующий случай в http://mysql.rjweb.org/doc.php/charcoll#fixes_for_various_cases

Ну, по-видимому, ALTER TABLE mytable DEFAULT CHARACTER SET utf8; не просто изменял значение по умолчанию, но копировал таблицу, и при этом вводил двойное кодирование.

Я преследую проблемы с кодировкой MySQL уже более десяти лет. Это новая морщина, которую я еще не наблюдал.

Я почти уверен, что character_set_system не имеет отношения к вашей проблеме. (Но я могу ошибаться!)

Неверные имена SET

Контрольный пример:

CREATE TABLE mytest ( MYDATA longtext ) ENGINE=InnoDB DEFAULT CHARSET=latin1;
SET NAMES latin1;
INSERT INTO mytest VALUES ( "é" );
SELECT MYDATA, HEX(MYDATA) FROM mytest;

Запуск этого теста:

mysql> SET NAMES latin1;

mysql> SHOW CREATE TABLE mytest\G
*************************** 1. row ***************************
       Table: mytest
Create Table: CREATE TABLE `mytest` (
  `MYDATA` longtext
) ENGINE=InnoDB DEFAULT CHARSET=latin1

mysql> INSERT INTO mytest VALUES ( "é" );

mysql> SELECT MYDATA, HEX(MYDATA), LENGTH(MYDATA),
              CHAR_LENGTH(MYDATA) FROM mytest;
+--------+-------------+----------------+---------------------+
| MYDATA | HEX(MYDATA) | LENGTH(MYDATA) | CHAR_LENGTH(MYDATA) |
+--------+-------------+----------------+---------------------+
| é      | C3A9        |              2 |                   2 |
+--------+-------------+----------------+---------------------+

Персонаж выглядит отлично. Но HEX выглядит как UTF-8, а не latin1. И CHAR_LENGTH "неправильно".

Случай: CHARACTER SET latin1, но в нем есть utf8 байтов. Чтобы оставить байты в покое при исправлении кодировки:

Затем преобразовать столбец без изменения байтов:

ALTER TABLE tbl MODIFY COLUMN MYDATA LONGBLOB;
ALTER TABLE tbl MODIFY COLUMN MYDATA LONGTEXT CHARACTER SET utf8mb4;

(Убедитесь, что у вас есть все атрибуты, которые у вас изначально были, например NOT NULL.)

Это «2-ступенчатый ALTER», как описано в http://mysql.rjweb.org/doc.php/charcoll.) (Обязательно сохраните остальные спецификации такими же - VARCHAR, NOT NULL и т. Д.)

Тестовый раздел:

DROP TABLE IF EXISTS ptest;
CREATE TABLE ptest (
        nn INT NOT NULL,
        ee LONGTEXT
    ) ENGINE=InnoDB DEFAULT CHARSET=latin1
    PARTITION BY RANGE (nn)
        (PARTITION p0 VALUES LESS THAN (1),
         PARTITION p1 VALUES LESS THAN MAXVALUE);
SET NAMES latin1;
INSERT INTO ptest (nn, ee)  VALUES ( 0, "é" ), ( 1, "ü" );
SELECT nn, ee, HEX(ee), LENGTH(ee), CHAR_LENGTH(ee) FROM ptest;
ALTER TABLE ptest
    DEFAULT CHARSET utf8;
SELECT nn, ee, HEX(ee), LENGTH(ee), CHAR_LENGTH(ee) FROM ptest;
SELECT @@version;
SHOW CREATE TABLE ptest\G

Результаты разбиения:

mysql>     DROP TABLE IF EXISTS ptest;
Query OK, 0 rows affected (0.02 sec)

mysql>     CREATE TABLE ptest (
    ->             nn INT NOT NULL,
    ->             ee LONGTEXT
    ->         ) ENGINE=InnoDB DEFAULT CHARSET=latin1
    ->         PARTITION BY RANGE (nn)
    ->             (PARTITION p0 VALUES LESS THAN (1),
    ->              PARTITION p1 VALUES LESS THAN MAXVALUE);
Query OK, 0 rows affected (0.03 sec)

mysql>     SET NAMES latin1;
Query OK, 0 rows affected (0.00 sec)

mysql>     INSERT INTO ptest (nn, ee)  VALUES ( 0, "é" ), ( 1, "ü" );
Query OK, 2 rows affected (0.00 sec)
Records: 2  Duplicates: 0  Warnings: 0

mysql>     SELECT nn, ee, HEX(ee), LENGTH(ee), CHAR_LENGTH(ee) FROM ptest;
+----+------+---------+------------+-----------------+
| nn | ee   | HEX(ee) | LENGTH(ee) | CHAR_LENGTH(ee) |
+----+------+---------+------------+-----------------+
|  0 | é    | C3A9    |          2 |               2 |
|  1 | ü    | C3BC    |          2 |               2 |
+----+------+---------+------------+-----------------+
2 rows in set (0.00 sec)

mysql>     ALTER TABLE ptest
    ->         DEFAULT CHARSET utf8;
Query OK, 0 rows affected (0.01 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql>     SELECT nn, ee, HEX(ee), LENGTH(ee), CHAR_LENGTH(ee) FROM ptest;
+----+------+---------+------------+-----------------+
| nn | ee   | HEX(ee) | LENGTH(ee) | CHAR_LENGTH(ee) |
+----+------+---------+------------+-----------------+
|  0 | é    | C3A9    |          2 |               2 |
|  1 | ü    | C3BC    |          2 |               2 |
+----+------+---------+------------+-----------------+
2 rows in set (0.00 sec)

mysql>     SELECT @@version;
+-----------------+
| @@version       |
+-----------------+
| 5.6.22-71.0-log |
+-----------------+
1 row in set (0.00 sec)

mysql>     SHOW CREATE TABLE ptest\G
*************************** 1. row ***************************
       Table: ptest
Create Table: CREATE TABLE `ptest` (
  `nn` int(11) NOT NULL,
  `ee` longtext CHARACTER SET latin1
) ENGINE=InnoDB DEFAULT CHARSET=utf8
/*!50100 PARTITION BY RANGE (nn)
(PARTITION p0 VALUES LESS THAN (1) ENGINE = InnoDB,
 PARTITION p1 VALUES LESS THAN MAXVALUE ENGINE = InnoDB) */
1 row in set (0.00 sec)

Хммм ... Я не вижу проблемы ALTER. Какую версию ты используешь? Вы видите проблему с этим контрольным примером?

...