MySQL 的解析器以及 MySQL8.0 做出的改进 | StoneDB技术分享 #2

image.png

设计:小艾
审核:丁奇
编辑:宇亭

作者:柳湛宇(花名:乌淄)
浙江大学-软件工程-在读硕士、StoneDB 内核研发实习生

一、MySQL 的解析器

MySQL 所使用的解析器(即 Lexer 和 Parser 的组合)是嵌入了 C/C++语言的 yacc/lex 组合,在 linux/GNU 体系上,这一组合的实现是 GNU Bison/Flex,即 Flex 负责生成 tokens, Bison 负责语法解析。

对于 Bison,请参阅[1]

Bison 本是一个自底向上(Bottom-Up)的解析器,但是由于历史原因,MySQL 语法编写的规则是以自顶向下(Top-Down)的,这将会产生一些问题,我们首先简要介绍这两种解析模式。

二、自底向上与自顶向下解析模式

更多详细讲解,请参阅[2]

当我们在谈论自底向上和自顶向下两种解析模式时,局面是我们手上已经有了编写完成的语法规则和将输入语句词法解析完成后的 token 数组,而之后的任务总体上就是构建语法解析树。

以下 yacc 语法约束和匹配序列(「例 1」)用于展示两种解析模式的不同。

exp1:
 'a' 'b' | 'b' 'c';
exp2:
 'x' 'y' 'z' | 'a' exp3;
exp3:
 'c' 'd' | exp1 'd';

a b c d作为输入序列。

自底向上(Bottom-Up)解析模式

自底向上的解析模式类似于进行「拼图」。对每一个入栈后 token 组成的序列,都尽可能尝试将其规约(reduce)成一个语法规则中规定的表达式并将新的表达式压栈。在达到 token 数组末尾时,栈中的表达式应且仅应匹配一个顶层表达式,如果因为规约顺序不符合实际表达式顺序而无法匹配到顶层表达式,则应当进行回溯并尝试新的规约选择。

对于例 1,自底向上解析模式的解析步骤为:

  • a不能被规约(没有可以匹配a的表达式子项)
  • a b可以被规约:
    • exp1 c d被规约为exp1 exp3
    • exp1 exp3无法被规约
    • 达到序列末尾,需要回溯
    • a b规约为exp1
    • exp1 c无法被规约
    • exp1 c d可以被规约:
    • 因此,exp1 c d无法被规约
    • 达到序列末尾,需要回溯
  • 因此,a b无法被规约
  • a b c可以被规约:
    • a b c可以被规约为a exp1
    • a exp1 d可以被规约
    • a exp1 d可以被规约为a exp3
    • a exp3可以被规约:
    • a exp3可以被规约为exp2
    • 达到序列末尾, a b c d成功匹配表达式exp2

自顶向下(Top-Down)解析模式

自顶向下的表达式类似于「多叉树的先序遍历」。对于给定的每一个 token 子序列,都尝试断言(Assertion)其匹配一个表达式,并进一步递归地考察:

1.这个序列是否能通过断言匹配该表达式的子选项;
2.断言匹配子选项后,其对应改规则可归约的子串是否匹配子选项中的表达式。

每当断言失败时,同样进行回溯,来尝试匹配不同的表达式或表达式内不同的子选项,直至构建正确的语法解析树或匹配失败而报错。

对于例 1,自顶向下解析模式的解析步骤为:

  • 假设(此处的原语是断言,Assertion)a b c d匹配exp1的第一个子选项'a' 'b'
  • 断言错误,因此排除这一选项;
  • 同样地,显然可以排除exp1的第二个子选项'b' 'c'exp2的第一个子选项'x' 'y' 'z',此处省略这些步骤;
  • 假设a b c d匹配exp2的第二个子选项'a' exp3
    • 应有b c d匹配exp3
    • 假设b c d匹配'c' 'b'
    • 断言错误,排除这一选项
    • 假设b c d匹配'exp1' 'd'
    • 应有b c匹配exp1
    • 假设b c匹配'a' 'b'
    • 断言错误,排除这一选项
    • 假设b c匹配'b' 'c'
    • 「断言正确且无子表达式,匹配成功,」a b c d「匹配」exp2

二者的对比与 MySQL 面临的问题

可以看到,自底向上解析模式更符合计算机程序的风格,其将规约操作提前,在后半部分执行匹配和回溯动作。但其缺点在于,每一次匹配和回溯的触发点都仅仅在达到 token 数组末尾时进行,因此如果没有优先级约束,每次有效回溯的代价都较大。

自顶向下的解析模式更符合人类阅读和编写语法文件的习惯,其将断言和回溯动作提前,将实际的匹配动作置于解析的后半段。这样的模式缺点在于,它需要回溯的次数更多,同时语法愈发复杂,如果没有合适的断言顺序(实际上对于不同的 SQL 语句,最优的断言顺序也不尽相同),就会有更多冗余的比较分支和更深的有效回溯长度。

由于 MySQL 因历史原因选择了易读的自顶向下的解析模式,其在语法解析时,会产生二义性带来的两种冲突(conflict):移位/规约(shift/reduce)冲突和规约/规约(reduce/reduce)冲突,而使用自底向上解析模式的 posgres[3]则不会产生这两种冲突。

三、移位/规约冲突与规约/规约冲突

两种操作

首先简要介绍自底向上分析方法的移位(shift)和规约(reduce)操作。按自底向上的解析模式,解析器对输入符号串从左到右扫描,读取输入并与语法规则比较,其中:

  • 移位操作是将符号从输入流转入分析栈中的操作。如果当前输入与语法规则匹配,解析器就将当前输入移入(shift)语法栈中,并继续尝试处理下一个符号。简单演示见下例 2:

对于如下语法定义:

simpleStrSeq:
 'a' 'b' 'c' | 'e' 'f' 'g';

处理输入串a b c时,处理前两个 token 时都会将其直接放入语法栈,因为它们匹配simpleStrSeq表达式。

  • 规约操作是将语法栈上的一部分内容替换为相应的非终结符的操作。当解析器发现输入与语法规则的右侧匹配时,它可以执行归约操作,将右侧的符号替换为对应的非终结符。简单演示见下例 3:

对于如下语法定义:

%type<int> num
%%
product:
num '*' num;
plus:
product '*' product;
%%

处理输入串1 * 2 + 3 * 4时,在处理到符号2时,会将语法栈中现有的1 * 2规约(reduce)为product,进一步地,会在处理到4时将3 * 4规约为product,将product + product规约为plus

两种冲突

上述的移位和规约操作是针对自底向上范式提出的,因此使用自顶向下顺序编写语法约束,就会产生移位/规约冲突与规约/规约冲突:

  • 移位/规约冲突:移位/规约冲突指当解析器处理一个符号时,它既可以进行移位(shift)操作,将符号部分或完全匹配一个表达式,同时也可以进行规约(reduce)操作,将当前语法栈内的内容联合输入替换成表达式。简单演示见下例 4:

对于如下语法定义:

%type<int> num
%%
numToken:
 numToken '+' numToken | num;
%%

当处理输入1 + 2 + 3时,处理到符号2时,解析器既可以仅仅将其视作numToken的第二个子选项,移入(shift)语法栈,也可以将其与语法栈中部分内容结合组成1 + 2,匹配成为一个numToken表达式。因此,这个输入合法语法树(指最终结果只有一个顶层表达式)就有 2 个:

image.png

  • 规约/规约冲突:规约/规约冲突是在解析器在遇到一个输入符号时,存在多个可以进行归约操作的情况。这种冲突通常在文法规则中存在二义性或相似的产生式时发生。简单演示见下例 5:

对于如下语法定义:

%type<int> num
%%
numToken:
 numToken '+' numToken |  numToken '*' numToken | num;
%%

当处理输入1 + 2 * 3时,解析器既可以将2其视作numToken的第 1 个子选项的后半部分规约为加法,也可以将其视作numToken第 2 个与子项的前半副本,规约为乘法。因此,这个输入合法语法树(指最终结果只有一个顶层表达式)就有 2 个:

image.png

MySQL 中的语法冲突

我们之前提到,由于历史原因和可读性考虑,MySQL 的 yacc 语法文件采用自顶向下的编写方式,它引入了上述两种语法冲突。产生冲突的原因是,自顶向下的解析方法需要层层进行断言与子表达式的匹配,而在更顶层的子表达式无法在实际上以自底向上执行的 Bison 解析器中直接确定匹配选项。

这意味着语法冲突并不总是意味着语句的二义性而导致解析失败(对于确实需要指定关联性和优先级的操作符,MySQL 也对它们进行了%left%right%nonassoc),事实上 MySQL 的问题是广泛存在的 shift/reduce 冲突引起的断言失败数量增加,进而使得解析时间变长。

image.png

正如我们从上图中看到的,MySQL 各个版本中都有相当数量的 shift/reduce 冲突,但除了图中显示的 MySQL 4.0 中存在的 4 个会导致解析二义性的 reduce/reduce 冲突[4],shift/reduce 冲突不会使得解析器最终得到正确的结果,因此 MySQL8.0 的态度是:

1.We do not accept any reduce/reduce conflicts
2.We should not introduce new shift/reduce conflicts any more.

四、MySQL 8.0 对语法约束的改进

从上图中可以看到,MySQL 8.0 版本降低了语法文件中的 shift/reduce 冲突数量,且随着版本不断更新,目前这一冲突数量已下降到了 63[5](通过语法文件中的%expect语句显式声明)。

MySQL 8.0 做出了很多努力来达到这一成果。其中最关键一点在于对 query 语句整体格式的重构。MySQL 8.0 以前,相同的语法结构(如 create select 和 select 语句都是用的参数列表,select、update 和 delete 语句中都需要使用的 table 列表等)会直接被不同类型的语句直接引用,而没有做额外的约束。

在 MySQL 8.0 中,它同意了所有语句的语法结构,将共用的子结构段进行了进一步的约束和封装,这使得自顶向下的断言可以更快地匹配到对应的语法,同时也能体现于结构上的简洁性。

以下是 MySQL 5.7 到 MySQL 8.0 上层语法结构对比一览:

image.png
image.png

可以看出 MySQL 8.0 使得整体架构更加清晰有序。

同时,8.0 将部分只有一处定义的语法结构展开到上层结构的子选项中,这样的操作以增加边缘功能的代码行数、降低可读性为代价减少了 shift/reduce 冲突。此外,MySQL 8.0 通过显示定义两个伪 token:%left KEYWORD_USED_AS_IDENT%left KEYWORD_USED_AS_KEYWORD来显式地声明对以关键字作为标识符的行为,减少了解析过程中二义性因其的断言失败。

结论

从整体上看,关系数据库系统对于典型的 SQL 语句在语法解析阶段的耗时很短,几乎可以忽略不计,因此 MySQL 维持其自顶向下解析结构以获得语法文件的可读性和可扩展性是可以理解的。我们可以看到 MySQL 8.0 并没有对将语法解析模块更改成类似 Posgres 那样 LALR 的模式以消除语法冲突,而是尽可能地将语法树表达的更加简洁,进而使其对基于 MySQL 语法进行扩展和兼容的开发者更加友好。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,204评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,091评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,548评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,657评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,689评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,554评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,302评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,216评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,661评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,851评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,977评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,697评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,306评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,898评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,019评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,138评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,927评论 2 355

推荐阅读更多精彩内容