Advertisement

高薪程序员&面试题精讲系列100之如何对数据库进行优化(中)?

阅读量:

一. 面试题及剖析

1. 今日面试题

如何对数据库进行优化?

说说你是怎么进行数据库优化的?

2. 题目剖析

壹哥 在之前的文章中详细介绍了数据库优化的基本内容。今天这篇文章我将深入探讨数据库优化的具体实施方法。如果你还没有阅读过之前的介绍,请访问之前的链接。

资深开发者·高频面试题解析(第99集)——掌握数据库优化技巧(第一部分)

今天壹哥 会继续往下讲解第二部分优化措施,敬请各位继续关注。

二. 具体优化措施二(重点)

在上一节中,壹哥给大家介绍了几个数据库优化的方向,接下来我会就这几个方向进行详细讲解。

1. 优化索引

1.1 索引创建原则

壹哥 曾经分享过,在优化数据库性能方面索引发挥着不可或缺的作用。因此,在编写各种查询语句时我们通常会设计和应用索引以提升执行效率;当构建索引时应当遵循一些基本原则这样才能保证其长期稳定性和高性能;以下是我们总结的一些通用原则希望对大家有所帮助

  • 为唯一性字段创建索引 :如果一个表中存在唯一性的字段,比如主键,或者其他的唯一性字段,比如课程表的课程号,那我们就可以为该字段创建唯一性索引,该索引的值是唯一的,通过该索引就可以快速的确定某条记录。
  • 为经常进行搜索的字段创建索引 :如果某个字段经常用来做为查询条件,即经常出现在WHERE 子句中的列,该字段的查询速度将会影响整个表的查询速度。为这样的字段建立索引,可以提高整个表的查询速度。
  • 尽量为长度较短的列创建索引 :索引字段要尽量的小。一个CHAR(100)长度的列进行全文检索需要的时间,要比对CHAR(10)长度的列需要的时间要多。所以我们要尽量为长度较短的列创建索引,否则查询速度也会降低。
  • 对经常根据范围进行搜索的列创建索引 :我们可以在经常需要根据范围,比如<,<=,=,>,>=,BETWEEN,IN等进行搜索的列上创建索引,因为索引已经排序,其指定的范围是连续的。
  • 要满足最左前缀匹配原则 :对于组合索引,总是会从索引最前面的字段开始,接着往后,中间不会跳过。比如我们创建一个多列组合索引(name,age,sex),会先匹配name字段,再匹配age字段,最后匹配sex字段,中间是不会跳过的。MySQL会一直向右匹配,直到遇见范围查询(>、<、between、like),就会停止匹配。一般在创建多列索引时,where子句中使用最频繁的一列要放在最左边。
  • 尽量选择区分度高的列作为索引 :我们在创建索引时,要尽量选择区分度搞的列作为索引。比如我们可以选择学号来做索引,而不要选择性别来做索引。
  • 对经常order by的列创建索引 :我们可以对经常需要排序(order by)的列创建索引,因为索引已经排序,这样查询可以利用索引的排序,加快排序查询时间。
  • 尽量的扩展索引而不是新建索引 :假如我们某个表中已经有了a的索引,现在要创建(a,b)的索引,其实只需要修改原来的索引即可。
  • 索引列不要参与计算 :索引中的列不要参与计算,要保持该列职责的”单一“,比如我们有个主键索引id,这时执行 'xxx where id+1> 10' 的计算,在每次检查索引时,都会先将索引计算一次,然后再做比较,这个成本会很高。
  • 数据量少的表没必要创建索引 :如果一个表的数据量较少,本身进行查询的时间并不多,没必要为该表创建索引,否则可能遍历索引的时间比直接查询数据库的时间还多。
  • 删除不再使用或很少使用的索引 :表中的数据被大量更新后,如果原有的一些索引不再需要,我们应该删除对应的索引,以减少索引对更新操作的影响。
  • 要限制索引的数量 :虽然索引可以提升查询的速度,但并不是说索引创建的越多越好,这是因为索引本身也是需要占用磁盘空间的,索引越多,占用的磁盘空间就越大。而且在更新表后,索引也必须进行更新,甚至还可能需要重构。因此,索引越多更新表的时间就越长。另外MySQL在生成一个执行计划时也要考虑索引,这也要花费时间。过多的索引不仅不会优化查询,可能还会造成降低MySQL的性能。

在创建索引时应当遵循巴比熊的一系列具体规定,在违反这些规定的情况下将会导致索引无法正常运行。此外我们还有若干非强制性操作规范请各位遵循。

1.2 索引使用规范

接下来壹哥 会再给大家梳理几条索引的使用规范。

  • 推荐索引命名时应遵循特定规范;
  • 索引字段数量控制在5个以内;
  • 单表范围内的索引数量不应超过5个;
  • InnoDB表类型通常建议包含主键列(尤其是在高可用集群架构中此为主 URI项);
  • 在创建复合索引时,请优先考虑具有较高区分度的字段放置于前部;
  • 当执行UPDATE、DELETE操作时,请确保相关WHERE子句包含了必要的索引信息;
  • 在决定是否修改现有索引之前,请务必与DBA团队进行充分沟通;
  • 应谨慎避免无限制地增加索引数量,在实际应用中维护好现有 indexes 可能会对系统性能产生显著影响。

请各位注意:规则与规范之间存在明确区别。这些规定都是必须执行的条款。然而这些指导原则并非强制要求每位成员必须完全遵循。尽管如此,
但对这些指导原则我们应该尽可能地严格遵守,
因为它们将直接关系到系统性能的关键指标。

1.3 索引失效

另外,即便我们为某些列建立了索引,在执行查询操作时这些索引也可能无法发挥作用。这表明建立的索引失去了效用,并且会导致我们的查询效率无法得到改善。下面我会列出一些可能导致索引失效的因素供各位参考。

以下是改写后的文本

本节归纳了几种可能导致索引失效的情形,并深入探讨了这对SELECT语句优化的意义。举个例子来说,当面试官询问如何优化SELECT查询时,我们需要特别注意避免出现索引失效的情况。因此答案仍然围绕这些要点展开,只是回答方式有所调整。关于数据库索引的相关知识,在之前的文章中壹哥已经进行了详尽的阐述。至于其他关于索引的内容,则不在本次讨论范围内了。各位读者如有兴趣可以参考之前的优质文章:

\text{优化SQL查询:避免索引失效的关键点}

MySQL有哪些类型的索引?你是否了解其基本原理?在什么情况下索引可能会失效?

2. 优化存储操作

在之前的讲解中提到的几个优化措施中大部分是针对SELECT操作的,在实际使用过程中我们还可能遇到另一个可能会影响数据库性能的操作这涉及到INSERT存储操作的问题因此我们需要进一步提升INSERT存储操作的效率接下来将深入探讨如何优化INSERT存储操作的方法

在之前的讲解中提到的几个优化措施中大部分是针对SELECT操作的,在实际使用过程中我们还可能遇到另一个可能会影响数据库性能的操作这涉及到INSERT存储操作的问题因此我们需要进一步提升INSERT存储操作的效率接下来将深入探讨如何优化INSERT存储操作的方法

2.1 禁用索引

当向使用索引的表中插入记录时(即执行INSERT语句),MySQL系统会自动创建一个新的临时表用于生成这些记录的元数据,并将该临时表合并到主表之后。如果一次性进行大批量的数据导入操作(即执行INSERT + SET transaction commit),由于主表中的元数据必须先生成并复制到目标表中才能进行后续的数据复制操作(即SET transaction commit),因此此时必须先禁用主表上的所有约束条件(包括主键约束和其他非唯一键约束)。待所有数据完成插入后才重新启用这些约束条件。这种做法能够有效避免一次性大量数据导入过程中因频繁生成和复制元数据而导致的时间浪费问题。

2.1.1 禁用索引的语句

复制代码
    ALTER TABLE table_name DISABLE KEYS;

2.1.2 开启索引语句

复制代码
    ALTER TABLE table_name ENABLE KEYS;

MyISAM对于空表批量插入数据,则无需执行任何操作;其原因在于,在数据导入完成后才生成索引。

2.2 禁用唯一性检查

验证操作同样会减缓数据插入速度,在执行数据插入前应先关闭此验证功能,在数据全部输入完成后方能重新启用。

2.2.1 禁用唯一性检查的语句

复制代码
    SET UNIQUE_CHECKS = 0;

2.2.2 开启唯一性检查的语句

复制代码
    SET UNIQUE_CHECKS = 1;

2.3 禁用外键检查

在插入数据前避免对外键进行检查,在数据完成插入后重新执行该检查操作以提升数据插入效率。

2.3.1 禁用语句

复制代码
    SET foreign_key_checks = 0;

2.3.2 开启语句

复制代码
    SET foreign_key_checks = 1;

2.4 批量插入数据

我们在进行数据插入操作时可以选择不同的方式以提高效率。具体来说,在单次操作中我们可以采用一条INSERT语句来完成对单条记录的添加;而如果需要提升处理速度则可以一次批量处理多个记录将多个INSERT语句整合在一个操作中这样不仅能够减少数据库连接开销还能显著提升事务执行效率

一个SQL语句插入一条数据:

一个SQL语句插入多条数据:

2.5 禁止自动提交

在插入数据前启动无冲突自动提交机制,在数据完成插入后进行后续恢复操作,则能有效提升整体效率。

2.5.1 禁用语句

复制代码
    SET autocommit = 0;

2.5.2 开启语句

复制代码
    SET autocommit = 1;

这些操作手段不仅能够显著提升插入语句的运行效率,并且还能有效降低查询响应时间的同时这无疑也是一种优化数据库性能的重要策略

3. 选择合适的存储引擎

深入探讨了壹哥 在之前的文章中详细阐述了存储引擎的作用和类型。如需进一步了解相关内容,请参考以下文章。

高薪程序员&面试题精讲系列88之MySQL涉及哪些存储引擎!

我们知道,每个存储引擎都有其独特的特点,并且各自适应于特定的应用场景,在实际开发过程中,我们应当根据具体的场景需求来选择最适合的存储引擎类型。为了方便大家参考选择,在这里总结一下几个常用存储引擎的主要应用场景。

3.1 InnoDB的适用场景

  • 常执行UPDATE/INSERT操作的表,并能高效处理大量并发的写请求;
  • 为实现事务功能,默认推荐使用InnoDB数据库引擎;
  • 提供基于日志文件和自动事务回滚的支持,在故障恢复场景下确保数据完整性;
  • 不仅支持外键约束关系的建立(FOREIGN KEY),同时也支持主键字段自动生成(AUTO_INCREMENT)。

InnoDB的优势在于它支持了良好的事务处理能力、崩溃修复机制以及高效的并发控制能力。然而,在读取效率方面存在一定的局限性,并且占用的数据空间相对较大。因此,在需要实现事务关系管理、外键约束以及自增主键功能并追求高并发读取效率的场景下(即如果需要同时满足这些功能需求并且希望提升系统的并发读取性能),InnoDB是一个值得考虑的选择。

3.2 MyISAM的适用场景

该系统特别适合于对数据进行频繁查询操作的场景,并且能够高效地执行插入操作;
该系统不具备事务管理功能;
该系统特别适合于需要频繁进行COUNT统计的情况。

相对于其他数据库系统而言,在存储资源和处理速度方面MyISAM具有显著的优势,并且其主要缺陷在于不支持完整的事务性和高并发操作。因此,在对事务完整性无特殊需求或主要涉及SELECT和INSERT等操作的应用场景中,默认情况下推荐使用该数据库引擎来进行表结构设计。具体而言,由于其高效的存储机制和快速的数据访问能力,在处理大规模数据查询时表现出色,特别适合用于那些需要频繁进行插入操作的数据表。

4. 优化服务器系统配置

除了对现有SQL语句、索引以及数据库架构和表结构进行优化外,在进一步提升数据库性能方面,建议对服务器系统的相关参数进行调优配置。

在MySQL服务器中有很多可供调优的参数数量丰富。壹哥在此分享几个常用且有效的优化参数设置方法。如有更多细节和具体操作建议,则可参考官方文档中的详细说明。

  • back_log**:** back_log值可以指出在MySQL暂停应答新请求之前的时间内,会有多少个请求可以被存储在堆栈中。即如果MySQL的连接数据达到max_connections时,新来的请求将会被存在堆栈中,以等待某一连接释放资源,该堆栈的数量即back_log。如果等待连接的数量超过back_log,将不被授予连接资源。我们可以把默认的50升至500。
  • wait_timeout**:** 数据库连接闲置时间 ,闲置连接会占用内存资源,当MySQL连接闲置超过一定时间后将会被强行关闭。MySQL默认的wait-timeout值为8个小时,我们可以把默认的8小时减到半小时。
  • max_connections**:** MySQL的最大连接数,如果服务器的并发连接请求量比较大,建议调高此值,以增加并行连接数量。MySQL会为每个连接提供一个连接缓冲区,所以如果连接数越多,内存开销就会越多,所以要适当调整该值,不能盲目提高设值。
  • max_user_connection**:** 每个数据库的最大并行用户连接数,简单说就是指同一个账号能够同时连接到MySQL服务的最大连接数。设置为0表示不限制,最好设置一个合理上限。
  • thread_concurrency**:** 并发线程数,该值设置的正确与否, 对MySQL的性能影响很大,thread_concurrency应设为CPU核数的2倍。
  • skip_name_resolve : 禁止对外部连接进行DNS解析,消除DNS的解析时间,但需要所有远程主机利用IP进行访问。
  • key_buffer_size**:** 索引块的缓存大小,增加会提升索引处理速度,对MyISAM表性能影响最大。对于内存4G左右,可设为256M或384M,我们可以通过查询show status like 'key_read%',保证key_reads / key_read_requests在0.1%以下最好。
  • innodb_buffer_pool_size : 缓存数据块和索引块,对InnoDB表的性能影响最大。通过查询show status like 'Innodb_buffer_pool_read%',保证 (Innodb_buffer_pool_read_requests – Innodb_buffer_pool_reads) / Innodb_buffer_pool_read_requests的结果越高越好。
  • innodb_additional_mem_pool_size : InnoDB存储引擎用来存放数据字典信息以及一些内部数据结构的内存空间大小。当数据库对象非常多的时候,适当调整该参数的大小以确保所有数据都能存放在内存中提高访问效率;当过小的时候,MySQL会记录Warning信息到数据库的错误日志中,这时就需要该调整这个参数大小。
  • innodb_log_buffer_size : InnoDB存储引擎的事务日志所使用的缓冲区,一般来说不建议超过32MB。
  • query_cache_size**:** 缓存MySQL中的ResultSet,也就是一条SQL语句执行的结果集,所以只能针对select语句。当某个表的数据有任何的变化,都会导致所有引用了该表的select语句在Query Cache中的缓存数据失效。所以,当我们的数据变化非常频繁的情况下,使用Query Cache可能会得不偿失。根据命中率(Qcache_hits/(Qcache_hits+Qcache_inserts)*100))进行调整,一般不建议太大,256MB可能已经差不多了,大型的配置型静态数据可适当调大。可以通过命令show status like 'Qcache_%'来查看目前系统Query catch使用大小。
  • read_buffer_size : MySQL的读入缓冲区大小。对表进行顺序扫描的请求将分配一个读入缓冲区,MySQL会为它分配一段内存缓冲区。如果对表的顺序扫描请求非常频繁,可以通过增加该变量值以及内存缓冲区大小来提高其性能。
  • sort_buffer_size : MySQL执行排序使用的缓冲大小。如果想要增加ORDER BY的速度,首先要看是否可以让MySQL使用索引而不是额外的排序阶段。如果不能,可以尝试增加sort_buffer_size变量的大小。
  • read_rnd_buffer_size : MySQL的随机读取缓冲区大小。当按任意顺序读取行时(例如按照排序顺序),将分配一个随机读缓存区。进行排序查询时,MySQL首先会扫描一遍该缓冲,以避免磁盘搜索,提高查询速度,如果需要排序大量数据,可适当调高该值。但MySQL会为每个客户连接发放该缓冲空间,所以应尽量适当设置该值,以避免内存开销过大。
  • record_buffer : 每个进行一个顺序扫描的线程为其扫描的每张表分配这个大小的一个缓冲区。如果你做很多顺序扫描,可能想要增加该值。
  • thread_cache_size : 保存当前没有与连接关联但是准备为后面新的连接服务的线程,可以快速响应连接的线程请求而无需创建新的。
  • table_cache : 类似于thread_cache_size,但用来缓存表文件,对InnoDB效果不大,主要用于MyISAM存储引擎。
  • **default-storage-engine :**设置创建数据库及表时的默认存储引擎。

这些系统参数是否配置得足够合理呢?仅凭直觉进行猜测难以准确判断其合理性。为了更精确地评估 MySQL 的参数设置情况,请参考以下推荐的第三方测试工具:

SysBench: 款模块化设计、具备跨平台兼容性和多线程支持的高性能测试基准工具;

IIBench-MYSQL: 一种基于 Java 语言实现的MySQL/Percona/MariaDB 索引插入性能测试工具;

TPCC-MYSQL: 则是由 Percona 开发团队所推出的 TPC-C 测试基准方案。

三. 结语

本次我们由壹哥为大家归纳若干优化措施。随后会由壹哥在后续的文章中继续讲解其他方面的优化方案。期待诸位持续关注与学习交流!

全部评论 (0)

还没有任何评论哟~