前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【转】迁移到 utf8mb4:需要考虑的事项

【转】迁移到 utf8mb4:需要考虑的事项

作者头像
保持热爱奔赴山海
发布2022-06-02 09:47:01
5400
发布2022-06-02 09:47:01
举报
文章被收录于专栏:饮水机管理员饮水机管理员

原文来自Percona Blog: ​​https://www.percona.com/blog/migrating-to-utf8mb4-things-to-consider/​

utf8mb4字符集是 MySQL 8.0 的新默认字符集,此更改既不会影响现有数据,也不会强制进行任何升级

迁移到utf8mb4有很多优点,包括:

不过,您可能想知道迁移如何影响您现有的数据。该博客涵盖了它的多个方面。

存储要求

顾名思义,使用字符集utf8mb4的一个字符可以占用的最大字节数是 4 个字节。这比utf8mb3的要求要大,后者需要三个字节和许多其他 MySQL 字符集。

幸运的是,utf8mb3是 utf8mb4 的子集,现有数据的迁移不会增加存储在磁盘上的数据大小:每个字符根据需要占用多少字节。例如,拉丁字母表中的任何数字或字母都需要一个字节。来自其他字母的字符最多可以占用四个字节。这可以通过一个简单的测试来验证。

代码语言:javascript
复制
SET NAMES utf8mb4; 

CREATE TABLE charset_len(
  id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY ,  
  `name` VARCHAR(255), val CHAR(1) 
) CHARACTER SET=utf8mb4;

INSERT INTO charset_len (`name`,val) 
VALUES('Latin A', 'A'),  
('Cyrillic А', 'А'), 
('Korean ㉿', '㉿'), 
('Dolphin 🐬', '🐬');

INSERT INTO charset_len (`name`,val) SELECT `name`,val FROM charset_len ;

SELECT * FROM charset_len LIMIT 10 ;

【转】迁移到 utf8mb4:需要考虑的事项_mysql
【转】迁移到 utf8mb4:需要考虑的事项_mysql

SELECT `name`, val, HEX(val), BIT_LENGTH(val)/8 FROM charset_len LIMIT 10 ;

【转】迁移到 utf8mb4:需要考虑的事项_mysql_02
【转】迁移到 utf8mb4:需要考虑的事项_mysql_02

因此,您所有最多使用三个字节的数据都不会改变,您将能够存储需要 4 字节编码的字符。

列的最大长度

虽然数据存储没有改变,但当 MySQL 计算列可以存储的最大数据量时,对于某些适用于utf8mb3的列大小定义,它可能会失败。例如,您可以有一个具有以下定义的表:

代码语言:javascript
复制
mysql🐬> CREATE TABLE len_test(
      -> foo VARCHAR(16384)
      -> ) ENGINE=InnoDB CHARACTER SET utf8mb3;
Query OK, 0 rows affected, 1 warning (0,06 sec)

如果您决定将此表转换为使用utf8mb4字符集,则操作将失败:

代码语言:javascript
复制
mysql🐬> ALTER TABLE len_test CONVERT TO CHARACTER SET utf8mb4;
ERROR 1074 (42000): Column length too big for column 'foo' (max = 16383); use BLOB or TEXT instead

原因是 MySQL 可以在VARCHAR列中存储的最大字节数为 65,535,即 utf8mb3 字符集为 21845 个字符,utf8mb4字符集为 16383字符。

因此,如果您的列可能包含超过 16383 个字符,则需要将它们转换为TEXTLONGTEXT数据类型。

如果您运行查询,您可以找到所有此类列:

代码语言:javascript
复制
SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME,
       CHARACTER_MAXIMUM_LENGTH, DATA_TYPE
FROM information_schema.columns
WHERE CHARACTER_MAXIMUM_LENGTH > 16383 AND
      DATA_TYPE NOT LIKE '%text%' AND 
      DATA_TYPE NOT LIKE '%blob%' AND
      TABLE_SCHEMA NOT IN ('mysql', 'information_schema', 'performance_schema');


例如,在我的测试环境中,它返回:
*************************** 1. row ***************************
TABLE_SCHEMA: test
TABLE_NAME: setup
COLUMN_NAME: value
CHARACTER_MAXIMUM_LENGTH: 20000
DATA_TYPE: varchar
1 row in set (0,02 sec

索引存储要求

MySQL 事先并不知道在创建索引时将在列中存储哪些字符。因此,当它计算索引所需的存储空间时,它会取所选字符集的最大值。因此,当从另一个字符集转换为utf8mb4时,您可能会达到索引存储限制。对于 InnoDB,索引的最大大小对于REDUNDANTCOMPACT行格式为 767 字节,对于DYNAMIC和COMPRESSED行格式为 3072 字节。有关详细信息,请参阅​用户参考手册​

这意味着您需要在执行更新之前检查是否有可能增长到超过这些值的索引。您可以使用以下查询执行此操作:

代码语言:javascript
复制
WITH indexes AS (
     WITH tables AS  (
          SELECT SUBSTRING_INDEX(t.NAME, '/', 1) AS `database`, SUBSTRING_INDEX(t.NAME, '/', -1) AS `table`, i.NAME AS `index`, ROW_FORMAT
          FROM information_schema.INNODB_INDEXES i JOIN information_schema.INNODB_TABLES t USING(TABLE_ID)
    )
    SELECT `database`, `table`, `index`, ROW_FORMAT, GROUP_CONCAT(kcu.COLUMN_NAME) AS columns,
           SUM(c.CHARACTER_MAXIMUM_LENGTH) * 4 AS index_len_bytes
    FROM tables JOIN information_schema.KEY_COLUMN_USAGE kcu
         ON (`database` = TABLE_SCHEMA AND `table` = kcu.TABLE_NAME AND `index` = kcu.CONSTRAINT_NAME)
         JOIN information_schema.COLUMNS c ON (kcu.COLUMN_NAME = c.COLUMN_NAME AND `database` = c.TABLE_SCHEMA AND `table` = c.TABLE_NAME)
    WHERE c.CHARACTER_MAXIMUM_LENGTH IS NOT NULL
    GROUP BY `database`, `table`, `index`, ROW_FORMAT ORDER BY index_len_bytes
) SELECT * FROM indexes WHERE index_len_bytes >= 768;


这是在我的测试环境中运行查询的结果:
+----------+--------------+---------+------------+------------+-----------------+
| database | table        | index   | ROW_FORMAT | columns    | index_len_bytes |
+----------+--------------+---------+------------+------------+-----------------+
| cookbook | hitcount     | PRIMARY | Dynamic    | path       |            1020 |
| cookbook | phrase       | PRIMARY | Dynamic    | phrase_val |            1020 |
| cookbook | ruby_session | PRIMARY | Dynamic    | session_id |            1020 |
+----------+--------------+---------+------------+------------+-----------------+
3 rows in set (0,04 sec)

一旦您确定了此类索引,请检查列并相应地调整表定义。

注意:查询使用​​CTE​​,自 MySQL 8.0 起可用。如果您仍在使用 5.7 或更早版本,则需要重写查询。

补充,可以看这个例子:

代码语言:javascript
复制
mysql> show create table sb1 \G
*************************** 1. row ***************************
       Table: sb1
Create Table: CREATE TABLE `sb1` (
  `name` varchar(900) DEFAULT NULL,
  KEY `idx_name` (`name`(768))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)

-- 使用严格的sql_mode 看下效果
mysql> set sql_mode='STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION';
Query OK, 0 rows affected (0.00 sec)

mysql> show create table sb1 \G^L
*************************** 1. row ***************************
       Table: sb1
Create Table: CREATE TABLE `sb1` (
  `name` varchar(900) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)

mysql> ALTER TABLE sb1 ADD INDEX idx_name(`name`);
ERROR 1071 (42000): Specified key was too long; max key length is 3072 bytes

mysql> show create table sb1 \G
*************************** 1. row ***************************
       Table: sb1
Create Table: CREATE TABLE `sb1` (
  `name` varchar(900) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)

mysql> ALTER TABLE sb1 ADD INDEX idx_name(`name`);
ERROR 1071 (42000): Specified key was too long; max key length is 3072 bytes

mysql> select 3072/4;   -- utf8mb4每个会占4bytes,因此支持的最大index长度是768
+----------+
| 3072/4   |
+----------+
| 768.0000 |
+----------+
1 row in set (0.00 sec)

mysql> ALTER TABLE sb1 ADD INDEX idx_name(`name`(768));
Query OK, 0 rows affected (0.02 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> show create table sb1 \G
*************************** 1. row ***************************
       Table: sb1
Create Table: CREATE TABLE `sb1` (
  `name` varchar(900) DEFAULT NULL,
  KEY `idx_name` (`name`(768))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)


对于对个列的情况:
CREATE TABLE `sb2` (
  `name1` VARCHAR(300) DEFAULT NULL,
  `name2` VARCHAR(800) DEFAULT NULL,
  `name3` VARCHAR(768) DEFAULT NULL
  ) ENGINE=INNODB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

mysql> set sql_mode='NO_ENGINE_SUBSTITUTION';
Query OK, 0 rows affected (0.00 sec)

-- 在sql_mode严格情况下直接报错,不严格模式下,会根据允许的最大长度来创建(也就是name2(768)
ALTER TABLE sb2 ADD INDEX idx_name2(`name2`); 

-- 这个768长度满足最大限制,可以正常创建
ALTER TABLE sb2 ADD INDEX idx_name3(`name3`);

mysql> show create table sb2 \G
CREATE TABLE `sb2` (
  `name1` VARCHAR(300) DEFAULT NULL,
  `name2` VARCHAR(800) DEFAULT NULL,
  `name3` VARCHAR(768) DEFAULT NULL,
  KEY `idx_name2` (`name2`(768)),
  KEY `idx_name3` (`name3`)
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

临时表

转换为utf8mb4字符集后您可能遇到的另一个问题是: MySQL 创建以解决查询的隐式临时表的大小增加。由于utf8mb4可能比其他字符集存储更多的数据,因此此类隐式表的列大小也会更大。要确定您是否受到此问题的影响,请查看全局状态变量Created_tmp_disk_tables。如果迁移后这开始显着增加,您可以考虑更新计算机上的 RAM 并增加临时表的最大大小。请注意,此问题可能是您的某些查询优化不佳的症状。

结论

转换为utf8mb4字符集可为您带来更好的性能、更大范围的字符可供您使用,包括表情符号和新的排序规则(排序规则)。这种转换几乎没有代价,而且可以顺利完成。

确保:

  • 您将所有可以存储超过 16383 个字符的VARCHAR列转换为TEXTLONGTEXT数据类型
  • 您调整了索引定义,对于REDUNDANTCOMPACT行格式,迁移后可能需要超过 767 个字节,而对于DYNAMICCOMPRESSED行格式,可能需要3072 个字节。
  • 您优化了查询,使它们不应该开始使用基于内部磁盘的临时表

相关阅读:

​https://dev.mysql.com/blog-archive/mysql-8-0-collations-migrating-from-older-collations/​

​https://dev.mysql.com/blog-archive/mysql-8-0-collations-migrating-from-older-collations-part-2/​

​https://dev.mysql.com/blog-archive/mysql-8-0-when-to-use-utf8mb3-over-utf8mb4/​

​https://dev.mysql.com/doc/refman/8.0/en/innodb-row-format.html​

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2022-05-31,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 存储要求
  • 列的最大长度
  • 索引存储要求
  • 临时表
  • 结论
相关产品与服务
对象存储
对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档