文章851
标签121
分类10

Mysql 5.7 升级到 Mysql 8.0

2024-11-22T08:55:23.png
升级前的准备工作实际上是为了消除升级程序中无法自动处理的“不兼容的”变化

  • MySQL 8.0引入了全新的数据字典用于保存数据库中的元信息,Server层和InnoDB层共享一份元数据。MySQL 8.0的information_schema中的视图全部源自于数据字典表,与InnoDB相关的视图被重命名(INNODB_SYS_XXX重命名为INNODB_XXX)。若用户的应用依赖于这些视图,需要确保应用已做出相应的修改。在升级前需要确认业务是否依赖于MySQL 8.0中删除的系统表(如mysql.procmysql.event等),若存在则需使用新的访问方式去获取所需的数据。此外,对于和MySQL 8.0系统表同名的用户表(如catalogsroutines等),需要手动执行RENAME/DROP TABLE操作。
  • RDS MySQL 8.0不再支持部分旧版本数据类型。如果表中字段含有MySQL 8.0不支持的数据类型,需要在升级前通过REPAIR TABLE或逻辑导出+导入的方式修复。更多信息请参见准备升级安装。
  • MySQL 8.0中,分区表的处理已由Server层下沉至引擎层,MySQL Server不再支持通用分区。Non-native的分区表在MySQL 8.0中被废弃,因此需要将这类分区表改为Native类型(如InnoDB引擎)或是删除该分区表。
  • 较早版本的MySQL 5.7(5.0.17之前的版本)的触发器(Triggers)不支持DEFINER属性,因此在MySQL 5.7实例中可能会存在缺失DEFINER属性的触发器,这些触发器会导致升级MySQL 8.0失败。在升级前您需要重新创建这些缺失DEFINER属性的触发器。
  • MySQL 8.0中,外键约束的长度存在限制,如果外键约束过长,在升级过程中可能会导致错误和失败。因此,在进行升级之前,需要将问题外键修改为长度小于64个字符的约束。
  • MySQL 8.0之前版本,视图的列名长度上限为255个字符,而在MySQL 8.0版本中,视图的列名长度上限缩短至64个字符。因此,在进行升级之前,需要将视图中过长的列名修改为长度小于64个字符的列名。
  • MySQL 8.0中,单个ENUM和SET列元素的长度不得超过255个字符或1020个字节,因此在升级之前需要修改超出限制的ENUMSET
  • 如果.frm文件和InnoDB数据字典表元数据信息不一致,那么会导致升级错误。因此,在进行升级之前,需要对数据进行逻辑导出和导入。若存在游离的.frm文件(即不含.ibd文件仅有.frm文件),则需要进行相应清理。
  • MySQL 8.0废弃了部分空间函数。如果生成列中包含了已删除的函数(新引入的空间函数以"ST"和"MBR"开头),则需要在升级前对相应的列进行修改。
  • 在进行升级前,必须确保MySQL 5.7的实例已经进行了正确的关闭,即需要确保在升级前不存在待应用的REDO日志和等待回滚的事务。
  • MySQL 8.0中引入了一些新的保留字,其中大多数禁止用作表名、列名等,因此需要对MySQL 5.7中包含的MySQL 8.0引入的新保留字做处理。
  • 在升级前,需要将废弃的sql_mode变量内容(例如NO_AUTO_CREATE_USER等)修改为MySQL 8.0支持的模式,以避免导致实例无法启动。
  • 较新版本的MySQL 8.0实例(8.0.13及之后的版本),共享表空间(系统表空间和通用表空间)中不允许存在InnoDB类型的分区表,因此在进行升级前需要将共享表空间移动至独立表空间。
  • MySQL 8.0实例中,GROUP BY子句不支持ASCDESC排序规则,因此,在进行升级前,需要修改或删除包含相应句法的存储过程。
  • 在MySQL 5.7中,lower_case_table_names是一个可修改的值,但在MySQL 8.0中,lower_case_table_names是一个初始化参数,一旦实例初始化完成,就无法在后续进行修改。在进行升级时,若需要将该参数值修改为1,请确保升级前库表名称为小写,以避免出现升级错误。
  • MySQL 5.7和MySQL 8.0的字符集(character set)和排序规则(collation)的配置可能存在差异,这可能导致索引失效、查询报错等问题,例如:

    • MySQL 8.0默认字符集为utf8mb4,使用1~4字节存储一个字符,MySQL 5.7默认字符集为utf8mb3,使用1~3字节存储一个字符,因此字段和索引长度会受到影响。在REDUNDANT或者COMPACT行格式下,InnoDB引擎所允许的最大索引长度为767字节,对应在5.7中索引允许的最大字符数是255,而在8.0中则无法创建超过191字符的索引。
    • 当在MySQL 5.7中使用了utf8mb3字符集创建表,如果升级MySQL 8.0后默认使用的字符集为utf8mb4,新建表在没有指定character set时默认使用 utf8mb4。当新表(utf8mb4)和迁移表(utf8mb3)相关字段发生JOIN时,会因为JOIN两端字符集类型不一致导致索引失效。
    • 当在MySQL 5.7中使用了utf8mb4_general_ci排序规则创建表,如果升级到MySQL 8.0后,默认使用的排序规则为utf8mb4_0900_ai_ci,新建表在没有指定collation时默认使用utf8mb4_0900_ai_ci。由于utf8mb4_general_ciutf8mb4_0900_ai_ci优先级相同(无法选择排序规则),当新表和迁移表相关字段发生JOIN时,会因为JOIN两端排序规则不兼容导致报错。

为避免出现字符集引发的问题,在升级前需要检查MySQL中的字符集和排序规则使用情况,您也可以通过下述SQL来修改库、表、字段的字符集和排序规则。

# 修改库的字符集和排序规则
ALTER DATABASE database_name CHARACTER SET = charset COLLATE = collation;
# 修改表的字符集和排序规则
ALTER TABLE table_name CONVERT TO CHARACTER SET charset COLLATE collation;
# 修改字段的字符集和排序规则
ALTER TABLE table_name CHANGE column_name column_name type CHARACTER SET charset COLLATE collation;

重要

  • 修改列的charset时,MySQL会尝试映射数据值,但如果修改前后charset不兼容,可能会发生数据丢失。
  • 在阿里云RDS MySQL中,各版本默认字符集均使用utf8mb3,默认排序规则均使用utf8mb3_general_ci,如果没有手动修改过字符集和排序规则,大版本升级前后不会出现此类问题。
  • 更多信息可参考:调整实例character_set_server参数和collation_server参数和MySQL 社区字符集配置。

参考文献 : https://help.aliyun.com/zh/rds/apsaradb-rds-for-mysql/rds-mysql-helps-mysql-5-7-upgrade-8-0

MySQL表字段字符集不同导致的索引失效问题

原文地址 : https://mp.weixin.qq.com/s/ns9eRxjXZfUPNSpfgGA7UA

1. 概述

昨天在一位同学的MySQL机器上面发现了这样一个问题,MySQL两张表做left join时,执行计划里面显示有一张表使用了全表扫描,扫描全表近100万行记录,大并发的这样的SQL过来数据库变得几乎不可用了。MySQL版本为官方5.7.12。

2. 问题重现

首先,表结构和表记录如下:

mysql> show create table t1\G
*************** 1. row ***************
Table: t1
Create Table: CREATE TABLE `t1` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(20) DEFAULT NULL,
`code` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_code` (`code`),
KEY `idx_name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8
1 row in set (0.00 sec)

mysql> show create table t2\G
*********** 1. row *******************
Table: t2
Create Table: CREATE TABLE `t2` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(20) DEFAULT NULL,
`code` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_code` (`code`),
KEY `idx_name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4
1 row in set (0.00 sec)

mysql> select * from t1;
+—-+——+———————————-+
| id | name | code |
+—-+——+———————————-+
| 1 | aaaa | ...... |
| 2 | bbbb | ...... |
| 3 | cccc | ...... |
| 4 | dddd | ...... |
| 5 | eeee | ...... |
+—-+——+———————————-+
5 rows in set (0.00 sec)

mysql> select * from t2;
+—-+——+———————————-+
| id | name | code |
+—-+——+———————————-+
| 1 | aaaa | ...... |
| 2 | bbbb | ...... |
| 3 | cccc | ...... |
| 4 | dddd | ...... |
| 5 | eeee | ...... |
+—-+——+———————————-+
5 rows in set (0.00 sec)

2张表 left join 的执行计划如下:

mysql> desc select * from t2 left join t1 on t1.code = t2.code where t2.name = 'dddd'\G
******************* 1. row ****************
id: 1
select_type: SIMPLE
table: t2
partitions: NULL
type: ref
possible_keys: idx_name
key: idx_name
key_len: 83
ref: const
rows: 1
filtered: 100.00
Extra: NULL
****************** 2. row **************
id: 1
select_type: SIMPLE
table: t1
partitions: NULL
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 5
filtered: 100.00
Extra: Using where; Using join buffer (Block Nested Loop)
2 rows in set, 1 warning (0.01 sec)

可以明显地看到,t2.name = ‘dddd’使用了索引,而t1.code = t2.code这个关联条件没有使用到t1.code上面的索引,一开始Scott也百思不得其解,但是机器不会骗人。Scottshow warnings查看改写后的执行计划如下:

mysql> show warnings;

| Level | Code | Message |
| Note | 1003 | /* select#1 */ select `testdb`.`t2`.`id` AS `id`,`testdb`.`t2`.`name` AS `name`,`testdb`.`t2`.`code` AS `code`,`testdb`.`t1`.`id` AS `id`,`testdb`.`t1`.`name` AS `name`,`testdb`.`t1`.`code` AS `code` from `testdb`.`t2` left join `testdb`.`t1` on((convert(`testdb`.`t1`.`code` using utf8mb4) = `testdb`.`t2`.`code`)) where (`testdb`.`t2`.`name` = 'dddd') |

1 row in set (0.00 sec)

在发现了 convert(testdb.t1.code using utf8mb4)之后,Scott发现2个表的字符集不一样。t1为utf8,t2为utf8mb4。但是为什么表字符集不一样(实际是字段字符集不一样)就会导致t1全表扫描呢?下面来做分析。

(1)首先t2 left join t1决定了t2是驱动表,这一步相当于执行了select * from t2 where t2.name = ‘dddd’,取出code字段的值,这里为’8a77a32a7e0825f7c8634226105c42e5’;

(2)然后拿t2查到的code的值根据join条件去t1里面查找,这一步就相当于执行了select * from t1 where t1.code = ‘8a77a32a7e0825f7c8634226105c42e5’;

(3)但是由于第(1)步里面t2表取出的code字段是utf8mb4字符集,而t1表里面的code是utf8字符集,这里需要做字符集转换,字符集转换遵循由小到大的原则,因为utf8mb4是utf8的超集,所以这里把utf8转换成utf8mb4,即把t1.code转换成utf8mb4字符集,转换了之后,由于t1.code上面的索引仍然是utf8字符集,所以这个索引就被执行计划忽略了,然后t1表只能选择全表扫描。更糟糕的是,如果t2筛选出来的记录不止1条,那么t1就会被全表扫描多次,性能之差可想而知。

3. 问题解决

既然原因已经清楚了,如何解决呢?当然是改字符集了,把t1改成和t2一样或者把t2改成t1都可以,这里选择把t1转成utf8mb4。那怎么转字符集呢?

有的同学会说用alter table t1 charset utf8mb4;但这是错的,这只是改了表的默认字符集,即新的字段才会使用utf8mb4,已经存在的字段仍然是utf8

mysql> alter table t1 charset utf8mb4;
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0

mysql> show create table t1\G
************** 1. row ***************
Table: t1
Create Table: CREATE TABLE `t1` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(20) CHARACTER SET utf8 DEFAULT NULL,
`code` varchar(50) CHARACTER SET utf8 DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_code` (`code`),
KEY `idx_name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4
1 row in set (0.00 sec)

只有用alter table t1 convert to charset utf8mb4;才是正确的。

但是还要注意一点,alter table 改字符集的操作是阻塞写的(用lock = node会报错)所以业务高峰时请不要操作,即使在业务低峰时期,大表的操作仍然建议使用pt-online-schema-change在线修改字符集。

mysql> alter table t1 convert to charset utf8mb4, lock=none;
ERROR 1846 (0A000): LOCK=NONE is not supported. Reason: Cannot change column type INPLACE. Try LOCK=SHARED.
mysql> alter table t1 convert to charset utf8mb4, lock=shared;
Query OK, 5 rows affected (0.04 sec)
Records: 5 Duplicates: 0 Warnings: 0

mysql> show create table t1\G
******************** 1. row **************
Table: t1
Create Table: CREATE TABLE `t1` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(20) DEFAULT NULL,
`code` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_code` (`code`),
KEY `idx_name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4
1 row in set (0.00 sec)

现在再来查看执行计划,可以看到已经没问题了。

mysql> desc select * from t2 join t1 on t1.code = t2.code where t2.name = 'dddd'\G
******** 1. row ******************
id: 1
select_type: SIMPLE
table: t2
partitions: NULL
type: ref
possible_keys: idx_code,idx_name
key: idx_name
key_len: 83
ref: const
rows: 1
filtered: 100.00
Extra: Using where
********* 2. row *************
id: 1
select_type: SIMPLE
table: t1
partitions: NULL
type: ref
possible_keys: idx_code
key: idx_code
key_len: 203
ref: testdb.t2.code
rows: 1
filtered: 100.00
Extra: NULL
2 rows in set, 1 warning (0.00 sec)

4. 注意点

(1)表字符集不同时,可能导致join的SQL使用不到索引,引起严重的性能问题;

(2)SQL上线前要做好SQL Review工作,尽量在和生产环境一样的环境下Review;

(3)改字符集的alter table操作会阻塞写,尽量在业务低峰操作,建议用pt-online-schema-change;

(4)表结构字符集要保持一致,发布时要做好审核工作;

(5)如果要大批量修改表的字符集,同样做好SQL的Review工作,关联的表的字符集一起做修改。

5. 问题讨论

最后问一个问题,假设现在t1和t2表的字符集还未修改,如果上面那个问题SQL换成如下(即把t2 left join t1换成t1 left join t2),还会出现索引失效问题吗?为什么?

select * from t1 join t2 on t1.code = t2.code where t1.name = 'dddd'

MySQL用错编码怎么救

2024-11-22T08:29:33.png

  1. 备份,不然崩了就只有删库跑路了;
  2. 升级MySQL服务端到5.3.3及以上版本,以支持utf8md4
  3. 将数据库、表、列的字符编码、collation改为utf8md4:

# For each database:
ALTER DATABASE database_name CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
# For each table:
ALTER TABLE table_name CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
# For each column:
ALTER TABLE table_name CHANGE column_name column_name VARCHAR(length) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
  1. 检查列和索引键的最大长度;
  2. 修改连接、客户端、服务端的字符集;
  3. 修复和优化所有的表,以免出现一些莫名其妙的错误,可以使用如下的方式:
# For each table
REPAIR TABLE table_name;
OPTIMIZE TABLE table_name;

或者是使用mysqlcheck工具:

$ mysqlcheck -u root -p --auto-repair --optimize --all-databases

Git 忽略文件权限修改

Git 忽略文件权限修改

当前版本库

$ git config core.filemode false  

所有版本库

$ git config --global core.fileMode false

Composer 依赖管理

简介

ComposerPHP 的一个依赖管理工具。它允许你申明项目所依赖的代码库,它会在你的项目中为你安装他们。

依赖管理

Composer 不是一个包管理器。是的,它涉及 "packages" 和 "libraries",但它在每个项目的基础上进行管理,在你项目的某个目录中(例如 vendor)进行安装。默认情况下它不会在全局安装任何东西。因此,这仅仅是一个依赖管理。

这种想法并不新鲜,Composer 受到了 node's [npm][1] 和 ruby's bundler 的强烈启发。而当时 PHP 下并没有类似的工具。

Composer 将这样为你解决问题:

a) 你有一个项目依赖于若干个库。

b) 其中一些库依赖于其他库。

c) 你声明你所依赖的东西。

d) Composer 会找出哪个版本的包需要安装,并安装它们(将它们下载到你的项目中)。

声明依赖关系

比方说,你正在创建一个项目,你需要一个库来做日志记录。你决定使用 monolog。为了将它添加到你的项目中,你所需要做的就是创建一个 composer.json 文件,其中描述了项目的依赖关系。

{
    "require": {
        "monolog/monolog": "1.2.*"
    }
}

我们只要指出我们的项目需要一些 monolog/monolog 的包,从 1.2 开始的任何版本。

系统要求

运行 Composer 需要 PHP 5.3.2+ 以上版本。一些敏感的 PHP 设置和编译标志也是必须的,但对于任何不兼容项安装程序都会抛出警告。

我们将从包的来源直接安装,而不是简单的下载 zip 文件,你需要 git 、 svn 或者 hg ,这取决于你载入的包所使用的版本管理系统。

Composer 是多平台的,我们努力使它在 Windows 、 Linux 以及 OSX 平台上运行的同样出色。

安装 - *nix

下载 Composer 的可执行文件

局部安装
要真正获取 Composer,我们需要做两件事。首先安装 Composer (同样的,这意味着它将下载到你的项目中):

curl -sS https://getcomposer.org/installer | php
注意: 如果上述方法由于某些原因失败了,你还可以通过 php >下载安装器:
php -r "readfile('https://getcomposer.org/installer');" | php

这将检查一些 PHP 的设置,然后下载 composer.phar 到你的工作目录中。这是 Composer 的二进制文件。这是一个 PHAR 包(PHP 的归档),这是 PHP 的归档格式可以帮助用户在命令行中执行一些操作。

你可以通过 --install-dir 选项指定 Composer 的安装目录(它可以是一个绝对或相对路径):

curl -sS https://getcomposer.org/installer | php -- --install-dir=bin

全局安装

你可以将此文件放在任何地方。如果你把它放在系统的 PATH 目录中,你就能在全局访问它。 在类Unix系统中,你甚至可以在使用时不加 php 前缀。

你可以执行这些命令让 composer 在你的系统中进行全局调用:

curl -sS https://getcomposer.org/installer | php
mv composer.phar /usr/local/bin/composer
注意: 如果上诉命令因为权限执行失败, 请使用 sudo 再次尝试运行 mv 那行命令。

现在只需要运行 composer 命令就可以使用 Composer 而不需要输入 php composer.phar

全局安装 (on OSX via homebrew)
Composer 是 homebrew-php 项目的一部分。

brew update
brew tap josegonzalez/homebrew-php
brew tap homebrew/versions
brew install php55-intl
brew install josegonzalez/php/composer

安装 - Windows

使用安装程序

这是将 Composer 安装在你机器上的最简单的方法。

下载并且运行 [Composer-Setup.exe][3],它将安装最新版本的 Composer ,并设置好系统的环境变量,因此你可以在任何目录下直接使用 composer 命令。

手动安装

设置系统的环境变量 PATH 并运行安装命令下载 composer.phar 文件:

C:\Users\username>cd C:\bin
C:\bin>php -r "readfile('https://getcomposer.org/installer');" | php
注意: 如果收到 readfile 错误提示,请使用 http 链接或者在 php.ini 中开启 php_openssl.dll

composer.phar 同级目录下新建文件 composer.bat

C:\bin>echo @php "%~dp0composer.phar" %*>composer.bat

关闭当前的命令行窗口,打开新的命令行窗口进行测试:

C:\Users\username>composer -V
Composer version 27d8904

使用 Composer

现在我们将使用 Composer 来安装项目的依赖。如果在当前目录下没有一个 composer.json 文件,请查看基本用法章节。

要解决和下载依赖,请执行 install 命令:

php composer.phar install

如果你进行了全局安装,并且没有 phar 文件在当前目录,请使用下面的命令代替:

composer install

继续 上面的例子,这里将下载 monolog 到 vendor/monolog/monolog 目录。

自动加载

除了库的下载,Composer 还准备了一个自动加载文件,它可以加载 Composer 下载的库中所有的类文件。使用它,你只需要将下面这行代码添加到你项目的引导文件中:

require 'vendor/autoload.php';

Composer 是 PHP 的一个依赖管理工具

">