我们先从一个神话故事开始本专栏的内容:
在人类的早期,世界上的所有人说着同一种语言,彼此之间沟通毫无障碍。这种统一的语言让人们心生野心,他们决定联合起来建造一座高耸入云的塔,这座塔就是巴别塔。人们希望通过这座塔能够直达天堂,以此展示他们的力量和智慧。
然而,他们的行为引起了上帝的关注。上帝看到人类如此团结,担心他们一旦成功建造巴别塔,将会变得无比强大,甚至可能威胁到神的权威。于是,上帝决定采取行动,他让人们说不同的语言,使他们之间无法沟通。这样一来,原本统一的团队陷入了混乱,巴别塔的建造计划也因此搁置。
巴别塔的故事很有意思,就我个人理解来说,沟通协作其实正是人类的进步的关键。而在沟通中,最重要的是降低他人的理解成本。
我很喜欢Flink官网的这个图,我们技术的发展也是类似,通过一层层封装和抽象,一步步降低使用者的使用门槛,也就是理解成本。
就像我在深入HDFS中提到的一样,我们的技术发展,最底层的思想就是抽象和封装。从古至今,能够风靡世界的技术,都采用了抽象和封装的思想,通过隐藏底层细节,提供简单实现方式,让很多没有相关底层知识的人,也能够快速上手使用。比如大家耳熟能详的JVM,其本质就是通过抽象和封装的思想,提供一套标准化的使用规则,让任何通过实现它这套规则的语言,就能借助它轻松获取跨平台,内存管理,垃圾回收等能力。
而我们本专栏的主角SQL也是类似的。它的设计思路很像领导思维,强调的是要什么结果,即只需要执行查询或数据操作,去获取想要的结果就行,而不需要详细说明要如何执行这些操作。尽管不同的数据库产品之间存在一些差异,但是SQL的基本结构和语法在大多数关系型数据库中都是通用的。这意味着一旦用户掌握了SQL的基本语法和特性,便可以在不同的数据库系统中进行数据查询等操作,而无须重新学习新的语言或工具。除此之外,SQL这种声明式语言支持多种底层实现策略,这意味着在不改变目标的前提下,可以不断地对实现方式进行优化。
正是这种能降低用户理解成本的哲学,结合SQL标准化的定义,使得SQL在大数据体系中得到广泛应用。
但还是那句话——No Silver Bullet。
使用SQL作为统一查询语言的优势我们已经大概梳理了,但它也是有一些局限性的。
表达能力有限:SQL在处理某些复杂业务逻辑时可能不够灵活。例如,对于需要复杂过程计算或顺序运算的场景,SQL可能无法直接表达,需要借助用户自定义函数(UDF)或其他编程语言来实现。
跨平台兼容性问题:尽管SQL是一种标准化语言,但不同数据库系统之间仍存在一些差异。这可能导致在不同系统之间迁移或共享SQL代码时遇到兼容性问题,需要进行额外的调整和优化。
学习成本:虽然SQL本身易于学习,但要编写高效的SQL查询,仍需要深入理解SQL的执行原理和优化技巧。这包括理解基于成本(CBO)和基于规则(RBO)的优化器原理,以及常见的性能瓶颈和调优策略。
依赖底层引擎:SQL的执行效率高度依赖于底层的大数据引擎。如果引擎的优化能力不足,可能会导致SQL查询性能不佳。此外,不同引擎对SQL的支持程度也不同,可能需要针对特定引擎进行优化。
接下来,我们看看SQL的本质。
SQL总结起来,主要包含数据管理、存储和查询三大功能。由于不同大数据引擎的实现差异,执行SQL时需经过复杂转换和优化,以适应分布式计算环境、处理不同数据模型,并确保查询的高性能、可扩展性、兼容性、安全性和交互性。
除大数据引擎在分布式、故障转移、故障恢复、冗余备份等方面的独特物理实现外,当用户通过客户端提交SQL语句时,引擎需考虑以下问题:
如何将输入数据转换为统一、可识别的数据结构?关系型和非关系型数据库均需考虑数据的存储形式及是否为结构化或半结构化数据。转换过程需确保与SQL标准关键字对应,并兼顾跨平台和跨环境的可移植性及兼容性。
如何校验查询语句中的拼写或格式错误?如何验证执行语句的读写权限?能否对查询字段隐式补齐?
引擎如何进行隐式优化?例如,处理 WHERE id= 1 + 1
条件时,相较于直接使用常量 2,若每次执行都在内存中进行加法运算,会产生不必要的计算开销。
而SQL的执行过程,总结起来就是解析(Parsing)、校验(Validation)、优化(Optimization)和执行(Execution)四个主要步骤:
词法分析:将SQL语句分解为由单词和符号组成的词汇单元,例如将SELECT * FROM table
分解为SELECT、*、FROM、table等。
语法分析:检查SQL语句是否符合语法规则。例如,SELECT
必须有FROM
子句,否则语句是不合法的。
授权验证:检查用户是否有权限执行该SQL语句,如是否有权限读取、写入或修改指定的表和数据。
语义校验:确保SQL语句中的表、列等对象存在,并且操作是可行的。例如,检查SELECT
语句中的列名是否存在于表中。
查询优化:为了提高查询效率,优化器会分析查询语句,选择最佳的执行计划。例如,决定是否使用索引、选择合适的连接算法等。
代价评估:基于数据的统计信息(如表的行数、列的分布等)评估不同执行计划的成本,选择成本最低的执行计划。
生成执行计划:优化器生成的执行计划被转换为物理的执行步骤,由执行引擎执行。
数据读取/写入:根据执行计划,从数据存储中读取或写入数据。例如,从磁盘或内存中读取数据,或者将数据写入数据库。
结果输出:执行完成后,将查询结果返回给用户,或者将更新操作的结果反馈给用户。
无论是关系型数据库还是非关系型数据库,只要是基于SQL进行数据的写入和查询,尽管具体的实现机制和执行顺序可能有所不同,但抽象出的核心步骤都是一样的。
对于执行过程中的校验和规则优化,在SQL中,都是基于抽象语法树实现的。
抽象语法树(Abstract Syntax Tree,AST)是一种用于表示程序代码结构的树形数据结构。它是由编译器或解析器在解析源代码时生成的,用于表示源代码的结构。抽象语法树的每个节点代表源代码中的一个构造,如表达式、语句、函数等。通过抽象语法树,可以对程序代码进行分析和修改,例如进行语义分析、语义校验、优化和生成目标代码等。
抽象语法树由节点组成,每个节点代表源代码中的一个构造。节点可以分为以下几种类型:
内部节点:代表程序的构造,如语句、表达式等。例如,在SQL中,SELECT
语句可以作为一个内部节点,其子节点可以是FROM
子句、WHERE
子句等。
叶子节点:代表基本的元素,如标识符、常量、运算符等。例如,在SQL中,表名、列名、数值等都可以作为叶子节点。
抽象语法树的结构反映了源代码的语法结构,可以通过遍历树的节点来分析和处理源代码。
抽象语法树的生成过程通常包括以下几个阶段:
词法分析:将源代码分解为由单词和符号组成的词汇单元,例如将SELECT * FROM table
分解为SELECT、*、FROM、table等。
语法分析:根据目标语言的语法规则,将词汇单元组合成语法单元,例如将SELECT * FROM table
组合成一个SELECT
语句。
生成抽象语法树:根据语法分析的结果,生成抽象语法树。每个语法单元对应树中的一个节点,节点之间的关系反映了源代码的结构。
抽象语法树忽略了解析树包含的一些语法信息,剥离掉一些不重要的细节,它是源代码语法结构的一种抽象表示。AST以树状的形式表现编程语言的结构,可以说高级语言的解析过程都依赖于AST。这棵树会包含很多节点对象,每个节点都拥有特定的数据类型,同时会有0或多个子节点(节点对象在代码中定义为TreeNode对象)。
抽象语法树是对源代码语法结构的高度概括,舍弃了部分非关键信息和细节(压缩单继承节点,操作运算符变为内部节点,去除如冒号等不必要的语法细节)。它以树状结构呈现编程语言体系,由具有特定数据类型和子节点的节点对象组成,在编程语言解析中广泛应用,通用性和可扩展性强,适用于多种语言的解析,包括 SQL 。
可以说,解析成语法树和基于规则的等价变换,是所有SQL优化器的共性和基石。
在了解使用SQL作为统一查询语言的优势和局限性后,我们知道SQL虽然看起来简单易用,但是想要把它用好不是一件容易的事情。同样的统计需求,面对不同的开发思路、不同的数据集、不同的执行引擎等等,所消耗的资源和运行时长都是截然不同的。因此,想要提高任务的稳定性和时效性,就需要对SQL进行调整和优化。
在我个人看来,调优的本质总结起来就是以下两点:
第一点很好理解,以下则梳理了一些常见需要进行SQL调优的原因:
原因 | 详细说明 |
---|---|
资源浪费与效率低下 | 数据处理系统中的硬件资源,如CPU、内存、存储I/O等,都是稀缺且昂贵的。不高效的SQL查询会过度占用这些资源,导致资源浪费。例如,一个未经优化的查询可能需要扫描大量的数据,即使只需要其中的一小部分数据。这种情况下,CPU会花费大量时间处理不必要的数据,存储I/O也会因为频繁的读取操作而变得繁忙。此外,低效的SQL查询可能无法充分利用系统资源,导致整体工作效率低下。 |
数据量增长带来的压力 | 随着数据量的不断增长,未优化的SQL查询的执行时间可能会显著增加。例如,在一个包含大量数据的表中进行全表扫描的查询,当数据量增长到数千万甚至数亿条记录时,执行时间可能会从几秒增加到几分钟甚至更长。定期调优SQL查询,能够确保查询能够适应数据量的增长,并且在合理的时间范围内返回结果。 |
用户体验 | 较长的查询响应时间会严重影响用户体验。在企业级应用中,用户可能需要等待数分钟甚至更长时间才能获得查询结果,这可能会导致用户对系统的满意度下降,甚至影响企业的业务决策效率。此外,在一些实时性要求较高的应用场景,如在线广告投放、金融交易系统等,SQL查询的响应时间可能会直接影响业务的成败。例如,在在线广告投放系统中,如果广告投放决策的查询响应时间过长,可能会导致广告错过最佳的投放时机,从而影响广告的投放效果。 |
其他系统组件的依赖 | 在现代企业级应用中,SQL查询通常与其他系统组件紧密集成。例如,缓存系统、任务调度系统、队列系统等都可能依赖于SQL查询的结果。如果SQL查询性能不佳,可能会导致这些依赖的组件出现性能瓶颈,进而影响整个系统的效率和可靠性。例如,缓存系统可能无法及时获取最新的数据,导致缓存未命中率增加,增加后端数据库的压力。任务调度系统可能会因为无法及时获取任务的状态信息而出现调度延迟,影响业务流程的执行效率。队列系统可能会因为无法及时处理队列中的消息而导致消息堆积,影响业务的实时性。 |
而对于第二点,在实际工作中,作为数据开发的大部分工作内容都是写SQL,正因为如此,很多时候会被戏称为“SQL Boy”之类的。有些人甚至会对自己的工作产生疑问,怀疑自己的工作是否有意义,怀疑自己的发展前景是什么,以及思考怎么才能不做工具人。
正如前面提到的,SQL通过极低的使用门槛带来了很好的便捷性和广泛的受众群体。但也正因此,大多数人对SQL和底层处理引擎是了解不够的。这带来的问题,从不知道怎么调优,到一股脑怼内存加并行,甚至写一些极端SQL搞崩整个系统,对企业的资源消耗,稳定性保障都带来了极大的挑战。
作为数据开发,最核心的工作,就是成为数据和应用中间的桥梁。只有能够对需求或出现的问题有准确判断,了解底层实现的原理,知道哪些地方会是瓶颈,清楚应该如何实现和调优,才能高效而准确的支撑下游的应用场景。
本专栏从SQL调优入手,但不止于SQL,调优是一个涉及很多方面的事项,掌握好调优,才算真的入门数据开发。