RDS for MySQL 字符序(collation)引发的性能问题

在帮客户排查问题的时候,经常会遇到的 RDS 实例性能问题(比如 RDS 实例 CPU 使用率高),而其中有一类是由于字符集的字符排序规则不一致导致的。从处理的过程中可以看出来,这类问题比较容易出现但不容易定位排查,所以今天通过两个实战案例来分析的下“RDS for MySQL 字符序(collation)引发的性能问题”。

首先介绍下背景知识: 字符集 和 字符序。

1. 字符集(character set)和字符序(collation)

字符集是一组符号和编码,用来保存和解释 MySQL 的字符类型数据,比如 varchar 类型的数据。
字符序是一组在指定字符集中进行字符比较的规则,比如是否忽略大小写,是否按二进制比较字符等等。

2. 字符序基本比较规则

两组字符类型数据进行比较,需要一致的字符集(character set)和 字符序(collation),否则需要进行隐式转换。

3. 实战案例分析

  • 案例分析一:实例 CPU 使用率达到 100%,业务响应时间长,影响使用体验。

问题原因定位到一条普通查询语句:

select
    aid, ip, adid, openudid
from
    `tab01`
where
    `reg_time` between '2016-10-12 00:00:00' and '2016-10-12 23:59:59'

该语句在上线前通过 MySQL 命令行进行过测试,执行时间在 20 MS(毫秒)左右。
但在生产环境由 PHP Lavravel 框架提交执行需要 20 Sec(秒)以上才可以完成; 大量该类型查询执行导致连接堆积,RDS 实例 CPU 使用率 100%。

首先在 MySQL 命令行下,检查表结构:

CREATE TABLE `tab01` (
    `id` int(11) NOT NULL AUTO_INCREMENT,
    `reg_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
    `ip` char(15) NOT NULL,
    `aid` bigint(20) NOT NULL,
    `adid` varchar(255) NOT NULL,
    `openudid` varchar(255) NOT NULL,
    PRIMARY KEY (`id`),
    KEY `reg_time` (`reg_time`),
    KEY `aid` (`aid`),
    KEY `adid` (`adid`) USING BTREE
)  ENGINE=InnoDB AUTO_INCREMENT=11964136 DEFAULT CHARSET=utf8

检查执行计划,未见异常:
 
请用户协助捕捉 PHP Laravel 框架提交查询的网络通信过程:


在网络交互过程中,发现应用在连接建立后执行了下面的语句,然后间隔部分其他查询后才执行的上述查询:

set names utf8 collate utf8_unicode_ci;

那么这条命令具体修改了什么,可以通过 MySQL 命令行连接来模拟验证下 :

可以看到,该条命令将连接的字符序(collation_connection)从 utf8_general_ci (默认值)修改为 utf8_unicode_ci ;而表中数据使用的是默认字符序(utf8_general_ci,在表的 create 定义语句中如果没有指定,则使用字符集的默认字符序),两者并不相同。
注:
    RDS for MySQL 支持的字符序可以通过下面的命令获取:

-- 查看 RDS for MySQL 支持的所有字符序
show collation;

-- 查看 RDS for MySQL 支持的某一字符集对应的字符序
show collation like 'utf8%';

 

在修改了字符序后,语句的执行计划就变为全索引扫描:


请注意查询的执行成本由 8427 改变为 13771569,增加了 1633 倍。

修改框架的字符序设置后,查询执行时间恢复正常,RDS 实例 CPU 使用率过高的问题解决。

案例分析二:RDS 实例 CPU 使用率波动性打高,导致业务卡顿。

定位到下面的查询,检查语句执行计划,发现优化器对表 tab03 选择了全表扫描的方式来访问数据。

explain
SELECT
    r.org_no,
    r.cp_no,
    r.NAME cp_name
    FROM
        tab02 r
    LEFT JOIN tab03 a ON r.cp_no = a.cp_no
        AND A.SHARD_NO =  r.shard_no
    WHERE
        r.shard_no = '41401'
            AND r.org_no LIKE '41401%'
            limit 100;
+----+-------------+-------+------+---------------------------------------------+-------------------------+---------+-------+-------+------------------------------------------------+
| id | select_type | table | type | possible_keys                               | key                     | key_len | ref   | rows  | Extra                                          |
+----+-------------+-------+------+---------------------------------------------+-------------------------+---------+-------+-------+------------------------------------------------+
| 1  | SIMPLE      | r     | ref  | auto_shard_key_shard_no                     | auto_shard_key_shard_no | 99      | const | 30637 | Using index condition; Using where             |
| 1  | SIMPLE      | a     | ALL  | R_CP_TAB03_UK,auto_shard_key_shard_no       |                         |         |       | 13221 | Range checked for each record (index map: 0xA) |
+----+-------------+-------+------+---------------------------------------------+-------------------------+---------+-------+-------+------------------------------------------------+
共返回 2 行记录,花费 2.23 ms.

而表 tab03 上有合适的唯一索引 R_CP_TAB03_UK:

CREATE TABLE `tab03` (
    `TERMINAL_ID` bigint(16) NOT NULL,
    `CP_NO` varchar(16) NOT NULL,
    `CP_NAME` varchar(256) DEFAULT NULL,
    `DATA_SRC` varchar(8) DEFAULT NULL,
    `IS_DIRECT` varchar(8) DEFAULT NULL,
    `SHARD_NO` varchar(32) DEFAULT NULL,
    PRIMARY KEY (`TERMINAL_ID`),
    UNIQUE KEY `R_CP_TAB03_UK` (`CP_NO`),
    KEY `auto_shard_key_shard_no` (`SHARD_NO`)
)  ENGINE=InnoDB DEFAULT CHARSET=utf8

而且 Extra 字段给出的是 Range checked for each record(index map:0xA),说明存在潜在可以使用的索引,但由于某种原因无法使用。

查看表 tab02 的定义:

CREATE TABLE `tab02` (
    `cp_no` varchar(32)CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
    `name` varchar(512)CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
    `data_src` varchar(16)CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL,
    `shard_no` varchar(32) DEFAULT NULL,
    PRIMARY KEY (`cp_no`),
    KEY `auto_shard_key_shard_no` (`shard_no`),
    KEY `INDX_TAB02_NAME` (`name` (255))
)  ENGINE=InnoDB DEFAULT CHARSET=utf8

表 tab02 的 cp_no 字段采用 utf8_bin(按二进制比较,不忽略大小写) 字符序,而表 tab03 的 cp_no 字段采用 utf8_general_ci(默认)字符序,两者字符序不匹配,因此无法使用正确的索引。

修改表 tab03 的 cp_no 字段字符序为 utf8_bin,执行计划恢复正常,RDS 实例 CPU 波动性打高的问题解决。

从以上的案例可以看到,正确的执行计划相较调整前的执行计划效率大约提升了 13221 倍。字符序不仅仅可以导致 CPU 使用率问题,也可能引入比如 IOPS 使用率高 等其他问题。因此建议应用开发保持统一的字符集和字符序使用规范,避免规范不统一引入性能问题。

时间: 2024-08-03 08:23:51

RDS for MySQL 字符序(collation)引发的性能问题的相关文章

MySQL · 实现分析 · 对字符集和字符序支持的实现

前言 在使用MySQL数据库的时候,常常会发现由于charset或collation设置不正确导致的各种问题.一方面由于数据在client和server之间传输需要做转换会导致CPU使用率增加:另一方面由于charset或collation设置的不一致在查询过程中无法使用索引而导致全表扫描.比如数据库的charset是utf8,collation是utf8_general_ci,而client或connection设置的collation是utf8_unicode_ci,就会导致性能问题.所以我

MySQL字符集和校对规则(Collation)

MySQL字符集和校对规则(Collation) 阅读目录:MySQL的字符集和校对规则 MySQL的字符集 MySQL与字符集 正确使用字符集 MySQL客户端与字符集 字符集编码转换原理 字符集常见处理操作 字符集的正确实践 MySQL的校对规则 一.字符集(Character set) 是多个字符(英文字符,汉字字符,或者其他国家语言字符)的集合,字符集种类较多,每个字符集包含的字符个数不同. 特点: ①字符编码方式是用一个或多个字节表示字符集中的一个字符 ②每种字符集都有自己特有的编码方

php 返回mysql字符编码与删除字符编码

php 返回mysql字符编码与删除字符编码 function Ebak_GetSetChar($char){  global $empire;  if(empty($char))  {   return '';  }  $r=$empire->fetch1("SHOW COLLATION LIKE '".$char."';");  return $r['Charset']; } //---------------------返回设置编码 function E

RDS for MySQL查询缓存 (Query Cache) 的设置和使用

功能和适用范围 原理 限制 设置 验证效果 1. 功能和适用范围 功能: 降低 CPU 使用率 降低 IOPS 使用率(某些情况下) 减少查询响应时间,提高系统的吞吐量 适用范围: 表数据修改不频繁.数据较静态 查询(Select)重复度高 查询结果集小于 1 MB 注: 查询缓存并不一定带来性能上的提升,在某些情况下(比如查询数量大,但重复的查询很少)开启查询缓存会带来性能的下降. 2. 原理 RDS for MySQL 对来自客户端的查询(Select)进行 Hash 计算得到该查询的Has

RDS for MySQL Mysqldump 常见问题和处理

RDS for MySQL Mysqldump 常见问题和处理   GTID 特性相关 避免表级锁等待 设置导出字符集 其他导出时需要注意的选项 举例 RDS for MySQL 不支持的选项 RDS for MySQL 逻辑备份 1. GTID 特性相关 MySQL 5.6 引入了 GTID 特性,因此随 5.6 版本分发的 mysqldump 工具增加了 --set-gtid-purged 选项. # 选项名称 默认值 可选值 作用 1 set-gtid-purged AUTO ON, OF

RDS for MySQL 使用 utf8mb4 字符集存储 emoji 表情

RDS for MySQL 使用 utf8mb4 字符集存储 emoji 表情 1. 基本原则 2. 三个条件的说明 2.1 应用客户端 2.2 应用到 RDS MySQL 实例的连接 2.3 RDS 实例配置 3. 通过 set names 命令设置会话字符集 1. 基本原则 如果要实现存储 emoji 表情到 RDS for MySQL 实例,需要应用客户端.到 RDS for MySQL 实例的连接.RDS 实例内部 3 个方面统一使用或者支持 utf8mb4 字符集. 注:关于 utf8

RDS for MySQL CPU 性能问题浅析

RDS for MySQL CPU 性能问题浅析 1. 原因 1.1 应用负载高 1.2 查询执行成本高 2. 解决方法2.1 相关工具 2.2 应用负载高 2.3 查询语句执行成本高 3. 避免出现的一般原则 RDS for MySQL 实例在日常使用中,会碰到 CPU 使用率达到 100% 的情况.比如: 1. 原因 根本原因:应用提交的查询访问的 逻辑读(逻辑 IO) 总量 (需要访问的 表 数据) 过高. 大量逻辑读会导致数据缓存 Buffer Pool 中用于维护数据一致性的 Latc

RDS for MySQL Online DDL 使用

RDS for MySQL Online DDL 使用   Online DDL 的限制 Online DDL 建议的选项 异常处理 RDS for MySQL 5.6 支持 Online DDL 特性. Online DDL (在线 DDL)功能允许在表上执行 DDL 的操作(比如创建索引)的同时不阻塞并发的 DML 操作 和 查询(select)操作.  注: 从低版本(比如 RDS for MySQL 5.5)升级到 RDS for MySQL 5.6,第一次执行 DDL 时有可能会因为表

RDS for MySQL InnoDB 行锁等待和锁等待超时的处理

RDS for MySQL InnoDB 行锁等待和锁等待超时的处理   1. InnoDB 引擎表行锁等待和等待超时发生的场景 2.InnoDB 引擎行锁等待情况的处理 2.1 InnoDB 行锁等待超时参数 innodb_lock_wait_timeout 2.2 大量行锁等待和行锁等待超时的处理 1. InnoDB 引擎表行锁等待和等待超时发生的场景 当一个 RDS for MySQL 连接会话等待另外一个会话持有的互斥行锁时,会发生 InnoDB 引擎表行锁等待情况. 通常情况下,持有该