高性能MySQL基础篇

1. MySQL体系结构与存储引擎

MySQL 体系结构由 Client Connectors 层、MySQL Server 层及存储引擎层组成。

接下来我们用一条 SQL SELECT 语句的执行轨迹来说明客户端与 MySQL 的交互过程,如下图所示。

 

 在 MySQL 5.6 版本之前,默认的存储引擎都是 MyISAM,但 5.6 版本以后默认的存储引擎就是 InnoDB 了。

 2. MySQL 锁分类

 

 InnoDB 中的锁:在 MySQL InnoDB 存储引擎中,锁分为行锁和表锁。其中行锁包括两种锁。

1⃣️ 共享锁(S):允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。

2⃣️ 排他锁(X):允许获得排他锁的事务更新数据,阻止其他事务取得相同数据集的共享读锁和排他写锁。

排查 InnoDB 锁问题通常有 2 种方法。

打开 innodb_lock_monitor 表,注意使用后记得关闭,否则会影响性能。

在 MySQL 5.5 版本之后,可以通过查看 information_schema 库下面的 innodb_locks、innodb_lock_waits、innodb_trx 三个视图排查 InnoDB 的锁问题。

InnoDB 死锁

 

互斥条件:一个资源每次只能被一个进程使用; 

请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放; 

不剥夺条件:进程已获得的资源,在没使用完之前,不能强行剥夺; 

循环等待条件:多个进程之间形成的一种互相循环等待资源的关系。

建议:

加锁顺序一致;

尽量基于 primary 或 unique key 更新数据。

单次操作数据量不宜过多,涉及表尽量少。

减少表上索引,减少锁定资源。

相关工具:pt-deadlock-logger。

场景:

 

 session1 首先拿到 id=1 的锁,session2 同期拿到了 id=5 的锁后,两者分别想拿到对方持有的锁,于是产生死锁。

 

 session1 和 session2 都在抢占 id=1 和 id=6 的元数据的资源,产生死锁。

查看 MySQL 数据库中死锁的相关信息,可以执行 show engine innodb status\G 来进行查看,重点关注 “LATEST DETECTED DEADLOCK” 部分。

给大家一些开发建议来避免线上业务因死锁造成的不必要的影响。

更新 SQL 的 where 条件时尽量用索引;

加锁索引准确,缩小锁定范围;

减少范围更新,尤其非主键/非唯一索引上的范围更新。

控制事务大小,减少锁定数据量和锁定时间长度 (innodb_row_lock_time_avg)。

加锁顺序一致,尽可能一次性锁定所有所需的数据行。

高性能数据库表该如何设计? 

1.必须指定默认存储引擎为 InnoDB,并且禁用 MyISAM 存储引擎,随着 MySQL 8.0 版本的发布,所有的数据字典表都已经转换成了 InnoDB,MyISAM 存储引擎已成为了历史。

2.默认字符集 UTF8mb4,以前版本的 UTF8 是 UTF8mb3,未包含个别特殊字符,新版本的 UTF8mb4 包含所有字符,官方强烈建议使用此字符集。

3.关闭区分大小写功能。设置 lower_case_tables_name=1,即可关闭区分大小写功能,即大写字母 T 和小写字母 t 一样。

规范命名

命名规范如下,命名时的字符取值范围为:a~z,0~9 和 _(下画线)。 

1. 所有表名小写,不允许驼峰式命名;

2. 允许使用 -(横线)和 (空格);如下图所示,当使用 -(横线),后台默认会转化成 @002d;

3. 不允许使用其他特殊字符作为名称,减少潜在风险。

 

 禁用列为 NULL。

原因:

MySQL 难以优化 NULL 列;

NULL 列加索引,需要额外空间;

含 NULL 复合索引无效。

浮点数与定点数区别

浮点数:float、double(或 real)。

定点数:decimal(或 numberic)。

 

 从上图中可以观察到:

1. 浮点数存在误差问题;

2. 尽量避免进行浮点数比较;

3. 对货币等对精度敏感的数据,应该使用定点数。

N 解释

字符集都为 UTF8mb4,中文存储占三个字节,而数据或字母,则只占一个字节。

1. CHAR(N) 和 VARCHAR(N) 的长度 N,不是字节数,是字符数。

如 username varchar(40) username 最多能存储 40 个字符,占用 120 个字节。

Char 与 Varchar 类型

存储字符串长度相同的全部使用 Char 类型;字符长度不相同的使用 Varchar 类型,不预先分配存储空间,长度不要超过 255。

 

 Varchar 值存储为 1 字节或 2 字节长度前缀加数据。如果值不超过 255 个字节,则列使用一个字节长度;如果值可能需要超过 255 个字节,则列使用两个字节长度。

为什么超过 255 个字节时,必须使用两个字节长度。

(28=256,1 个字节是 8 位;216=65535,2 个字节是 16 位。)

案例

1⃣️ IP处理

1. 一般使用 Char(15) 进行存储,但是当进行查找和统计时,字符类型不是很高效。

2. MySQL 数据库内置了两个 IP 相关的函数 INET_ATON()、INET_NTOA(),可以实现 IP 地址和整数的项目转换。

 

 将 IP 的存储从字符型转换成整形,转化后数字是连续的,提高了查询性能,使查询更快,占用空间更小。

2⃣️ TIMESTAMP 处理

同样的方法,我们使用 MySQL 内置的函数(FROM_UNIXTIME(),UNIX_TIMESTAMP()),可以将日期转化为数字,用 INT UNSIGNED 存储日期和时间。

 索引使用细节

首先是创建索引后如何确认 SQL 语句是否走索引了呢?创建索引后通过查看执行 SQL 语句的执行计划即可知道 SQL 语句是否走索引。执行计划重点关注跟索引相关的关键项,有 type、possible_keys、key、key_len、ref、Extra 等。

其中,possible_keys 表示查询可能使用的索引,key表示真正实际使用的索引,key_len 表示使用索引字段的长度。

另外执行计划中 Extra 选项也值得关注,例如 Extra 显示 use index 时就表示该索引是覆盖索引,通常性能排序的结果是 usd index > use where > use filsort,如下图所示。

key_len 计算规则

从两个方面考虑,一方面是索引字段的数据类型,另一方面是表、字段所使用的字符集。

1. 索引字段的数据类型,根据索引字段的定义可以分为变长和定长两种数据类型:

索引字段为定长数据类型,比如 char、int、datetime,需要有是否为空的标记,这个标记需要占用 1 个字节;

对于变长数据类型,比如 Varchar,除了是否为空的标记外,还需要有长度信息,需要占用 2 个字节;(备注:当字段定义为非空的时候,是否为空的标记将不占用字节)。

2. 表所使用的字符集,不同的字符集计算的 key_len 不一样,例如,GBK 编码的是一个占用 2 个字节大小的字符,UTF8 编码的是一个占用 3 个字节大小的字符。

举例说明:在四类字段上创建索引后的 key_len 如何计算呢?

Varchr(10) 变长字段且允许 NULL:10*(Character Set:utf8=3,gbk=2,latin1=1)+1(标记是否为 NULL 需要 1 个字节)+ 2(变长字段存储长度信息需要 2 个字节)。

Varchr(10) 变长字段且不允许 NULL:10*(Character Set:utf8=3,gbk=2,latin1=1)+2(变长字段存储长度信息需要2个字节),非空不再需要占用字节来标记是否为空。

Char(10) 固定字段且允许 NULL:10*(Character Set:utf8=3,gbk=2,latin1=1)+1(标记是否为 NULL 需要 1 个字节)。  

Char(10) 固定字段且不允许 NULL:10*(Character Set:utf8=3,gbk=2,latin1=1),非空不再需要占用字节来标记是否为空。

判断性能好坏

创建一个 test 表。 在 a、b、c 上创建索引,执行表中的 SQL 语句,快速定位语句孰好孰坏。

 首先分析 key_len, 因为 a、b、c 不允许 NULL 的 varchar(50),那么,每个字段的 key_len 为 50×4+2=202,整个联合索引的 key_len 为 202×3=606。

SQL1 可以使用覆盖索引,性能好;

SQL2 可以使用覆盖索引同时可以避免排序,性能好;

SQL3 可以使用覆盖索引,但是需要根据 where 字句进行过滤;

SQL4 可以使用部分索引 a,但无法避免排序,性能差;

SQL5 完全使用覆盖索引,同时可以避免排序,性能好;

SQL6 可以使用覆盖索引,但无法避免排序,这是因为 MySQL InnoDB 创建索引时默认asc升序,索引无法自动倒序排序;

SQL7 可以使用覆盖索引,但是需要根据 where 子句进行过滤(非定值查询)。

如何提高查询性能?

1. 查看 SQL 执行计划:

explain SQL;

desc 表名;

show create table 表名。

2. 通过 Profile 定位 QUERY 代价消耗:

set profiling=1;

执行 SQL;

show profiles; 获取 Query_ID。

show profile for query Query_ID; 查看详细的 profile 信息。

3. 通过 Optimizer Trace 表查看 SQL 执行计划树:

set session optimizer_trace='enabled=on';

执行 SQL;

查询 information_schema.optimizer_trace 表,获取 SQL 查询计划树;

set session optimizer_trace=‘enabled=off';开启此项影响性能,记得用后关闭。

分析慢查询常用的工具有

explain;

Mysqldumpslow,官方慢查询分析工具;

pt-query-digest,Percona 公司开源的慢查询分析工具;

vc-mysql-sniffer,第三方的慢查询抓取工具;

pt-kill,Percona 公司开源的慢查询 kill 工具,常用于生产环境的过载保护。

这里重点介绍 pt-query-digest ,它是用于分析 MySQL 慢查询的一个常用工具,先对查询语句的条件进行参数化,然后对参数化以后的查询进行分组统计,统计出各查询的执行时间、次数、占比等,同时把分析结果输出到文件中。也可以结合 Anemometer 工具将慢查询平台化展示。

编写规范建议:

1. 用 IN 代替 OR。SQL 语句中 IN 包含的值不宜过多,应少于 1000 个。过多会使随机 IO 增大,影响性能。

2. 大量的更新/删除操作控制频度,例如每秒操作 2000 行以下。

3. 尽量少使用 distinct、order by、group by、union 等 SQL,排序需求可以放到前端。

4. 大事务或者长查询的需求根据业务特点拆分。

Bad SQL 案例

 

posted @ 2020-08-13 00:33  潮哥  阅读(319)  评论(0编辑  收藏  举报