Java性能优化(十)-数据库调优-数据库参数设置优化

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
日志服务 SLS,月写入数据量 50GB 1个月
简介: Java性能优化(十)-数据库调优-数据库参数设置优化

MySQL是一个灵活性比较强的数据库系统,提供了很多可配置参数,便于我们根据应用和服务器硬件来做定制化数据库服务。如果现在让你回想,你可能觉得在开发的过程中很少去调整MySQL的配置参数,但我今天想说的是我们很有必要去深入了解它们。

我们知道,数据库主要是用来存取数据的,而存取数据涉及到了磁盘I/O的读写操作,所以数据库系统主要的性能瓶颈就是I/O读写的瓶颈了。MySQL数据库为了减少磁盘I/O的读写操作,应用了大量内存管理来优化数据库操作,包括内存优化查询、排序以及写入操作。

也许你会想,我们把内存设置得越大越好,数据刷新到磁盘越快越好,不就对了吗?其实不然,内存设置过大,同样会带来新的问题。例如,InnoDB中的数据和索引缓存,如果设置过大,就会引发SWAP页交换。还有数据写入到磁盘也不是越快越好,我们期望的是在高并发时,数据能均匀地写入到磁盘中,从而避免I/O性能瓶颈。

SWAP页交换:SWAP分区在系统的物理内存不够用的时候,就会把物理内存中的一部分空间释放出来,以供当前运行的程序使用。被释放的空间可能来自一些很长时间没有什么操作的程序,这些被释放的空间的数据被临时保存到SWAP分区中,等到那些程序要运行时,再从SWAP分区中恢复保存的数据到内存中。

所以,这些参数的设置跟我们的应用服务特性以及服务器硬件有很大的关系。MySQL是一个高定制化的数据库,我们可以根据需求来调整参数,定制性能最优的数据库。

不过想要了解这些参数的具体作用,我们先得了解数据库的结构以及不同存储引擎的工作原理。

MySQL体系结构

我们一般可以将MySQL的结构分为四层,最上层为客户端连接器,主要包括了数据库连接、授权认证、安全管理等,该层引用了线程池,为接入的连接请求提高线程处理效率。

第二层是Server层,主要实现SQL的一些基础功能,包括SQL解析、优化、执行以及缓存等,其中与我们这一讲主要相关的就是缓存。

第三层包括了各种存储引擎,主要负责数据的存取,这一层涉及到的Buffer缓存,也和这一讲密切相关。

最下面一层是数据存储层,主要负责将数据存储在文件系统中,并完成与存储引擎的交互。

接下来我们再来了解下,当数据接收到一个SQL语句时,是如何处理的。

1. 查询语句

一个应用服务需要通过第一层的连接和授权认证,再将SQL请求发送至SQL接口。SQL接口接收到请求之后,会先检查查询SQL是否命中Cache缓存中的数据,如果命中,则直接返回缓存中的结果;否则,需要进入解析器。

解析器主要对SQL进行语法以及词法分析,之后,便会进入到优化器中,优化器会生成多种执行计划方案,并选择最优方案执行。

确定了最优执行计划方案之后,执行器会检查连接用户是否有该表的执行权限,有则查看Buffer中是否存在该缓存,存在则获取锁,查询表数据;否则重新打开表文件,通过接口调用相应的存储引擎处理,这时存储引擎就会进入到存储文件系统中获取相应的数据,并返回结果集。

2. 更新语句

数据库更新SQL的执行流程其实跟查询SQL差不多,只不过执行更新操作的时候多了记录日志的步骤。在执行更新操作时MySQL会将操作的日志记录到 binlog(归档日志)中,这个步骤所有的存储引擎都有。而InnoDB除了要记录 binlog 之外,还需要多记录一个 redo log(重做日志)。

redo log 主要是为了解决 crash-safe 问题而引入的。我们知道,当数据库在存储数据时发生异常重启,我们需要保证存储的数据要么存储成功,要么存储失败,也就是不会出现数据丢失的情况,这就是crash-safe了。

我们在执行更新操作时,首先会查询相关的数据,之后通过执行器执行更新操作,并将执行结果写入到内存中,同时记录更新操作到redo log的缓存中,此时redo log中的记录状态为prepare,并通知执行器更新完成,随时可以提交事务。执行器收到通知后会执行binlog的写入操作,此时的binlog是记录在缓存中的,写入成功后会调用引擎的提交事务接口,更新记录状态为commit。之后,内存中的redo log以及binlog都会刷新到磁盘文件中。

内存调优

基于以上两个SQL执行过程,我们可以发现,在执行查询SQL语句时,会涉及到两个缓存。第一个缓存是刚进来时的Query Cache,它缓存的是SQL语句和对应的结果集。这里的缓存是以查询SQL的Hash值为key,返回结果集为value的键值对,判断一条SQL是否命中缓存,是通过匹配查询SQL的Hash值来实现的。

很明显,Query Cache可以优化查询SQL语句,减少大量工作,特别是减少了I/O读取操作。我们可以通过以下几个主要的设置参数来优化查询操作:

我们可以通过设置合适的 query_cache_min_res_unit 来减少碎片,这个参数最合适的大小和应用程序查询结果的平均大小直接相关,可以通过以下公式计算所得:

(query_cache_size - Qcache_free_memory)/ Qcache_queries_in_cache

Qcache_free_memory 和 Qcache_queries_in_cache 的值可以通过以下命令查询:

show status like 'Qcache%'

Query Cache虽然可以优化查询操作,但也仅限于不常修改的数据,如果一张表数据经常进行新增、更新和删除操作,则会造成Query Cache的失效率非常高,从而导致频繁地清除Cache中的数据,给系统增加额外的性能开销。

这也会导致缓存命中率非常低,我们可以通过以上查询状态的命令查看 Qcache_hits,该值表示缓存命中率。如果缓存命中率特别低的话,我们还可以通过query_cache_size = 0或者query_cache_type来关闭查询缓存。

经过了Query Cache缓存之后,还会使用到存储引擎中的Buffer缓存。不同的存储引擎,使用的Buffer也是不一样的。这里我们主要讲解两种常用的存储引擎。

1. MyISAM存储引擎参数设置调优

MyISAM存储引擎使用key buffer缓存索引块,MyISAM表的数据块则没有缓存,它是直接存储在磁盘文件中的。

我们可以通过key_buffer_size设置key buffer缓存的大小,而它的大小并不是越大越好。正如我前面所讲的,key buffer缓存设置过大,实际应用却不大的话,就容易造成内存浪费,而且系统也容易发生SWAP页交换,一般我是建议将服务器内存中可用内存的1/4分配给key buffer。

如果要更准确地评估key buffer的设置是否合理,我们还可以通过缓存使用率公式来计算:

1-((key_blocks_unused*key_cache_block_size)/key_buffer_size)

key_blocks_unused表示未使用的缓存簇(blocks)数 key_cache_block_size表示key_buffer_size被分割的区域大小key_blocks_unused*key_cache_block_size则表示剩余的可用缓存空间(一般来说,缓存使用率在80%作用比较合适)。

2. InnoDB存储引擎参数设置调优

InnoDB Buffer Pool(简称IBP)是InnoDB存储引擎的一个缓冲池,与MyISAM存储引擎使用key buffer缓存不同,它不仅存储了表索引块,还存储了表数据。查询数据时,IBP允许快速返回频繁访问的数据,而无需访问磁盘文件。InnoDB表空间缓存越多,MySQL访问物理磁盘的频率就越低,这表示查询响应时间更快,系统的整体性能也有所提高。

我们一般可以通过多个设置参数来调整IBP,优化InnoDB表性能。

  • innodb_buffer_pool_size

IBP默认的内存大小是128M,我们可以通过参数innodb_buffer_pool_size来设置IBP的大小,IBP设置得越大,InnoDB表性能就越好。但是,将IBP大小设置得过大也不好,可能会导致系统发生SWAP页交换。所以我们需要在IBP大小和其它系统服务所需内存大小之间取得平衡。MySQL推荐配置IBP的大小为服务器物理内存的80%。

我们也可以通过计算InnoDB缓冲池的命中率来调整IBP大小:

(1-innodb_buffer_pool_reads/innodb_buffer_pool_read_request)*100

但如果我们将IBP的大小设置为物理内存的80%以后,发现命中率还是很低,此时我们就应该考虑扩充内存来增加IBP的大小。

  • innodb_buffer_pool_instances

InnoDB中的IBP缓冲池被划分为了多个实例,对于具有数千兆字节的缓冲池的系统来说,将缓冲池划分为单独的实例可以减少不同线程读取和写入缓存页面时的争用,从而提高系统的并发性。该参数项仅在将innodb_buffer_pool_size设置为1GB或更大时才会生效。

在windows 32位操作系统中,如果innodb_buffer_pool_size的大小超过1.3GB,innodb_buffer_pool_instances默认大小就为innodb_buffer_pool_size/128MB;否则,默认为1。

而在其它操作系统中,如果innodb_buffer_pool_size大小超过1GB,innodb_buffer_pool_instances值就默认为8;否则,默认为1。

为了获取最佳效率,建议指定innodb_buffer_pool_instances的大小,并保证每个缓冲池实例至少有1GB内存。通常,建议innodb_buffer_pool_instances的大小不超过innodb_read_io_threads + innodb_write_io_threads之和,建议实例和线程数量比例为1:1。

  • innodb_read_io_threads / innodb_write_io_threads

在默认情况下,MySQL后台线程包括了主线程、IO线程、锁线程以及监控线程等,其中读写线程属于IO线程,主要负责数据库的读取和写入操作,这些线程分别读取和写入innodb_buffer_pool_instances创建的各个内存页面。MySQL支持配置多个读写线程,即通过innodb_read_io_threads和innodb_write_io_threads设置读写线程数量。

读写线程数量值默认为4,也就是总共有8个线程同时在后台运行。innodb_read_io_threads和innodb_write_io_threads设置的读写线程数量,与innodb_buffer_pool_instances的大小有关,两者的协同优化是提高系统性能的一个关键因素。

在一些内存以及CPU内核超大型的数据库服务器上,我们可以在保证足够大的IBP内存的前提下,通过以下公式,协同增加缓存实例数量以及读写线程。

( innodb_read_io_threads + innodb_write_io_threads ) = innodb_buffe_pool_instances

如果我们仅仅是将读写线程根据缓存实例数量对半来分,即读线程和写线程各为实例大小的一半,肯定是不合理的。例如我们的应用服务读取数据库的数据多于写入数据库的数据,那么增加写入线程反而没有优化效果。我们一般可以通过MySQL服务器保存的全局统计信息,来确定系统的读取和写入比率。

我们可以通过以下查询来确定读写比率:

SHOW GLOBAL STATUS LIKE 'Com_select';//读取数量
 
SHOW GLOBAL STATUS WHERE Variable_name IN ('Com_insert', 'Com_update', 'Com_replace', 'Com_delete');//写入数量

如果读大于写,我们应该考虑将读线程的数量设置得大一些,写线程数量小一些;否则,反之。

  • innodb_log_file_size

除了以上InnoDB缓存等因素之外,InnoDB的日志缓存大小、日志文件大小以及日志文件持久化到磁盘的策略都影响着InnnoDB的性能。 InnoDB中有一个redo log文件,InnoDB用它来存储服务器处理的每个写请求的重做活动。执行的每个写入查询都会在日志文件中获得重做条目,以便在发生崩溃时可以恢复更改。

当日志文件大小已经超过我们参数设置的日志文件大小时,InnoDB会自动切换到另外一个日志文件,由于重做日志是一个循环使用的环,在切换时,就需要将新的日志文件脏页的缓存数据刷新到磁盘中(触发检查点)。

理论上来说,innodb_log_file_size设置得越大,缓冲池中需要的检查点刷新活动就越少,从而节省磁盘I/O。那是不是将这个日志文件设置得越大越好呢?如果日志文件设置得太大,恢复时间就会变长,这样不便于DBA管理。在大多数情况下,我们将日志文件大小设置为1GB就足够了。

  • innodb_log_buffer_size

这个参数决定了InnoDB重做日志缓冲池的大小,默认值为8MB。如果高并发中存在大量的事务,该值设置得太小,就会增加写入磁盘的I/O操作。我们可以通过增大该参数来减少写入磁盘操作,从而提高并发时的事务性能。

  • innodb_flush_log_at_trx_commit

这个参数可以控制重做日志从缓存写入文件刷新到磁盘中的策略,默认值为1。

当设置该参数为0时,InnoDB每秒种就会触发一次缓存日志写入到文件中并刷新到磁盘的操作,这有可能在数据库崩溃后,丢失1s的数据。

当设置该参数为 1 时,则表示每次事务的 redo log 都会直接持久化到磁盘中,这样可以保证 MySQL 异常重启之后数据不会丢失。

当设置该参数为 2 时,每次事务的 redo log 都会直接写入到文件中,再将文件刷新到磁盘。

在一些对数据安全性要求比较高的场景中,显然该值需要设置为1;而在一些可以容忍数据库崩溃时丢失1s数据的场景中,我们可以将该值设置为0或2,这样可以明显地减少日志同步到磁盘的I/O操作。

总结

MySQL数据库的参数设置非常多,今天我们仅仅是了解了与内存优化相关的参数设置。除了这些参数设置,我们还有一些常用的提高MySQL并发的相关参数设置,总结如下:

相关实践学习
如何快速连接云数据库RDS MySQL
本场景介绍如何通过阿里云数据管理服务DMS快速连接云数据库RDS MySQL,然后进行数据表的CRUD操作。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
1月前
|
监控 算法 Java
Java虚拟机(JVM)垃圾回收机制深度剖析与优化策略####
本文作为一篇技术性文章,深入探讨了Java虚拟机(JVM)中垃圾回收的工作原理,详细分析了标记-清除、复制算法、标记-压缩及分代收集等主流垃圾回收算法的特点和适用场景。通过实际案例,展示了不同GC(Garbage Collector)算法在应用中的表现差异,并针对大型应用提出了一系列优化策略,包括选择合适的GC算法、调整堆内存大小、并行与并发GC调优等,旨在帮助开发者更好地理解和优化Java应用的性能。 ####
40 0
|
2月前
|
存储 算法 Java
Java内存管理深度剖析与优化策略####
本文深入探讨了Java虚拟机(JVM)的内存管理机制,重点分析了堆内存的分配策略、垃圾回收算法以及如何通过调优提升应用性能。通过案例驱动的方式,揭示了常见内存泄漏的根源与解决策略,旨在为开发者提供实用的内存管理技巧,确保应用程序既高效又稳定地运行。 ####
|
10天前
|
缓存 算法 搜索推荐
Java中的算法优化与复杂度分析
在Java开发中,理解和优化算法的时间复杂度和空间复杂度是提升程序性能的关键。通过合理选择数据结构、避免重复计算、应用分治法等策略,可以显著提高算法效率。在实际开发中,应该根据具体需求和场景,选择合适的优化方法,从而编写出高效、可靠的代码。
25 6
|
1月前
|
存储 监控 小程序
Java中的线程池优化实践####
本文深入探讨了Java中线程池的工作原理,分析了常见的线程池类型及其适用场景,并通过实际案例展示了如何根据应用需求进行线程池的优化配置。文章首先介绍了线程池的基本概念和核心参数,随后详细阐述了几种常见的线程池实现(如FixedThreadPool、CachedThreadPool、ScheduledThreadPool等)的特点及使用场景。接着,通过一个电商系统订单处理的实际案例,分析了线程池参数设置不当导致的性能问题,并提出了相应的优化策略。最终,总结了线程池优化的最佳实践,旨在帮助开发者更好地利用Java线程池提升应用性能和稳定性。 ####
|
24天前
|
存储 Java
Java 11 的String是如何优化存储的?
本文介绍了Java中字符串存储优化的原理和实现。通过判断字符串是否全为拉丁字符,使用`byte`代替`char`存储,以节省空间。具体实现涉及`compress`和`toBytes`方法,前者用于尝试压缩字符串,后者则按常规方式存储。代码示例展示了如何根据配置决定使用哪种存储方式。
|
1月前
|
存储 算法 Java
Java 内存管理与优化:掌控堆与栈,雕琢高效代码
Java内存管理与优化是提升程序性能的关键。掌握堆与栈的运作机制,学习如何有效管理内存资源,雕琢出更加高效的代码,是每个Java开发者必备的技能。
55 5
|
1月前
|
存储 监控 算法
Java虚拟机(JVM)垃圾回收机制深度解析与优化策略####
本文旨在深入探讨Java虚拟机(JVM)的垃圾回收机制,揭示其工作原理、常见算法及参数调优方法。通过剖析垃圾回收的生命周期、内存区域划分以及GC日志分析,为开发者提供一套实用的JVM垃圾回收优化指南,助力提升Java应用的性能与稳定性。 ####
|
2月前
|
存储 缓存 安全
Java 集合框架优化:从基础到高级应用
《Java集合框架优化:从基础到高级应用》深入解析Java集合框架的核心原理与优化技巧,涵盖列表、集合、映射等常用数据结构,结合实际案例,指导开发者高效使用和优化Java集合。
44 4
|
2月前
|
监控 算法 Java
Java虚拟机垃圾回收机制深度剖析与优化策略####
【10月更文挑战第21天】 本文旨在深入探讨Java虚拟机(JVM)中的垃圾回收机制,揭示其工作原理、常见算法及参数调优技巧。通过案例分析,展示如何根据应用特性调整GC策略,以提升Java应用的性能和稳定性,为开发者提供实战中的优化指南。 ####
46 5
|
2月前
|
关系型数据库 MySQL Java
MySQL索引优化与Java应用实践
【11月更文挑战第25天】在大数据量和高并发的业务场景下,MySQL数据库的索引优化是提升查询性能的关键。本文将深入探讨MySQL索引的多种类型、优化策略及其在Java应用中的实践,通过历史背景、业务场景、底层原理的介绍,并结合Java示例代码,帮助Java架构师更好地理解并应用这些技术。
55 2