MySQL从入门到精通【进阶篇】20个SQL优化定律+10条DBA经验


在这里插入图片描述

0.前言

在现代软件应用中,数据库一直起着至关重要的角色。尽管NoSQL`` 和 NewSQL 数据库的使用日益增加,但SQL数据库(结构化查询语言数据库),例如``MySQL、NoSQLPostgreSQLSQLServer`等仍广泛应用在各种业务领域。对于SQL查询的优化,无疑是每个开发者和数据库管理员(DBA)必备的一种技能。

SQL查询优化是一个复杂的过程,它包括对SQL语句的合理编写,数据库结构的合理设计,索引的科学使用,配置的合理调整,数据和硬件环境的适当匹配等方面。几乎每个涉及SQL查询的环节,都可能影响查询的效率。

我们本文解读主流的20个SQL优化的定律和建议。不要误解。这些定律不是具体的技术操作,而是一种指导和引导思路,不仅适用于某种特定的数据库,也适用于大部分应用场景。

这些定律将帮助在理解何时、在何处以及如何进行SQL的优化,它们将深入浅出地揭示SQL优化的本质,引领你领略简洁、高效、可维护SQL语句的魅力。

在开启这次优化之旅前,我们要保持这样一个心态:优化不是万能的,任何优化都是在一定场景下、对于特定问题的解决方案。因此,理解并掌握这些优化定律,更重要的是学会将这些定律融入到你的实战经验中,成为你设计和优化SQL的“内功”。不要追求所谓的“绝对优化”,而应密切关注业务需求,兼顾性能和开发效率

OK,让我们开始这个旅程,一同探索SQL优化的奥秘。

1. 20个SQL优化定律

翻译而来,也可以称为20个优化原则,或者SQL编写原则
在SQL优化方面的知识和实践都是在编程和数据库管理的众多专家的经验总结和实践基础上积累形成的。这20个SQL优化定律可能不存在一个特定的参考来源,它们更可能是由多个来源、多个经验丰富的数据库管理员和开发者的知识和经验整合形成的。

虽然这些优化定律已经被广泛接受和使用,但在具体应用时,需要结合具体的数据库类型(比如MySQL, NoSQL , SQL Server等)、数据结构以及查询需求来灵活应用。同时,数据库的优化是一个动态的、持续性的过程,涉及到的因素很多,包括但不限于数据的增减变化、硬件环境变化、系统升级、业务需求变更等。

1.1. Only Retrieve The Data You Really Need

避免SELECT *,尽量选择真正需要的字段,减少数据传输的负载。

这一项很直白,没啥需要解读的,这是上SQL的第一节课老师就告诉的。

1.2. Be Aware of The Index

制定有效的索引策略,如按照where条件或者join字段建索引,可以极大提升查询效率。

我们来解读解读这一条定律,这个也是大多数SQL优化的帖子告诉的。相比大家都一些实践经验了,
有效地使用索引是提高查询性能的关键。一份应用到位的索引策略可以使得数据库执行查询的时间从小时级别降至秒级别
一些制定有效索引策略的要点:

  1. 为常用查询语句中的WHERE子句中的字段建立索引:如果某个字段经常出现在查询的WHERE子句中,那么为该字段建立索引可以大大提升查询的效率。

    例如:

    CREATE INDEX idx_students_age ON Students(Age);
    
  2. 为经常被JOIN操作使用的字段创建索引:如果在JOIN操作中的字段没有被索引,那么数据库可能需要执行全表搜索来查找匹配的行,这是效率非常低的。而对这些字段建立索引,可以显著提升JOIN的性能。

    例如:

    CREATE INDEX idx_orders_customer_id ON Orders(CustomerID);
    
  3. 使用复合索引来优化多列查询:如果的查询语句经常需要在多个列上进行搜索,那么可以考虑创建一个包括这些列的复合索引。

    例如:

    CREATE INDEX idx_students_age_name ON Students(Age, Name);
    
  4. 避免过度索引:并非所有的列都需要建立索引。过度索引会导致插入,更新和删除操作的性能下降,因为每次这些操作发生时,索引都需要被更新。我们需要找到适当的平衡,只为重要的、常用的查询路径创建索引。

1.3. Use Joins Carefully

JOIN查询的效率通常高于子查询,但要注意JOIN的顺序,正确的顺序可以减少中间结果集的大小。

在SQL查询优化中,JOIN操作确实通常比子查询更为高效,原因在于JOIN操作可以在读取数据时就进行比较和过滤,而子查询通常需要首先生成一个中间结果集,再进行后续的处理,这在处理大数据量时可能会引起效率问题。

而在处理JOIN查询时,JOIN的顺序也非常关键。一个好的经验法则是,尽可能先JOIN行数较少的表,这将大大减少中间结果集的大小,从而提高查询效率

MySQL会执行JOIN优化,尝试找出最佳的JOIN顺序,但不总是能找出最优的顺序,尤其是在复杂查询中。因此,手动指定JOIN顺序,或者在查询中使用STRAIGHT_JOIN来强制MySQL按照指定的顺序来执行JOIN,有时能帮助改善性能

例如以下查询:

    SELECT * FROM Orders 
    STRAIGHT_JOIN Customers ON Orders.CustomerID = Customers.CustomerID 
    WHERE Customers.Country = 'USA' 

将优先执行Customers表的筛选操作,先减少结果集的大小,再进行JOIN操作,能有效提升查询效率。

优化JOIN查询是个需要经验和技巧的过程,JOIN的顺序只是其中的一部分。我们还需要关注其他方面,如合理使用索引,避免全表扫描等,才能全面提升查询性能。

1.4. Avoid Using NOT IN

尽量避免使用NOT IN,因为NOT IN会导致遍历全表。可以考虑用NOT EXISTS替换。

避免使用 NOT IN 是提升查询性能的一种有效策略。 NOT IN 子句在处理查询时会引发全表扫描,就算在字段上建立了索引,SQL引擎也不会使用相应的索引,这样将严重影响查询性能。

这是因为数据库无法预测 NOT IN 列表中的值,不得不查询全表以确认没有漏掉任何可能的结果

虽然 NOT IN 和 NOT EXISTS 能完成相似的查询任务,但在实际使用中,我们需要注意它们在性能上的差异,结合实际需求选择最优的方案。

我们来思考一下考虑以下查询:

SELECT * FROM Students WHERE Age NOT IN (18, 19);

我们想想就知道,该查询需要遍历全表,检查每一行的 Age 值是否在列表中,这将消耗大量资源

而 NOT EXISTS 则是一种替代方法,可以优化 NOT IN 造成的性能问题。NOT EXISTS 的性能一般比 NOT IN 好,尤其是在关联子查询返回的行数较多时。

NOT EXISTS 子句在SQL查询中用于确定主查询的某行与子查询没有匹配的行。它在子查询中为每行执行一次,直到发现匹配的行为止。一旦找到匹配的行,它立即停止进一步处理,返回结果表明主查询的这行不存在与子查询匹配的行,即"不存在"。

NOT EXISTS 是一种半连接(Semi-Join),它只关心是否存在匹配的行,而不关心匹配的行有多少。在子查询中找到首个匹配的行后,查询就会停止,不再进一步搜索,这就是它的执行原理。

NOT EXISTS 在处理查询时相对更高效,主要原因有:

1. NOT EXISTS 具有更短的测试周期,
2. 因为它在发现第一个匹配行时就停止检查,这就意味着数据引擎可以尽早停止扫描操作。

3. 数据库优化器通常能对 EXISTS 的查询进行优化,以提高其性能。

4. NOT EXISTS 通常能使用索引,而 NOT IN 无法使用索引进行查询优化。

这个测试不好测,因为我测试了几次发现这两者的性能差异基本上不大,可能是我造的数据太小或者其他原因,导致NOT IN 和 NOT EXISTS 可能产生近似相同的结果,但是就性能而言,NOT EXISTS 通常是更优的选择。大家就按这个铁律搞不要深究了,等后面有时间了,我在验证几次。

用 NOT EXISTS 重写上面的查询,可能如下所示:

SELECT s.* FROM Students s 
WHERE NOT EXISTS (SELECT 1 FROM (VALUES (18), (19)) AS v(Age) WHERE s.Age = v.Age);

1.5. Use UNION ALL Instead of UNION

UNION需要去重,消耗的性能相对较大。若数据默认不重复,优先考虑使用UNION ALL。

UNIONUNION ALL都被用于合并两个或两个以上SELECT语句的结果集。尽管它们都可以进行这项操作,但是在处理方式和性能方面存在明显差异。

UNION在合并结果集后会进行去重操作,这意味着它会删除重复的行。为了完成这项操作,数据库需要进行额外的工作,比如排序或哈希,这可能会消耗 substantial amount of resources,尤其在处理大量数据时。

例如:

SELECT column_name(s) FROM table1
UNION
SELECT column_name(s) FROM table2;

以上查询将返回来自两个表的唯一值。

相反,UNION ALL简单地把两个结果集合并在一起,不会去除重复行。这使得UNION ALL比UNION更快,因为它不需要额外的处理来删除重复的行。

例如:

SELECT column_name(s) FROM table1
UNION ALL
SELECT column_name(s) FROM table2;

以上查询将返回来自两个表的所有值,包括重复的值。

因此,如果知道或者能确保两个结果集不含有重复的值,应该优先考虑使用UNION
ALL
。然而,如果需要确保结果集中的值是唯一的,那么应该使用UNION,尽管其性能可能相对较差。

1.6. Mind the NULLs

NULL可以导致索引失效,需要特别注意处理。

在数据库中,对待NULL值的处理是需要特别注意的,因为NULL值在数据库的行为中具有一些独特的性质。特别是在索引和查询性能方面,NULL的行为可能会出乎的意料。

  1. 在索引中,NULL值可能会被数据库引擎特殊处理。有些数据库系统(比如MySQL实现的MyISAM和InnoDB引擎)会在索引中包含NULL值,但有些系统(比如NoSQL )则不会。这意味着,如果搜索一个字段的值为NULL,可能会导致全表扫描,因为数据库不能利用索引进行查询。例如,以下查询在某些数据库系统中,例如NoSQL ,可能不会使用索引:

    SELECT * FROM Students WHERE Age IS NULL;
    
  2. 在比较操作中,NULL也有其特殊性。在SQL中,NULL表示未知值,对于任何比较操作,包括=、<、>、<>等等,其结果都是未知,即NULL。即使对NULL使用=进行比较,结果也是NULL,而不是TRUEFALSE。这意味着,下面的查询可能不会返回期望的结果:

    SELECT * FROM Students WHERE Age = NULL;
    

上述查询在大多数数据库系统中将不会返回任何结果,因为NULL不等同于任何值,包括它自己。

在处理含有NULL值的字段时,我们应该特别小心。如果可能,应该尽量避免NULL值出现在索引字段中,这样可以确保查询能有效利用索引进行优化。同时,对NULL值的比较应使用IS NULL或者IS NOT NULL,而不是=或者<>。

此处就多说一些处理NULL值的常见方法吧,虽然跑题了,但是在工作中是很常见和重要的:

  1. 判断NULL值,可以使用 IS NULLIS NOT NULL 来检查字段是否为NULL值。
SELECT * FROM table_name WHERE column_name IS NULL;
  1. 在查询中,可以使用COALESCEIFNULL函数来处理NULL值,将其替换为特定的值或执行某种操作。
SELECT column_name, COALESCE(column_name, 'N/A') AS new_column FROM table_name;
  1. 包含NULL值的计算,在对包含NULL值的字段进行计算时需要格外小心,因为任何与NULL值进行运算的结果都将得到NULL值。可以使用IFNULLISNULL函数来处理这种情况。例如:
SELECT column1, column2, IFNULL(column1 * column2, 0) AS result FROM table_name;
  1. 处理NULL值的排序,当对含有NULL值的字段进行排序时,NULL值会被默认放在最后,可以使用ORDER BY语句中的NULLS FIRSTNULLS LAST来指定NULL值的排序位置
SELECT column_name FROM table_name ORDER BY column_name ASC NULLS FIRST;

处理NULL值时需要注意避免产生不正确的结果,所以在编写查询语句或进行计算时要仔细考虑NULL值的处理方式。

1.7. Do Not Use Functions in Predicates

谓词中的函数会导致索引失效,应尽可能避免。

通俗点理解就是说不能在查询条件左侧使用函数,还是我们举例来说明更直观一点。 谓词中的函数会导致索引失效,负面影响查询性能,尽量避免使用。选择合适的数据库设计和查询方式,有助于提高性能和效率。

函数会在其输入上运行,并返回一个输出。因此,当在谓词中使用函数时,数据库实际上不得不计算每一行的函数结果,然后才能决定是否满足谓词条件。这增加了处理每一行所需的计算量,并且使得数据库无法利用索引来加速操作

看下面这个例子:

SELECT * 
FROM Customers 
WHERE MONTH(BirthDate) = 7;

上述查询是搜索所有生日在7月份的顾客,但这样做不能使用索引,因为它在整个表的每一行上都需要计算函数MONTH(BirthDate)的值。

如何优化呢?一种做法是把条件变换为可以利用索引的形式。比如,我们可能会把上面的查询改写为:

SELECT * 
FROM Customers 
WHERE BirthDate >= '2021-07-01' 
AND BirthDate < '2021-08-01';

进一步优化,有时我们可以在数据库设计阶段就预见到这种需求,并进行相应的设计,比如在生日月份上建立一个索引,从而避免在查询时进行计算。

1.8. The More SQL Statements, the Slower

尽量减少SQL语句的数量,一个复杂的SQL通常比多个简单的SQL快。

其实这一定律我觉得很直白没有必要讲,有个一定基础的开发同学应该都明白,一条SQL的执行过程要搞哪些事情,为了照顾小白同学,我还是大概讲讲


为什么说一个复杂的SQL语句确实往往比多个简单的SQL语句执行得更快


原因在于数据库处理查询时,不仅需要执行语句本身,还需要处理与之相关的许多其他操作,比如解析查询、编译查询、建立和撤销数据库连接等。每个SQL语句都需要这些步骤,因此,执行多个独立的SQL语句会导致这些额外操作的开销累积。
执行一个复杂的SQL查询,虽然查询本身的解析和编译开销可能较大,但只需要进行一次。这样,就可以一次性从数据库中获取所有需要的信息,而无需多次往返于应用程序和数据库之间。


我们还是举例说明,以一个简单的例子为说明,假设有一个电商应用,需要获取特定订单的所有产品详情。可以使用两种策略

  1. 多个简单的SQL查询:先查询订单中所有产品的ID,然后对每个产品ID执行一个单独的查询以获取其详细信息。

    SELECT product_id FROM Orders WHERE order_id = 123;
    SELECT * FROM Products WHERE product_id = 1;
    SELECT * FROM Products WHERE product_id = 2;
    ...
    SELECT * FROM Products WHERE product_id = N;
    
  2. 一个复杂的SQL查询:利用连接操作(JOIN)把所有必要的信息集中在一个查询中获取。

    SELECT Orders.order_id, Products.* 
    FROM Orders 
    JOIN Products ON Orders.product_id = Products.product_id
    WHERE Orders.order_id = 123;
    

虽然后一种方法的查询语句更复杂,但其执行速度可能比前一种方法快得多,原因在于它避免了多次查询和数据库连接的开销。哈哈,此定律好像是我强行解读,大家可以刚一下,因为这个定律是总结出的大多数场景,如果在特殊情况下多个查询可能比关联查询效率会高,只是可能哦。

同时,一个复杂的查询也有助于保持数据库的处理逻辑原子性,这非常重要,尤其是在并发环境中。

切记,不要纠枉过正。我不是鼓励大家使用大SQL。要根据场景来定。尽管大型的SQL查询可能需要更多的时间来编写和调试,但在执行速度和效率方面,通常是优于多个小查询的。也包括有的同学写的循环嵌套执行SQL的屎山代码哈哈。

1.9. Use the Database for What It Is Built For

尽量将计算放到数据库中执行,除非对结果集要做的运算特别复杂,
否则数据库通常会比代码执行得快。

这句怎么理解呢?我其实不太赞同这个,我们公司的SQL规范中,不允许将业务逻辑以来SQL计算方式实现,更不允许自己去建个函数,存储过程,触发器更是不要碰,简直就是给业务代码下毒。因为这些都是有血泪史的,最直白一点就是出错了都不知道是哪的造成的,连在线debug 或者查日志的机会都没有,甚至在项目交接,项目数据库类型迁移(比如MySQL迁移到NoSQL 或者PGSQL)都是灾难。ε=(´ο`*)))唉不说了,说多了都是泪。此处银行系统的同学们可以忽略,那里面的大量报表,作业等等都是历史问题,没啥好说的,遵守公司的规范和大佬们留下的遗产。
如果强行解读话,我们也能从另一方面唠唠

  1. 数据库系统被设计为能够高效地处理大量数据。它们可以利用索引、查询优化器、并行处理等技术来加速数据处理,这些都是普通的应用程序所没有的。因此,尽可能利用数据库的计算能力通常都是一个好策略。

  2. 当在数据库中执行计算时,数据不需要通过网络传输到应用程序中,这可以大大降低网络延迟以及数据序列化和反序列化的开销。数据在数据库内部的处理也可以利用数据库的并行计算和内存管理等优化手段。

例如,假设想要计算一个订单的总价格,可以让数据库执行以下查询:

SELECT SUM(price * quantity) 
FROM OrderItems 
WHERE order_id = 1234;

而不是把所有的订单项取回应用程序中,然后在应用程序中计算总价。

还是那句话尽管在大多数情况下,让数据库执行计算都是更好的选择,但也存在一些例外。对于一些复杂的计算,例如涉及到复杂的算法或者业务规则的,数据库可能不是最佳的地方来处理这些逻辑。而且,把大量的计算负载放在数据库上,也有可能会影响数据库的性能和稳定性。因此,需要权衡一下,找到一个合理的平衡点。总的来说,尽量将让数据库做它擅长的事情——处理和计算数据,而应用程序则主要负责处理业务逻辑和用户交互。

1.10. Use LIMIT to Sample Result Data

使用LIMIT取一部分数据做样本分析,可以避免全表扫描,提升效率。

这个没啥好解读的,LIMIT关键字用于限制SQL查询结果的数据行数。可以使用它来获取一部分数据样本进行分析,这可以节省大量的时间和系统资源,避免了全表扫描。特别适用于那些只需要获取一部分数据进行初始分析或者快速预览的情况。

1.11. Optimize Like Statements

Like语句中针对前导%做优化,避免全表扫描。

我们来解读一下这一句定律,其实我们在多数SQL优化的帖子中总是能看到,最左匹配原则,其实就是和这个差不多。在SQL查询中,LIKE语句常用于进行模式匹配。但当我们在LIKE语句的搜索模式中使用前导百分号(%)时,这会导致索引无法使用,从而引发全表的扫描,如下查询:

SELECT * FROM Users WHERE name LIKE '%Smith';

SQL查询将搜索Users表中所有姓为Smith的用户,然而,由于模式以百分号(%)开头,数据库无法使用索引(如果存在的话),不得不遍历全表,搜索每一行。

为了改善查询性能,我们可以尽量避免使用前导百分号。如果我们知道搜索模式的开始部分,那么就应该使用它,而非百分号,如下查询将会搜索所有姓以Smith开头的用户。由于模式不再以百分号开头,数据库就可以使用索引(如果存在的话),从而提升查询的性能。 :

SELECT * FROM Users WHERE name LIKE 'Smith%';

听一个社群朋友说,有个面试官问,如果业务需求是必须使用前导%来模糊查询,怎么优化,直接把他给问懵逼了,在他的记忆力左模糊无法使用索引,也就没法优化了,其实不然,只要学艺够精,天无绝人之路
还有另外两个优化方法,一个优化方法是,可以考虑使用全文搜索,
第二个方法是使用倒序字段存储以减少全表扫描
。我想面试官考察的是对知识的理解程度,而不是只背了一些八股文。好了,我们来说说这两种优化方式。
当无法避免使用前导%时,确实可以帮助优化查询。

  1. 使用全文搜索: 全文搜索是一种强大的技术,可以帮助在文本数据中高效地进行搜索。它在执行搜索时不仅可以忽略前导%,还可以返回相关性得分,这是普通的LIKE查询无法做到的。在MySQL中,可以使用MATCH ... AGAINST语句进行全文搜索。需要注意的是,并非所有数据库系统都支持全文搜索,并且全文搜索需要在相关列上设置全文索引。

  2. 使用倒序字段: 这是另一种对前导%优化的技术。基本思路是在数据库中保留一个字段的反向拷贝。例如,如果经常需要搜索以特定字符串结尾的用户名,那么可以创建一个新列来保存用户名的反向拷贝,然后在该列上进行“以给定字符串开始”的搜索。这样,就可以利用索引进行搜索。例如:

    -- 原始查询,不会使用索引
    SELECT * FROM Users WHERE username LIKE '%son';
    
    -- 使用倒序字段进行查询,可以使用索引
    SELECT * FROM Users WHERE reversed_username LIKE 'nos%';  -- 假设reversed_username是username的反向拷贝
    
  1. 这些都是一些常见的全表扫描的优化方法。但是这些都是有一些前提。这些优化方法都需要在数据库设计阶段进行一些前期工作(比如设置全文索引或创建倒序字段)。因此,优化的关键是对的具体应用需求有深入的理解,以便能够选择最适合的优化策略。

  2. 虽然前导百分号的模式匹配为我们提供了强大的搜索能力,但对性能的影响也需要我们关注。我们在设计数据库和编写查询时,应尽量优化LIKE语句,避免全表扫描。

1.12. Mind the Column DataTypes

注意列类型,避免因数据类型转换导致的性能下降。

其实我们在工作中应该发现过两种情况,一种是字段类型不匹配强制导致索索引失效,一种是数据集编码不匹配,一个表里定义的是utf8,一个表里定义的是utf8mb4 坑死人不偿命,任怎么排查都不好发现问题,都是血泪教训。

数据类型不匹配

当我们在WHERE条件或者JOIN操作中使用不同数据类型的列进行比较时,SQL查询可能会发生隐式转换。这种转换可能会导致索引失效,使得查询无法利用索引进行优化,甚至在某些情况下可能导致错误的查询结果。例如 由于order_id是整数类型,但使用了字符串进行比较,因此数据库需要将所有的order_id转换为字符串,这会导致无法使用order_id上的索引。

SELECT * 
FROM Orders 
WHERE order_id = '12345';  -- 假设order_id是整数类型

数据集编码不匹配

数据集编码不匹配也是一个常见的问题,尤其是在处理多语种或者包含特殊字符的文本时。不同的表可能会采用不同的字符集编码,如utf8和utf8mb4,当两者进行数据交互时,就可能会出现无法预料的问题。例如,可能会遇到乱码、字符丢失或者查询性能下降等情况。

1.13. Use Transactions

有必要的使用事务能保持数据的一致性,并优化数据的修改操作。

这个没啥解读的,这是作为开发人员应知应会,基础知识,如果做不到该用事务的时候正确使用事务,就没必要搞SQL优化了,连正确性都保证不了,还搞什么性能。天天业务都有一堆问题,需要修正了,还有时间考虑性能,天方夜谭。

1.14. Use Prepared Statements

使用预备语句防止SQL注入并能提高性能。

这个没啥说的,学JDBC的时候老师就告诉了Prepared Statements。

1.15. Leverage Partial Indexes

利用部分索引,如在性别列设立部分索引。

其实这个要说下。对于MySQL而言,不支持创建部分索引的功能。因此,即便知道的数据有特定的模式或者分布,仍然不能创建只针对这部分数据的索引,
虽然我们学习到了这个定律,但是遗憾的是MySQL还不支持创建部分索引(Partial Index)。在MySQL中,每个索引都会对表中的每一行记录都进行索引,无法对表中的部分记录进行索引

我们可以思考一些方法可以实现类似的效果。例如,你可以考虑创建一个保存活跃用户的新表或者新视图,并在该新表或视图上创建索引。然后,可以在需要时查询这个新表或视图,而不是原始的大表。这样就能间接的能够提高查询的效率。也达到了曲线救国目的。
例如创建了一个新的视图ActiveUsers,只包含活跃的用户。然后,你就可以在查询活跃用户时查询这个视图,而不是原始的用户表。

CREATE VIEW ActiveUsers AS 
SELECT * 
FROM Users 
WHERE isActive = 1;

尽管这种方法不能真正地实现部分索引,并且在某些情况下可能会导致数据冗余或者数据不一致,但却是在MySQL中实现类似部分索引功能的一种可行方法。如果你的应用场景适合这种方法,那么完全可以考虑使用。

另外 在某些其他数据库系统中,如PostgreSQL ,SQLite等,是支持创建部分索引的。如果你的应用场景极其需要部分索引,而且不受数据库系统的限制,那么也可以考虑切换到支持部分索引的数据库系统。

还可以可以考虑使用其他一些策略来替代部分索引,例如,可以创建一个新的表,其中只包含需要索引的行。或者,对于布尔类型的字段,可以尝试使用位映射(bitmaps)来存储和查询数据。

方法总比困难多,没有最优解,我们可以寻找靠近最优解的替代方案。

16. Use Materialized Views

使用物化视图,节省计算量,提高效率。

也很遗憾,MySQL不支持物化视图。目前NoSQLPostgreSQL 支持。我们学习的事SQL优化守则,不局限在MySQL上。

物化视图的概念是一种特殊的数据库视图,它可以将查询结果进行物理存储,与常规的数据库视图相比,物化视图在数据更新时进行数据同步,可以大大提高数据查询的效率。

不仅仅在MySQL中,其实在多数数据库中,视图都是一个虚拟表,当我们对其进行查询时,实际上是执行对应的SELECT语句,生成临时表,并返回结果。这意味着,每次查询视图,后台都需要进行一次查询计算,包括连接、筛选、排序等操作。

然而,物化视图在创建的时候就已经执行了SELECT语句,并将结果存储起来。这样,当我们查询物化视图的时候,实际上查询的是已经计算好的结果集,避免了重复进行计算,大大提高了查询效率,尤其适合在数据量大,且查询复杂的场景下使用。
创建了一个名为SalesSummary的物化视图,它包含了每种产品的总销售额。当我们需要查询销售汇总信息时,只需查询这个物化视图即可。

CREATE MATERIALIZED VIEW SalesSummary AS
SELECT product_id, SUM(sales) total_sales
FROM Sales 
GROUP BY product_id;

有利有弊双刃剑,我们想想都知道,物化视图的数据并非实时的。当原始表(如上例中的Sales表)的数据发生变化时,物化视图的数据并不会立即更新。我们需要通过REFRESH命令手动更新物化视图,或者使用一些工具或者数据库的特性来自动维护物化视图。

在部分数据库系统中,如NoSQLPostgreSQL ,物化视图是一个内置的特性。然而,在MySQL中,我们可能需要通过一些额外的手段来实现类似的功能。例如,可以创建一个新的表来模拟物化视图,并编写触发器或者使用事件调度器来在原始数据变化时更新这个表,但这个措施笔者是强烈不建议,坑太多。

17. Use Appropriate Isolation Level

选择合适的事务隔离级别,平衡并发性能和数据一致性。

这块我就不详细解读了,大家应该都理解。
在 SQL 标准中定义了四种事务隔离级别,它们由低到高分别是:读未提交(Read Uncommitted),读已提交(Read Committed),可重复读(Repeatable Read),串行读(Serializable)。每一个级别都对应一种平衡点,它们分别解决了脏读,不可重复读,幻读这三类问题。选择合适的事务隔离级别需要根据业务需求和系统的负载情况进行取舍。如果对一致性要求较高,应该选择高的隔离级别;而如果更重视性能,可以选择低的隔离级别。这是因为,增加隔离级别可以减少数据不一致的问题,但同时也可能会降低并发性能,因为更高的隔离级别需要更复杂的锁管理,可能会导致事务等待

18. Analyze Your Data

深入理解的数据,找出最适合的优化策略。==也可以翻译为进行SQL执行计划分析==

只有深入理解你的数据,你才能找出最适合的优化策略。

数据分析

当你试图优化一个关键查询时,首先要了解查询所处理的数据的大致情况。你需要知道表中有多少行数据,所返回的结果集大约有多大,以及这些数据的分布情况等基本信息。数据库通常提供了一些内置的函数或者工具来帮助你进行数据分析。
你说得对,无论我们是使用什么样的数据库系统,它们通常都会提供一些内置的工具和函数来帮助我们更好的理解和分析我们的数据。这些工具和函数可以帮助我们更好地了解数据的基本信息,包括数据的数量、分布、变化趋势,等等。,还有一些开源或者商业的第三方工具,如pgAdmin, Toad, MySQL Workbench等,它们通常提供了一些图形化的界面,并集成了许多功能,如查询分析、索引优化建议、性能监控等,对于数据库性能优化同样很有帮助。

数据库提供的数据分析工具:

  1. MySQL:

    • SHOW TABLE STATUS: 这个命令可以提供表的大致信息,包括行数,数据的尺寸等。
    • EXPLAIN: 这个命令可以分析SQL查询的执行计划,帮助理解和优化查询语句。我们后续专门写一篇关于Explain 命令的结果解析的文章。
  2. PostgreSQL :

    • EXPLAIN: PostgreSQL 中也有EXPLAIN命令,用于分析查询的执行计划。
    • pg_stat_ 视图:* 这一系列的视图提供了关于表和索引使用的统计信息。例如,pg_stat_user_tables 视图包含了用户表的访问统计信息。
    • pg_stat_activity: 这个视图展示了当前系统中的活动会话,可以用于了解数据库的活动情况。
  3. NoSQL :

    • DBMS_STATS: 这个包提供了一系列的过程和函数,用于收集和管理数据库的统计信息。
    • EXPLAIN PLAN: NoSQL 中的EXPLAIN PLAN命令,用于显示SQL查询的执行计划。
    • V$ views: 这一系列的视图提供了大量的关于数据库内部工作的信息,包括性能、等待事件,等等。

查询计划分析

查看并理解查询的执行计划是优化SQL查询的关键步骤。查询计划可以告诉你数据库如何执行你的查询,包括它将如何访问数据库的数据(例如全表扫描还是使用索引)、需要执行哪些操作(例如排序或者连接)等。通过查询计划,你可以发现你的查询的瓶颈,从而找出优化的方向。

索引设计

对于大多数关系型数据库,索引是提高查询性能的关键工具。适当地设计和使用索引可以使得数据库更快地找到你需要的数据。然而,索引并非万能,索引需要占用额外的存储空间,并且会增加数据插入和更新的复杂性。因此,你需要根据你的数据和查询特点来设计合适的索引。

数据库特性

不同的数据库有各自的优点和特性。深入理解你所使用的数据库的特性,可以帮助你找到适合你的优化策略。例如,你可能可以利用数据库的某些特性,如分区表、压缩数据、并行查询等,来提高查询性能
标黄的这个很关键,我们公司的IoT数据车辆位置轨迹信息,业务系统虽然使用MySQL存储,但是也是能满足查询需求的,因为业务上是一般查询一辆车或者一个订单半年内的轨迹数据,查询范围不超过7天,所以我们使用MySQL的分区表特性,将GPS定位数据的以分片Key和GPSTime 进行了分区处理。即使单表上亿条数据,也是可以正常满足业务需求。

业务理解

最后,但也是最重要的一点,理解你的业务需求是最重要的。不管技术上怎么优化,都必须以满足业务需求为目标。理解你的业务需求,包括数据的生命周期、数据的变动频率、查询的复杂性等,这些都是进行数据库优化的基础。

19. Avoid Too Many Joins in a Single Query

一个查询中的JOIN不要过多,建议在5个以内。
我认为这个是经验值,查了一番,我也没有找到SQL标准描述中,对于join的数量要求,大家记住这个是经验值就OK。没有必要深究。
这里有几个建议:

  1. 如果你的查询包括大量的JOIN,可以尝试将一部分JOIN操作放入子查询,或者先创建临时表,这样可以简化查询的复杂性。
  2. 为常用的JOIN字段创建索引,这样数据库可以更快地找到匹配的行。
  3. 优化JOIN的顺序。关系型数据库在处理JOIN查询时,对表顺序的选择可能会影响查询的性能。尤其当你的查询中包含多个JOIN时,或者JOIN的表大小差别很大时,改变JOIN的顺序可能会对性能有显著的影响。

20. Do Regular Performance Tuning

==这句直白点翻译 定期做性能优化,这是一个持续的过程。==我们都应该能理解,数据库的数据时再不断积累累加过程中,MySQL单表的数据性能瓶颈也是存在的。所以当数据累加到一定程度,查询性能是指数型下降,所以定期优化,将数据归档,等等操作。如果需要,可以考虑优化你的数据结构,包括考虑数据的分区、分库、分表等策略,使用适当的数据类型,避免数据冗余等;定期检查并分析慢查询日志,找出执行时间长、资源消耗大的SQL语句,并进行调优;

总结

以上是我关于20条SQL优化定律的解读,在实际应用中需要根据具体需求选择适用的方法。在日常开发中,我们应培养出不断改进SQL语句性能的意识,把它养成一个基本功,出手即大招,写出即最优的境界。还是那句优化有时只需一点点小改变就能带来大幅度的提升

10条DBA经验

  1. 学习并理解你在处理的数据:你要知道数据是哪来的,用在什么地方、怎么使用等等。

  2. 设计好数据库:关于数据库的设计就像盖房子,要先画好图,确定要几个房间、房间怎么分布等等,以免后期麻烦。

  3. 定期备份数据:意外总是会发生,为了应对这些状况,要经常给数据做“身体检查”,如果发现有问题,及时做好“手术”。

  4. 定期优化数据库:为了让数据“跑得更快”,要对其进行定期的优化,比如修剪一下多余的数据分支,给常跑的道路加油脂等等。

  5. 持续关注数据库的动态:为了发现问题并提供及时的“救治”,你得经常盯着数据库的各种情况,比如查看它吃下了多少数据,或者是它正在处理什么数据。

  6. 了解使用的数据库工具:你工作的工具也许有许多有用的功能,学会并使用它们,就像一名木匠要了解他手上的所有工具。

  7. 遵循安全规则:保护数据就像保护你的宝贝,你得把它们放在一个安全的地方,防止不好的人或事情靠近它们。

  8. 不断学习新知识:新的数据技术和新的使用方法会让你的工作变得更容易,所以要保持学习,像海绵一样吸收新的知识。

  9. 记录你干了什么:每次操作数据库后,最好写下你做了什么,好比是写日记。这样当你有需要的时候,可以很容易地查找。

  10. 准备应对突发情况:这个很关键,稍微大一点的公司,都出现过例如自动备份失效,突然要还原等等。总有一些你没想到的问题会出现,所以你需要一个备用计划,比如备用设备,应急文档等等。

猜你喜欢

转载自blog.csdn.net/wangshuai6707/article/details/132571224
今日推荐