热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

MySQL8.0性能优化(实践)

原标题:MySQL8.0性能优化(实践)一台几年前的旧笔记本电脑的虚拟系统运行环境,作为本次实践的运行工具,仅供参考。案例环境:Linux、Docker、MySQLC

原标题:MySQL8.0性能优化(实践)

一台几年前的旧笔记本电脑的虚拟系统运行环境,作为本次实践的运行工具,仅供参考。

案例环境:Linux、Docker、MySQLCommunity8.0.31、InnoDB。

过早的MySQL版本不一定适用本章内容,仅围绕 InnoDB 引擎的阐述。

一、索引


1.1 索引的管理

-- create 方式创建
create [unique] index {index_name} on {tab_name}({col_name}[(length)]);
-- alter表 方式创建
alter {tab_name} add [unique] index {index_name} on ({col_name}[(length)]);
-- 创建组合索引
create index {index_name} on ({col_name1}[(length)], {col_name2}[(length)], {col_name3}[(length)]);
-- unique:唯一索引
-- col_name:一列为单列索引;逗号隔开的多列为组合索引
-- length:字段中前几个字符有效,避免无限长度(通常能够明显区分值即可的长度;如:员工表的Email,@后面都一样)
-- 查看表中的索引
show index from {tab_name};
-- 删除索引
drop index {index_name} on {tab_name};

1.2 索引创建的场景

过多查询的表,过少写入的表。

数据量过大导致的查询效率慢。

经常作为条件查询的列。

批量的重复值,不适合创建索引;比如<业务状态>列

值过少重复的列,适合创建索引;比如

1.3 理想的索引特征



  • 尽量能够覆盖常用字段

  • 字段值区分度高

  • 字段长度小(合适的长度,不是越小越好,至少能足够区分每个值)

  • 相对低频的写入操作,以及高频的查询操作的表和字段上建立索引

通过非聚集索引检索记录的时候,需要2次操作,先在非聚集索引中检索出主键,然后再到聚集索引中检索出主键对应的记录,这个过程叫做回表,比聚集索引多了一次操作。

1.4 非主键索引

where全部为and时,无所谓位置,都会命中索引(当多个条件中有索引的时候,并且关系是and的时候,会自动匹配索引区分度高的)

where后面为 or 时,索引列 依影响数据范围越精确 按序靠前写。

1.5 索引的使用

使用原则:


  • 按条件后面涉及到的列,创建出组合索引

  • 越精确的条件,就排在条件的顺序首位,最左匹配原则

-- 按现有数据,计算哪个列最精确;越精确的列,位置越靠前优先。
select sum(depno=28), sum(username like 'Sol%'), sum(position='dev') from tab_emp;
+---------------+---------------------------+---------------------+
| sum(depno=28) | sum(username like 'Sol%') | sum(position='dev') |
+---------------+---------------------------+---------------------+
| 366551 | 3 | 109 |
+---------------+---------------------------+---------------------+
-- 由此得出:username列的范围最精确,应该放到where后的首位;不在组合索引的列放到最后。
-- 如下组合索引的创建方式:
create index {index_name} on {tab_name}(username,position,depno);
-- 如下组合索引的查询方式:
select username,position,depno from tab_emp where username like 'Sol%' and position='dev' and depno=106 and age<27

1.5.1 使用索引查询

这里准备两张两千万相同表数据,测试效果如下图:

1.5.2 组合索引的使用

表创建的组合索引,如下图:

两千万数据表,组合索引查询效果,如下图:

总结:组合索引所包含的列,尽量在where, order中写全,非索引列或过少的组合索引列可能不会产生索引效果。

1.5.3 高性能分页查询

通常MySQL分页时用到的limit,当limit值过大时,查询效果会很慢。

当如 limit 9000000,10 时,需要先查询出900万数据,再抛掉900万数据;这900万的过程可否省略?

假如:每次查询一页时,把当前页的最后一条数据的重要栏位都做记录,并标识是第几页;当查询它的下页时,拿它的最后一条数据的重要栏位作为追加的查询条件,如何呢...??

下图示例:usercode 为主要的索引及排序字段,上页的最后一条作为追加条件,再往下取5条,效果有了显著提升。(排序列重复数据呢?)当然适用于类似code、time等这样重复数据较少的列。

1.6 索引覆盖,避免回表查询

当查询的列中包含了非索引列,系统相当于扫描了两遍数据,如果能只扫描了一遍,也提高了查询效率。

回表查询的过程


  1. 先按已有索引查询到数据,得出此数据的主键值

  2. 再按主键值,再次检索出具体的数据,获取其它列的值

查询涉及到的列都为组合索引列时,包括:selectwhereordergroup等,索引覆盖(索引下推),避免回表查询。

避免使用*,以避免回表查询;不常用的查询列或text类型的列,尽量以单独的扩展表存放。

通常列表数据需要的列并不多,查询的时候可以考虑为索引列;通常详细信息时涵盖的列多,可通过主键单独查询。

1.7 命中索引


1.7.1 无效索引

列类型转换可能会导致索引无效;如:


  • 字符转数值,会导致索引无效

  • 数值转字符,不影响索引。

不建议类型的转换,尽量按原类型查询。

条件中的函数导致索引无效;索引列不能用在函数内。如:where abs(Id) > 200

条件中的表达式导致索引无效;如:where (Id + 1) > 200

避免单列索引与组合索引的重复列;在组合索引中的列,去除单列索引。

全模糊查询导致索引无效;匹配开头不会影响索引,如 'Sol%';全模糊或'%Sol'时无效。

1.7.2 Explain

显示执行过程,查看是否命中索引

explain select * from tab_emp where uname='Sol'
-- 可能用到的索引、实际用到的索引、扫描了的行数
+----+-------------+---------+-------+---------------+---------------+---------+-------+------+-----------------------+
| id | select_type | table | type | possible_keys | key 文章来源站点https://www.yii666.com/ | key_len | ref | rows | Extra |
+----+-------------+---------+-------+---------------+---------------+---------+-------+------+-----------------------+
| 1 | SIMPLE | tab_emp | range | idx_emp_uname | idx_emp_uname | 4 | const | 1 | Using index condition |
+----+-------------+---------+-------+---------------+---------------+---------+-------+------+-----------------------+

在通常情况下,能不能命中索引,取决于索引列的值重复程度;如果是极少重复的值,就很容易命中索引。如果类似于状态或类型的值,重复程度很高,就很难命中索引,这是MySQL自动取舍的结果。

比如:没有索引的列-电话号码,有索引的列-部门,那么很难命中部门索引,因为MySQL认为[电话号码]更精确;或者使用force强行命中,通常MySQL的自动取舍是最有效的。

1.8 查询总结

避免使用*,以避免回表查询。

不常用的查询列或text类型的列,尽量以单独的扩展表存放。

条件避免使用函数。

条件避免过多的or,建议使用in()/union代替,in中的数据不可以极端海量,至少个数小于1000比较稳妥。

避免子查询,子查询的结果集是临时表不支持索引、或结果集过大、或重复扫描子表;以join代替子查询,尽量以inner join代替最为妥当。

避免使用'%Sol%'查询,或以'Sol%'代替。

二、表分区

表分区也就是把一张物理表的数据文件分成若干个数据文件存储,使得单个数据文件的量有限,有助于避免全表扫描数据,提升查询性能。

那,跨区查询的性能影响有多大,从整体看,表分区还是带来了不少的性能提升。

如果表中有主键列,分区列必须是主键列之一。比如:又有自增主键,又想按年份分区,那主键就是组合索引咯。(id+date)

2.1 分区的种类

HASH:按算法,平均分配到各分区

-- 表创建 HASH 分区12个
CREATE TABLE clients (
id INT,
fname VARCHAR(30),
lname VARCHAR(30),
signed DATE
)
PARTITION BY HASH(MONTH(signed))
PARTITIONS 12;

KEY:按算法,无序不等的分配到各分区

-- 表创建12个 KEY 分区
CREATE TABLE clients_lk (
id INT,
fname VARCHAR(30),
lname VARCHAR(30),
signed DATE
)
PARTITION BY LINEAR KEY(signed)
PARTITIONS 12;

RANGE:按划定的范围将数据存放到符合的分区

-- 按年份创建范围分区
CREATE TABLE tr (
id INT,
name VARCHAR(50),
purchased DATE
)
PARTITION BY RANGE(YEAR(purchased)) (
PARTITION p0 VALUES LESS THAN (1990),
PARTITION p1 VALUES LESS THAN (1995),
PARTITION p2 VALUES LESS THAN (2000)
);

LIST:按定义的一组包含值将数据存放到符合的分区

-- LIST 分组包含方式
CREATE TABLE tt (
id INT,
data INT
)
PARTITION BY LIST(data) (
PARTITION p0 VALUES IN (5, 10, 15),
PARTITION p1 VALUES IN (6, 12, 18)
);

2.2 分区的管理

新增 HASH/KEY 分区

-- 将原来的 12 个分区合并为 8 个分区
ALTER TABLE clients COALESCE PARTITION 4;
-- 在原有的基础上增加 6 个分区
ALTER TABLE clients ADD PARTITION PARTITIONS 6;

新增 RANGE/LIST 分区

-- RANGE 追加分区
ALTER TABLE tr ADD PARTITION (PARTITION p3 VALUES LESS THAN (2010));
-- LIST 追加新分区(不可包含已存在的值)
ALTER TABLE tt ADD PARTITION (PARTITION p2 VALUES IN (7, 14, 21));

变更 RANGE/LIST 分区

-- RANGE 拆分原有分区(重组分区)
ALTER TABLE tr REORGANIZE PARTITION p0 INTO (
PARTITION n0 VALUES LESS THAN (1980),
PARTITION n1 VALUES LESS THAN (1990)
);
-- RANGE 合并相邻分区
ALTER TABLE tt REORGANIZE PARTITION s1,s2 INTO (
PARTITIONwww.yii666.com s0 VALUES LESS THAN (1980)
);
-- LIST 重组原有分区
ALTER TABLE tt REORGANIZE PARTITION p1,np INTO (
PARTITION p1 VALUES IN (6, 18),
PARTITION np VALUES in (4, 8, 12)
);

删除指定分区

-- 丢掉指定分区及其数据
ALTER TABLE {TABLE_NAME} DROP PARTITION p2,p3;
-- 删除指定分区,保留数据
ALTER TABLE {TABLE_NAME} TRUNCATE PARTITION p2;
-- 删除表全部分区,保留数据
ALTER TABLE {TABLE_NAME} REMOVE PARTITIONING;

分区详细信息

-- 查询指定分区的数据
SELECT * FROM tr PARTITION (p2);
-- 查询各分区详细
SELECT * FROM information_schema.PARTITIONS WHERE TABLE_SCHEMA=SCHEMA() AND TABLE_NAME='tt';
-- 查看某个分区的状态
ALTER TABLE tr ANALYZE PARTITION p3;

修复分区

-- 检查分区是否损坏
ALTER TABLE tr CHECK PARTITION p1;
-- 修复分区
ALTER TABLE tr REPAIR PARTITION p1, p2;
-- 优化分区,整理分区碎片
ALTER TABLE tr OPTIMIZE PARTITION p0, p1;
-- 当前分区数据,重建分区
ALTER TABLE tr REBUILD PARTITION p0, p1;

三、查询综合测试

2000万相同数据、相同表结构,相同的查询方式,测试效果如下图:(仅供参考)

数据量大了,查询慢;加索引了,数据量越大,写入越慢;

还是物理分表好呀~

四、SQL服务参数优化

仅列出了点官方认可的稳定性良好的可靠的参数,以 InnoDB 为主。

4.1 Connections

[mysqld]
# 保持在缓存中的可用连接线程
# default = -1(无)
thread_cache_size = 16
# 最大的连接线程数(关系型数据库)
# default = 151
max_cOnnections= 1000
# 最大的连接线程数(文档型/KV型)
# default = 100
#mysqlx_max_cOnnections= 700

4.2 缓冲区 Buffer

[mysqld]
# 缓冲区单位大小;default = 128M
innodb_buffer_pool_size = 128M
# 缓冲区总大小,内存的70%,单位大小的倍数
# default = 128M
innodb_buffer_pool_size = 6G
# 以上两个参数的设定,MySQL会自动改变 innodb_buffer_pool_instances 的值

4.3 Sort merge passes

[mysqld]
# 优化 order/group/distinct/join 的性能
# SHOW GLOBAL STATUS 中的 Sort_merge_passes 过多就增加设置
# default = 1K
max_sort_length = 8K
#www.yii666.com default = 256K
sort_buffer_size = 2M
# 通常别太大,海量join时大
# default = 256K
#join_buffer_size = 128M

4.4 I/O 线程数

[mysqld]
# 异步I/O子系统
# default = NO
innodb_use_native_aio = NO
# 读数据线程数
# default = 4
innodb_read_io_threads = 32
# 写入数据线程数
# default = 4
innodb_write_io_threads = 32

4.5 Capacity 容量

[mysqld]
# default = 200
innodb_io_capacity = 1000
# default = 2000
innodb_io_capacity_max = 2500
# 数据日志容量值越大,恢复数据越慢
# default = 100M
innodb_redo_log_capacity = 1G
# 数据刷新到磁盘的方式
# 有些同学说用 O_DSYNC 方式,在写入时,有很大提升。但官网说:
# InnoDB does not use O_DSYNC directly because there have been problems with it on many varieties of Unix.
# 也就是少部分系统可以使用,或者已经过确认。
# 个人认为,默认值最可靠
# innodb_flush_method = fsync

4.6 Open cache

[mysqld]
# default = 5000
open_files_limit = 10000
# 计算公式:MAX((open_files_limit-10-max_connections)/2,400)
# default = 4000
table_open_cache = 4495
# 超过16核的硬件,肯定要增加,以发挥出性能
# default = 16
table_open_cache_instances = 32

五、写入综合测试

测试目的:

经过【四、SQL服务参数优化】的配置后,分别测试空表状态批量写入200万和500万数据的耗时。

测试场景:

一台几年前的破笔记本,创建的虚拟机4C8G,Docker + MySQL8.0.31。

桌面应用以36个线程写入随机数据。

批量写入脚本:INSERT INTO TABLE ... VALUES (...),(...),(...) 的方式,INSERT 每次1000条。

表结构:聚集索引 + 两列的非聚集索引 + 一组三列的组合索引;(参照 1.5.2)

+------------+--------------+------+-----+-------------------+-------------------+
| Field | Type | Null | Key文章来源地址67757.html | Default | Extra |
+------------+--------------+------+-----+-------------------+-------------------+
| id | bigint | NO | PRI | NULL | auto_increment |
| usercode | varchar(32) | YES | MUL | NULL | 文章来源地址67757.html|
| title | varchar(128) | YES | | NULL | |
| age | int | YES | MUL | NULL | |
| gender | char(1) | YES | | 男 | |
| phone | char(11) | YES | | NULL | |
| job | varchar(32) | YES | | NULL | |
| department | varchar(32) | YES | | NULL | |
| createtime | datetime | NO | PRI | CURRENT_TIMESTAMP | DEFAULT_GENERATED |
+------------+--------------+------+-----+-------------------+-------------------+

测试结果:

逐步追加MySQL服务参数配置+表分区,最终有了成倍的性能提升;每次测试后的日志记录了优化的递进过程;
如下图:(日志不够细,懂就行)

经过逐步优化:

  200万数据写入耗时从 9分4秒,提升到 5分50秒;(无表分区)

  500万数据写入耗时从 41分33秒,提升到 6分50秒。(有表分区)

来源于:MySQL8.0性能优化(实践)


推荐阅读
  • 图解redis的持久化存储机制RDB和AOF的原理和优缺点
    本文通过图解的方式介绍了redis的持久化存储机制RDB和AOF的原理和优缺点。RDB是将redis内存中的数据保存为快照文件,恢复速度较快但不支持拉链式快照。AOF是将操作日志保存到磁盘,实时存储数据但恢复速度较慢。文章详细分析了两种机制的优缺点,帮助读者更好地理解redis的持久化存储策略。 ... [详细]
  • Linux服务器密码过期策略、登录次数限制、私钥登录等配置方法
    本文介绍了在Linux服务器上进行密码过期策略、登录次数限制、私钥登录等配置的方法。通过修改配置文件中的参数,可以设置密码的有效期、最小间隔时间、最小长度,并在密码过期前进行提示。同时还介绍了如何进行公钥登录和修改默认账户用户名的操作。详细步骤和注意事项可参考本文内容。 ... [详细]
  • 本文介绍了深入浅出Linux设备驱动编程的重要性,以及两种加载和删除Linux内核模块的方法。通过一个内核模块的例子,展示了模块的编译和加载过程,并讨论了模块对内核大小的控制。深入理解Linux设备驱动编程对于开发者来说非常重要。 ... [详细]
  • 一、Hadoop来历Hadoop的思想来源于Google在做搜索引擎的时候出现一个很大的问题就是这么多网页我如何才能以最快的速度来搜索到,由于这个问题Google发明 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • qt学习(六)数据库注册用户的实现方法
    本文介绍了在qt学习中实现数据库注册用户的方法,包括登录按钮按下后出现注册页面、账号可用性判断、密码格式判断、邮箱格式判断等步骤。具体实现过程包括UI设计、数据库的创建和各个模块调用数据内容。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 1,关于死锁的理解死锁,我们可以简单的理解为是两个线程同时使用同一资源,两个线程又得不到相应的资源而造成永无相互等待的情况。 2,模拟死锁背景介绍:我们创建一个朋友 ... [详细]
  • PHP中的单例模式与静态变量的区别及使用方法
    本文介绍了PHP中的单例模式与静态变量的区别及使用方法。在PHP中,静态变量的存活周期仅仅是每次PHP的会话周期,与Java、C++不同。静态变量在PHP中的作用域仅限于当前文件内,在函数或类中可以传递变量。本文还通过示例代码解释了静态变量在函数和类中的使用方法,并说明了静态变量的生命周期与结构体的生命周期相关联。同时,本文还介绍了静态变量在类中的使用方法,并通过示例代码展示了如何在类中使用静态变量。 ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • 《数据结构》学习笔记3——串匹配算法性能评估
    本文主要讨论串匹配算法的性能评估,包括模式匹配、字符种类数量、算法复杂度等内容。通过借助C++中的头文件和库,可以实现对串的匹配操作。其中蛮力算法的复杂度为O(m*n),通过随机取出长度为m的子串作为模式P,在文本T中进行匹配,统计平均复杂度。对于成功和失败的匹配分别进行测试,分析其平均复杂度。详情请参考相关学习资源。 ... [详细]
  • Linux环境变量函数getenv、putenv、setenv和unsetenv详解
    本文详细解释了Linux中的环境变量函数getenv、putenv、setenv和unsetenv的用法和功能。通过使用这些函数,可以获取、设置和删除环境变量的值。同时给出了相应的函数原型、参数说明和返回值。通过示例代码演示了如何使用getenv函数获取环境变量的值,并打印出来。 ... [详细]
author-avatar
苏格兰的秋天
一枚在创业路上坚持走下去的人。。。。
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有