当前位置: 首页 > 科技观察

SQL分析在美团点评中是如何应用的?

时间:2023-03-12 02:20:16 科技观察

导读数据库作为核心基础组件,是需要保护的对象。任何不慎的线上操作都可能导致数据库出现严重故障,给业务造成巨大损失。为了避免这种损失,一般需要在管理上下功夫。例如,为研发人员制定数据库开发规范;新上线的SQL需要DBA审核;维护操作需要得到领导的批准等等。而如果你想能够有效地管理这些措施,你需要有效的数据库培训,还需要DBA们认真地进行SQL审计。许多中小型初创公司可以通过制定规范、进行培训和改进审核流程来管理数据库。随着美团点评业务的不断壮大,上述措施的实施成本越来越高。如何更多地依靠技术手段来提高效率,越来越受到关注。业界有很多基于MySQL源码开发的SQL审计、优化建议等工具,大大减轻了DBA的SQL审计负担。那么能否继续扩充MySQL的源码,辅助DBA和研发人员进一步提升效率呢?比如更全面的SQL优化功能;多维慢查询分析;辅助故障分析等。要实现以上功能,核心技术之一就是SQL解析。现状与场景SQL解析是一项复杂的技术,一般数据库厂商都掌握。当然也有提供SQL解析API的公司。由于近几年MySQL数据库中间件的兴起,需要支持读写分离、分库分表等功能,所以需要提取表名、数据库名和SQL中相关字段的值。所以Java语言写的Druid、C语言写的MaxScale、Go语言写的Kingshard等,都会对SQL进行部分解析。但真正使用SQL分析技术进行数据库维护的产品却很少,主要有:美团点评开源的SQLAdvisor。它基于MySQL原生的词法分析,结合SQL中where条件、聚合条件、多表Join关系的分析,给出索引优化建议。去哪儿开源Inception。专注于基于内置规则的SQL审计。阿里云DBA。根据官方文档,它还提供了SQL优化建议和改写。以上产品具有非常适合的应用场景,在行业中应用广泛。然而,SQL解析的应用场景还远未被充分挖掘,例如:基于表粒度的慢查询报表。例如,一个Schema包含属于不同业务线的数据表。从业务线的角度,希望提供表粒度的慢查询报表。生成SQL特征。将SQL语句中的值替换为问号,方便SQL分类。虽然可以用正则表达式来实现同样的功能,但是BUG很多,可以参考pt-query-digest。例如在pt-query-digest中,所有遇到的数字都会被替换为“?”,从而无法区分不同数字后缀的表。高风险操作的确认和规避。比如DBA不小心丢了数据表,目前还没有有效的工具可以回滚这种操作,尤其是大表,后果不堪设想。SQL合法性判断。出于安全、审计、管控等方面的考虑,美团点评不允许研发人员直接操作数据库,而是提供RDS服务。尤其是数据变更,需要研发人员上级主管进行业务审批。如果研发人员写的SQL有语法错误,RDS无法判断SQL是否合法,就会造成不必要的沟通成本。因此,为了让所有需要它的业务能够方便的使用SQL解析功能,我们认为它应该具备以下特点:直接暴露SQL解析接口,尽可能简单的使用。比如输入SQL,输出表名、特征、优化建议等。界面的使用不依赖于特定的语言,否则维护和使用成本太高。例如,通过HTTP等方式提供服务。千里之行,始于足下。先介绍一下SQL的解析原理。原理SQL解析优化属于编译器的范畴,与C等其他语言的解析没有本质区别,分为词法分析、句法语义分析、优化、执行代码生成。MySQL对应的部分如下图所示:图1SQL分析原理词法分析SQL分析包括两部分:词法分析和句法/语义分析。词法分析主要是将输入转化为记号。Token包含Keyword(也称symbol)和non-Keyword。比如SQL语句selectusernamefromuserinfo,经过分析,会得到4个Token,包括2个Keywords,分别是select和from:正常情况下,词法分析可以用Flex生成,但是MySQL没有用这个工具,而是手写的词法分析部分(据说是为了效率和灵活性,参考这篇文章)。具体代码在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,感兴趣的同学可以下载源码研究。解析解析是生成语法树的过程。这是整个解析过程中最本质也是最复杂的部分,但这部分是由MySQL使用Bison来完成的。即便如此,如何设计合适的数据结构和相关算法来存储和遍历所有信息,还是值得在这里研究的。a)语法分析树SQL语句:selectusername,ismalfromuserinfowhereage>20andlevel>5and1=1会生成如下语法树:图2语法树对于没有接触过编译器实现的同学,肯定会好奇如何生成这样的语法树语法树。其背后的原理都在编译器的范围内。您可以参考维基百科上的一篇文章和此链接中的参考书。我在学习MySQL源码的过程中也看了部分内容。由于编译涉及的内容太多,本人经验和时间有限,就不做过多研究了。从工程的角度来说,学习如何使用Bison构建语法树来解决实际问题,可能对我们的工作更有帮助。下面我将讨论基于Bison的过程。b)MySQL语法分析树生成过程的全部源码在sql/sql_yacc.yy中,MySQL5.6中约有17K行代码。下面摘录SQL解析过程中涉及的部分代码:selectusername,ismalfromuserinfowhereage>20andlevel>5and1=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_optionsselect_item_list/*解析列表名*/{Select->parsing_place=NO_MATTER;}select_intoselect_lock_type;select_into:opt_order_clauseopt_limit_clause{}|into|select_from/*from句子*/|intoselect_from|select_frominto;select_from:FROMjoin_table_list/*解析表名*/where_clause/*where句子*/group_clausehaving_clauseopt_order_clauseopt_limit_clauseprocedure_analyse_clause{Select->context.table_list=Select->context.first_name_resolution_table=Select->table_list.first;}|FROMDUAL_SYMwhere_clauseopt_limit_clause/*oraclecompatibility:oraclealwaysrequiresFROMclause,和DUAL是没有字段的系统表。是“SELECT1FROMDUAL”比“SELECT1”更好吗*/hmmm_where:):/*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();};/*所有可能的表达式*/expr:|exprandexpr%precAND_SYM{/*参见ruleexpr:exprorexpr*/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;/*(X1ANDX2)AND(Y1ANDY2)==>AND(X1,X2,Y1,Y2)*/item3->add_at_head(item1->argument_list());$$=$3;}else{/*(X1ANDX2)ANDY==>AND(X1,X2,Y)*/item1->add($3);$$=$1;}}elseif(is_cond_and($3)){item3=(Item_cond_and*)$3;/*XAND(Y1ANDY2)==>AND(X,Y1,Y2)*/item3->add_at_head($1);$$=$3;}else{/*XANDY*/$$=new(YYTHD->mem_root)Item_cond_and($1,$3);if($$==NULL)MYSQL_YYABORT;}}当你浏览上面的代码时,你会发现C++嵌入在Bison代码通过C++代码将解析后的信息存储到相关对象中。比如表信息会存放在TABLE_LIST中,order_list会存放信息在orderby子句中,where子句存放在Item中。有了这些信息,就可以借助相应的算法对SQL做进一步的处理。c)核心数据结构及其关系在SQL解析中,核心结构是SELECT_LEX,定义在sql/sql_lex.h中。下面仅列出与上述示例相关的部分。图3SQL解析树结构上图中,item_list中存储了列名username和ismale,table_list中存储了表名,where中存储了条件。其中where条件中的item层级最深,表达式也比较复杂,如下图所示:图4SQL分析在where条件中的应用为了更深入的理解SQL解析器,这里是应用SQL分析的两个示例。去除无用条件去除无用条件属于优化器的逻辑优化范畴。只能根据SQL本身和表结构来完成。有很多优化案例。代码在sql/sql_optimizer.cc文件的remove_eq_conds函数中。为了避免过于繁琐的描述和大段代码的粘贴,通过图分析以下四种情况:a)1=1and(m>3andn>4)b)1=2and(m>3andn>4)c)1=1or(m>3andn>4)d)1=2or(m>3andn>4)图5去除无用条件a图6去除无用条件b图7去除无用条件Removec图8去除无用条件d如果对其代码实现感兴趣,需要了解MySQL中一个重要的数据结构Item类。由于其复杂性,MySQL官方文档专门介绍了Item类。阿里的MySQL团队也有类似的文章。更详细的理解需要查看源码中的sql/item_*等文件。SQL特征生成为了保证系统基础组件数据库的稳定高效运行,业界有很多辅助系统。比如慢查询系统,中间件系统。这些系统在收集和接收SQL后,需要对SQL进行分类,以便收集统计信息或应用相关策略。分类时,往往需要获取SQL特征。例如SQL:selectusername,ismalfromuserinfowhereage>20andlevel>5;SQL功能:selectusername,ismalfromuserinfowhereage>?andlevel>?业界知名的慢查询分析工具pt-query-digest就是通过正则表达式来实现这个功能的,但是这种处理方式比较有bug。许多。下面介绍如何使用SQL解析完成SQL特征的生成。SQL特征生成由两部分组成。a)生成Token数组b)基于Token数组生成SQL特征。先回顾一下词法分析章节,我们在SQL中引入了关键字,每个关键字都有一个16位的ident,也就是对应一个16位的整数。下表:SQL转换为特征的过程:在SQL解析过程中,可以轻松完成Token数组的生成。Token数组生成完成后,就可以轻松完成SQL特征的生成。SQL特性广泛应用于各种系统中。比如pt-query-digest需要将SQL按照特性进行分类。但是,它基于正则表达式的实现有很多错误。下面是几个已知的bug:学习建议最近在摸索SQL解析器和优化器的过程中,从一开始的无所适从,到有章可循,也总结了一些心得体会,在这里和大家分享一下时间。首先,阅读相关书籍,可以让我们对解析器和优化器有一个系统的认识。但是市面上关于MySQL的这类书籍很少。目前的中文作品,你可以阅读《数据库查询优化器的艺术:原理解析与SQL性能优化》。其次,需要阅读源码,但是最好以某个版本为基础,比如MySQL5.6.23,因为SQL解析优化的代码是不断变化的。尤其是跨大版本的时候,变化幅度很大。再次,多用GDB调试,验证自己的猜测,检查阅读质量。***,需要自己写相关代码进行验证,只有写出来才能真正掌握。