数据库管理提速:SQL解析的探索与应用

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介:

数据库作为核心的基础组件,是需要重点保护的对象。任何一个线上的不慎操作,都有可能给数据库带来严重的故障,从而给业务造成巨大的损失。

为了避免这种损失,一般会在管理上下功夫,比如为研发人员制定数据库开发规范;新上线的SQL,需要DBA进行审核;维护操作需要经过领导审批等等。而且如果希望能够有效地管理这些措施,需要有效的数据库培训,还需要DBA细心的进行SQL审核。很多中小型创业公司可以通过设定规范、进行培训、完善审核流程来管理数据库。

随着美团点评的业务不断发展和壮大,上述措施的实施成本越来越高。如何更多的依赖技术手段,来提高效率,越来越受到重视。业界已有不少基于MySQL源码开发的SQL审核、优化建议等工具,极大的减轻了DBA的SQL审核负担。那么我们能否继续扩展MySQL的源码,来辅助DBA和研发人员来进一步提高效率呢?比如,更全面的SQL优化功能;多维度的慢查询分析;辅助故障分析等。要实现上述功能,其中最核心的技术之一就是SQL解析。

现状与场景

SQL解析是一项复杂的技术,一般都是由数据库厂商来掌握,当然也有公司专门提供SQL解析的API(参考链接:http://sqlparser.com/)。

由于这几年MySQL数据库中间件的兴起,需要支持读写分离、分库分表等功能,就必须从SQL中抽出表名、库名以及相关字段的值。因此像Java语言编写的Druid,C语言编写的MaxScale,Go语言编写的Kingshard等,都会对SQL进行部分解析。而真正把SQL解析技术用于数据库维护的产品较少,主要有如下几个:

d47e62d2b349aca45e42305ed6714efbe5ed61d9美团点评开源的SQLAdvisor。它基于MySQL原生态词法解析,结合分析SQL中的where条件、聚合条件、多表Join关系给出索引优化建议。

参考链接:https://github.com/Meituan-Dianping/SQLAdvisor

d47e62d2b349aca45e42305ed6714efbe5ed61d9去哪儿开源的Inception。侧重于根据内置的规则,对SQL进行审核。

参考链接:http://mysql-inception.github.io/inception-document

d47e62d2b349aca45e42305ed6714efbe5ed61d9阿里的Cloud DBA。根据官方文档介绍,其也是提供SQL优化建议和改写。

参考链接:https://yq.aliyun.com/articles/218442

上述产品都有非常合适的应用场景,在业界也被广泛使用。但是SQL解析的应用场景远远没有被充分发掘,比如:

d47e62d2b349aca45e42305ed6714efbe5ed61d9基于表粒度的慢查询报表。比如,一个Schema中包含了属于不同业务线的数据表,那么从业务线的角度来说,其希望提供表粒度的慢查询报表。
d47e62d2b349aca45e42305ed6714efbe5ed61d9生成SQL特征。将SQL语句中的值替换成问号,方便SQL归类。虽然可以使用正则表达式实现相同的功能,但是其Bug较多,可以参考pt-query-digest。比如pt-query-digest中,会把遇到的数字都替换成“?”,导致无法区别不同数字后缀的表。
d47e62d2b349aca45e42305ed6714efbe5ed61d9高危操作确认与规避。比如,DBA不小心Drop数据表,而此类操作,目前还无有效的工具进行回滚,尤其是大表,其后果将是灾难性的。
d47e62d2b349aca45e42305ed6714efbe5ed61d9SQL合法性判断。为了安全、审计、控制等方面的原因,美团点评不会让研发人员直接操作数据库,而是提供RDS服务。尤其是对于数据变更,需要研发人员的上级主管进行业务上的审批。如果研发人员,写了一条语法错误的SQL,而RDS无法判断该SQL是否合法,就会造成不必要的沟通成本。

因此为了让所有有需要的业务都能方便地使用SQL解析功能,我们认为应该具有如下特性:

d47e62d2b349aca45e42305ed6714efbe5ed61d9直接暴露SQL解析接口,使用尽量简单。比如:输入SQL,则输出表名、特征和优化建议。
d47e62d2b349aca45e42305ed6714efbe5ed61d9接口的使用不依赖于特定的语言,否则维护和使用的代价太高。比如:以HTTP等方式提供服务。

千里之行,始于足下,下面我先介绍下SQL的解析原理。

原理

SQL解析与优化是属于编译器范畴,和C语言等其他语言的解析没有本质的区别。其中分为词法分析、语法和语义分析、优化、执行代码生成。对应到MySQL的部分,如下图:

8d31ca1e56dbad1e6d799dc9909934732eef21ee

SQL解析原理

1、词法分析

SQL解析由词法分析和语法/语义分析两个部分组成。词法分析主要是把输入转化成一个个Token。其中Token中包含Keyword(也称symbol)和非Keyword。例如:SQL语句select username from userinfo,在分析之后,会得到4个Token,其中有2个Keyword,分别为select和from:

a8c5a6e244533db2c81b42101a04a3decffb050e

通常情况下,词法分析可以使用Flex来生成。

参考链接:https://www.gnu.org/software/flex

但是MySQL并未使用该工具,而是手写了词法分析部分(据说是为了效率和灵活性,可参考:https://yq.aliyun.com/articles/71979)。具体代码在sql/lex.h和sql/sql_lex.cc文件中。

MySQL中的Keyword定义在sql/lex.h中,如下为部分Keyword:

{ "&&",               SYM(AND_AND_SYM)},

{ "<",                SYM(LT)},

{ "<=",               SYM(LE)},

{ "<>",               SYM(NE)},

{ "!=",               SYM(NE)},

{ "=",                SYM(EQ)},

{ ">",                SYM(GT_SYM)},

{ ">=",               SYM(GE)},

{ "<<",               SYM(SHIFT_LEFT)},

{ ">>",               SYM(SHIFT_RIGHT)},

{ "<=>",              SYM(EQUAL_SYM)},

{ "ACCESSIBLE",       SYM(ACCESSIBLE_SYM)},

{ "ACTION",           SYM(ACTION)},

{ "ADD",              SYM(ADD)},

{ "AFTER",            SYM(AFTER_SYM)},

{ "AGAINST",          SYM(AGAINST)},

{ "AGGREGATE",        SYM(AGGREGATE_SYM)},

{ "ALL",              SYM(ALL)},

词法分析的核心代码在sql/sql_lex.c文件中的MySQLLex→lex_one_Token,有兴趣的同学可以下载源码研究。

2、语法分析

语法分析就是生成语法树的过程。这是整个解析过程中最精华、最复杂的部分,不过这部分MySQL使用了Bison来完成。即使如此,如何设计合适的数据结构以及相关算法,去存储和遍历所有的信息,也是值得在这里研究的。

语法分析树

SQL语句:

select username, ismale from userinfo where age > 20 and level > 5 and 1 = 1

会生成如下语法树:

a2d45ce5b5fade66fae574c69f14e59377858878

语法树

对于未接触过编译器实现的同学,肯定会好奇如何才能生成这样的语法树,不过其背后的原理都是编译器的范畴,大家可以参考维基百科的一篇文章,以及该链接中的参考书籍。本人也是在学习MySQL源码过程中,阅读了部分内容。

参考链接:https://en.wikipedia.org/wiki/LR_parser

由于编译器涉及的内容过多,本人经历和时间有限,不做过多探究。从工程的角度来说,学会如何使用Bison去构建语法树,来解决实际问题,对我们的工作也许有更大帮助。下面我就以Bison为基础,探讨该过程。

MySQL语法分析树生成过程

全部的源码在sql/sql_yacc.yy中,在MySQL5.6中有17K行左右代码。这里列出涉及到SQL:

select username, ismale from userinfo where age > 20 and level > 5 and 1 = 1

解析过程的部分代码摘录出来。其实有了Bison之后,SQL解析的难度也没有想象的那么大。特别是这里给出了解析的脉络之后。

代码示下:


select /*select语句入口*/:

         select_init

         {

           LEX *lex= Lex;

           lex->sql_command= SQLCOM_SELECT;

         }

       ;

select_init:

         SELECT_SYM /*select 关键字*/ select_init2

       | '(' select_paren ')' union_opt

       ;

select_init2:

         select_part2

         {

           LEX *lex= Lex;

           SELECT_LEX * sel= lex->current_select;

           if (lex->current_select->set_braces(0))

           {

             my_parse_error(ER(ER_SYNTAX_ERROR));

              MYSQL_YYABORT;

           }

            if (sel->linkage == UNION_TYPE &&

               sel->master_unit()->first_select()->braces)

           {

             my_parse_error(ER(ER_SYNTAX_ERROR));

             MYSQL_YYABORT;

           }

         }

         union_clause

        ;

select_part2:

         {

            LEX *lex= Lex;

           SELECT_LEX *sel= lex->current_select;

            if (sel->linkage != UNION_TYPE)

             mysql_init_select(lex);

            lex->current_select->parsing_place= SELECT_LIST;

         }

         select_options select_item_list /*解析列名*/

         {

           Select->parsing_place= NO_MATTER;

         }

         select_into select_lock_type

       ;

select_into:

         opt_order_clause opt_limit_clause {}

       | into

       | select_from /*from 字句*/

       | into select_from

       | select_from into

       ;

select_from:

         FROM join_table_list /*解析表名*/ where_clause /*where字句*/ group_clause having_clause

         opt_order_clause opt_limit_clause procedure_analyse_clause

          {

           Select->context.table_list=

              Select->context.first_name_resolution_table=

                Select->table_list.first;

         }

       | FROM DUAL_SYM where_clause opt_limit_clause

         /* oracle compatibility: oracle always requires FROM clause,

            and DUAL is system table without fields.

            Is "SELECT 1 FROM DUAL" any better than "SELECT 1" ?

         Hmmm :) */

       ;

where_clause:

         /* empty */  { Select->where= 0; }

       | WHERE

         {

           Select->parsing_place= IN_WHERE;

         }

         expr /*各种表达式*/

         {

           SELECT_LEX *select= Select;

           select->where= $3;

           select->parsing_place= NO_MATTER;

           if ($3)

              $3->top_level_item();

         }

        ;

/* all possible expressions */

expr:

          | expr and expr %prec AND_SYM

         {

           /* See comments in rule expr: expr or expr */

           Item_cond_and *item1;

           Item_cond_and *item3;

           if (is_cond_and($1))

           {

             item1= (Item_cond_and*) $1;

             if (is_cond_and($3))

              {

               item3= (Item_cond_and*) $3;

                /*

                 (X1 AND X2) AND (Y1 AND Y2) ==> AND (X1, X2, Y1, Y2)

               */

               item3->add_at_head(item1->argument_list());

                $$ = $3;

             }

              else

             {

               /*

                 (X1 AND X2) AND Y ==> AND (X1, X2, Y)

               */

               item1->add($3);

                $$ = $1;

             }

           }

           else if (is_cond_and($3))

           {

             item3= (Item_cond_and*) $3;

             /*

               X AND (Y1 AND Y2) ==> AND (X, Y1, Y2)

             */

             item3->add_at_head($1);

             $$ = $3;

           }

           else

           {

              /* X AND Y */

             $$ = new (YYTHD->mem_root) Item_cond_and($1, $3);

             if ($$ == NULL)

               MYSQL_YYABORT;

           }

         }

在大家浏览上述代码的过程,会发现Bison中嵌入了C++的代码。通过C++代码,把解析到的信息存储到相关对象中。例如表信息会存储到TABLE_LIST中,order_list存储order by子句里的信息,where字句存储在Item中。有了这些信息,再辅助以相应的算法就可以对SQL进行更进一步的处理了。

核心数据结构及其关系

在SQL解析中,最核心的结构是SELECT_LEX,其定义在sql/sql_lex.h中。下面仅列出与上述例子相关的部分。

c1db03e4e20496a3c25e279eaf773c98ce5358c4

SQL解析树结构

上面图示中,列名username、ismale存储在item_list中,表名存储在table_list中,条件存储在where中。其中以where条件中的Item层次结构最深,表达也较为复杂,如下图所示:

cc687f0fbfe3c6e55cd5a02065735fcd52862b7c

where条件

SQL解析的应用

为了更深入的了解SQL解析器,这里给出2个应用SQL解析的例子:

1、无用条件去除

“无用条件去除”属于优化器的逻辑优化范畴,仅仅根据SQL本身以及表结构即可完成,其优化的情况较多,代码在sql/sql_optimizer.cc文件中的remove_eq_conds函数。为了避免过于繁琐的描述,以及大段代码的粘贴,这里通过图片来分析以下四种情况:

d47e62d2b349aca45e42305ed6714efbe5ed61d91=1 and (m > 3 and n > 4)
d47e62d2b349aca45e42305ed6714efbe5ed61d91=2 and (m > 3 and n > 4)
d47e62d2b349aca45e42305ed6714efbe5ed61d91=1 or (m > 3 and n > 4)
d47e62d2b349aca45e42305ed6714efbe5ed61d91=2 or (m > 3 and n > 4)

无用条件去除a:

4f84e0ce42e5ae5fdd605c5a054cc28ca9475d0d

无用条件去除b 45e029d57db9cb4f82e6d76bcf619eafc740dd15

无用条件去除c

6c495663e85fb4ade8a7397ca9c7abe0fab8cb7f

无用条件去除d

78d44e667f3a1e5d5e2917171d52774c5d974452

如果对其代码实现有兴趣的同学,需要对MySQL中的一个重要数据结构Item类有所了解。因为其比较复杂,所以MySQL官方文档专门介绍了Item类。

参考链接:https://dev.mysql.com/doc/internals/en/item-class.html

阿里的MySQL小组也有类似的文章。如需更详细的了解,就需要去查看源码中sql/item_*等文件。

参考链接:http://www.orczhou.com/index.php/2012/11/mysql-innodb-source-code-optimization-1

2、SQL特征生成

为了确保数据库这一系统基础组件稳定、高效运行,业界有很多辅助系统。比如慢查询系统、中间件系统。这些系统采集、收到SQL之后,需要对SQL进行归类,以便统计信息或者应用相关策略。归类时,通常需要获取SQL特征。比如SQL:

select username, ismale from userinfo where age > 20 and level > 5;

SQL特征为:

select username, ismale from userinfo where age > ? and level > ?

业界著名的慢查询分析工具pt-query-digest,通过正则表达式实现这个功能,但是这类处理办法Bug较多。接下来就介绍如何使用SQL解析,完成SQL特征的生成。

SQL特征生成分两部分组成:

d47e62d2b349aca45e42305ed6714efbe5ed61d9生成Token数组;
d47e62d2b349aca45e42305ed6714efbe5ed61d9根据Token数组,生成SQL特征。

首先回顾在词法解析章节,我们介绍了SQL中的关键字,并且每个关键字都有一个16位的整数对应,而非关键字统一用ident表示,其也对应了一个16位整数。如下表:

172ca95fb9acc9fee1600db0a9b4c37cfc3728b4

将一个SQL转换成特征的过程:

a52dd4e4c3edcc193ee8e65eb28d02b73fa429a6

在SQL解析过程中,可以很方便的完成Token数组的生成。而一旦完成Token数组的生成,就可以很简单的完成SQL特征的生成。SQL特征被广泛用于各个系统中,比如pt-query-digest需要根据特征对SQL归类,然而其基于正则表达式的实现有诸多Bug。下面列举几个已知的Bug:

6418315a6126f0216750926751c00c8daa5eb984

学习建议

最近,在对SQL解析器和优化器探索的过程中,从一开始的茫然无措到有章可循,也总结了一些心得体会,在这里跟大家分享一下:

d47e62d2b349aca45e42305ed6714efbe5ed61d9首先,阅读相关书籍,书籍能给我们一个系统的认识解析器和优化器的角度。但是该类针对MySQL的书籍市面上很少,目前中文作品可以看下《数据库查询优化器的艺术:原理解析与SQL性能优化》;
d47e62d2b349aca45e42305ed6714efbe5ed61d9其次,要阅读源码,但是最好以某个版本为基础,比如MySQL5.6.23,因为SQL解析、优化部分的代码在不断变化,尤其是在跨越大的版本时,改动力度大;
d47e62d2b349aca45e42305ed6714efbe5ed61d9再次,多使用GDB调试,验证自己的猜测,检验阅读质量;

d47e62d2b349aca45e42305ed6714efbe5ed61d9最后,需要写相关代码验证,只有写出来了才能算真正的掌握。


原文发布时间为:2018-06-21

本文作者:广友 金龙 邢帆

本文来自云栖社区合作伙伴“DBAplus社群”,了解相关信息可以关注“DBAplus社群”。

相关文章
|
10天前
|
缓存 Kubernetes Docker
GitLab Runner 全面解析:Kubernetes 环境下的应用
GitLab Runner 是 GitLab CI/CD 的核心组件,负责执行由 `.gitlab-ci.yml` 定义的任务。它支持多种执行方式(如 Shell、Docker、Kubernetes),可在不同环境中运行作业。本文详细介绍了 GitLab Runner 的基本概念、功能特点及使用方法,重点探讨了流水线缓存(以 Python 项目为例)和构建镜像的应用,特别是在 Kubernetes 环境中的配置与优化。通过合理配置缓存和镜像构建,能够显著提升 CI/CD 流水线的效率和可靠性,助力开发团队实现持续集成与交付的目标。
|
1月前
|
机器学习/深度学习 人工智能 自然语言处理
AI技术深度解析:从基础到应用的全面介绍
人工智能(AI)技术的迅猛发展,正在深刻改变着我们的生活和工作方式。从自然语言处理(NLP)到机器学习,从神经网络到大型语言模型(LLM),AI技术的每一次进步都带来了前所未有的机遇和挑战。本文将从背景、历史、业务场景、Python代码示例、流程图以及如何上手等多个方面,对AI技术中的关键组件进行深度解析,为读者呈现一个全面而深入的AI技术世界。
138 10
|
7天前
|
供应链 搜索推荐 API
深度解析1688 API对电商的影响与实战应用
在全球电子商务迅猛发展的背景下,1688作为知名的B2B电商平台,为中小企业提供商品批发、分销、供应链管理等一站式服务,并通过开放的API接口,为开发者和电商企业提供数据资源和功能支持。本文将深入解析1688 API的功能(如商品搜索、详情、订单管理等)、应用场景(如商品展示、搜索优化、交易管理和用户行为分析)、收益分析(如流量增长、销售提升、库存优化和成本降低)及实际案例,帮助电商从业者提升运营效率和商业收益。
73 17
|
5天前
|
SQL Java 数据库连接
如何在 Java 代码中使用 JSqlParser 解析复杂的 SQL 语句?
大家好,我是 V 哥。JSqlParser 是一个用于解析 SQL 语句的 Java 库,可将 SQL 解析为 Java 对象树,支持多种 SQL 类型(如 `SELECT`、`INSERT` 等)。它适用于 SQL 分析、修改、生成和验证等场景。通过 Maven 或 Gradle 安装后,可以方便地在 Java 代码中使用。
93 11
|
30天前
|
SQL Oracle 数据库
使用访问指导(SQL Access Advisor)优化数据库业务负载
本文介绍了Oracle的SQL访问指导(SQL Access Advisor)的应用场景及其使用方法。访问指导通过分析给定的工作负载,提供索引、物化视图和分区等方面的优化建议,帮助DBA提升数据库性能。具体步骤包括创建访问指导任务、创建工作负载、连接工作负载至访问指导、设置任务参数、运行访问指导、查看和应用优化建议。访问指导不仅针对单条SQL语句,还能综合考虑多条SQL语句的优化效果,为DBA提供全面的决策支持。
68 11
|
23天前
|
安全 API 数据安全/隐私保护
速卖通AliExpress商品详情API接口深度解析与实战应用
速卖通(AliExpress)作为全球化电商的重要平台,提供了丰富的商品资源和便捷的购物体验。为了提升用户体验和优化商品管理,速卖通开放了API接口,其中商品详情API尤为关键。本文介绍如何获取API密钥、调用商品详情API接口,并处理API响应数据,帮助开发者和商家高效利用这些工具。通过合理规划API调用策略和确保合法合规使用,开发者可以更好地获取商品信息,优化管理和营销策略。
|
1月前
|
存储 关系型数据库 MySQL
double ,FLOAT还是double(m,n)--深入解析MySQL数据库中双精度浮点数的使用
本文探讨了在MySQL中使用`float`和`double`时指定精度和刻度的影响。对于`float`,指定精度会影响存储大小:0-23位使用4字节单精度存储,24-53位使用8字节双精度存储。而对于`double`,指定精度和刻度对存储空间没有影响,但可以限制数值的输入范围,提高数据的规范性和业务意义。从性能角度看,`float`和`double`的区别不大,但在存储空间和数据输入方面,指定精度和刻度有助于优化和约束。
161 5
|
1月前
|
SQL 关系型数据库 MySQL
MySQL导入.sql文件后数据库乱码问题
本文分析了导入.sql文件后数据库备注出现乱码的原因,包括字符集不匹配、备注内容编码问题及MySQL版本或配置问题,并提供了详细的解决步骤,如检查和统一字符集设置、修改客户端连接方式、检查MySQL配置等,确保导入过程顺利。
|
1月前
|
SQL 监控 安全
SQL Servers审核提高数据库安全性
SQL Server审核是一种追踪和审查SQL Server上所有活动的机制,旨在检测潜在威胁和漏洞,监控服务器设置的更改。审核日志记录安全问题和数据泄露的详细信息,帮助管理员追踪数据库中的特定活动,确保数据安全和合规性。SQL Server审核分为服务器级和数据库级,涵盖登录、配置变更和数据操作等事件。审核工具如EventLog Analyzer提供实时监控和即时告警,帮助快速响应安全事件。
|
1月前
|
机器学习/深度学习 搜索推荐 API
淘宝/天猫按图搜索(拍立淘)API的深度解析与应用实践
在数字化时代,电商行业迅速发展,个性化、便捷性和高效性成为消费者新需求。淘宝/天猫推出的拍立淘API,利用图像识别技术,提供精准的购物搜索体验。本文深入探讨其原理、优势、应用场景及实现方法,助力电商技术和用户体验提升。

热门文章

最新文章

推荐镜像

更多