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

 

设计:小艾

审核:丁奇

编辑:宇亭

作者:柳湛宇(花名:乌淄)
浙江大学-软件工程-在读硕士、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 exp1 d可以被规约为a exp3

    • a exp3可以被规约:

    • a exp3可以被规约为exp2

    • 「达到序列末尾,」a b c d「成功匹配表达式」exp2

    • a b c可以被规约为a exp1

    • a exp1 d可以被规约:

自顶向下(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匹配exp1

    • 假设b c匹配'a' 'b'

    • 断言错误,排除这一选项

    • 假设b c匹配'b' 'c'

    • 「断言正确且无子表达式,匹配成功,」a b c d「匹配」exp2

    • 应有b c d匹配exp3

    • 假设b c d匹配'c' 'd'

    • 断言错误,排除这一选项

    • 假设b c d匹配exp1 'd'

二者的对比与 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 个:

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

对于如下语法定义:

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

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

MySQL 中的语法冲突

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

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

正如我们从上图中看到的,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 上层语法结构对比一览:

可以看出 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 语法进行扩展和兼容的开发者更加友好。

参考资料


  1. https://www.gnu.org/software/bison/manual/bison.html

  2. https://qntm.org/top

  3. https://github.com/postgres/postgres/blob/47556a0013fa64d44add2760577d49cf2eca4cd0/src/pl/plpgsql/src/pl_gram.y#L4

  4. MySQL Bugs: #2690: bison -y -d sql_yacc.yy && mv y.tab.c sq y.tab.c - No such file or directory

  5. https://github.com/mysql/mysql-server/blob/trunk/sql/sql_yacc.yy

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/19350.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Git】git reset 版本回退 git rm

前言 在日常开发时&#xff0c;我们经常会需要撤销之前的一些修改内容或者回退到之前的某一个版本&#xff0c;这时候reset命令就派上用场了 git reset 用法1——所有文件回退到某个版本 1、使用git reflog查看要回退的commit对象 2、使用git reset [-- hard/soft /mixed] …

算法通关村第二关——反转链表白银笔记

文章目录 1.链表指定区间翻转2.两两交换链表中的节点 1.链表指定区间翻转 LeetCode 92.反转链表 解法一&#xff1a;头插法。利用虚拟节点进行反转&#xff0c;因为头节点有可能发生变化&#xff0c;比如 left1 那么需要 dummyNode.next 记录头结点&#xff0c;使用虚拟头节点…

Arcgis通过模型构建器计算几何坐标

模型 模型中&#xff0c;先添加字段&#xff0c;再计算字段 计算字段 模型的计算字段中&#xff0c;表达式是类似这样写的&#xff0c;其中Xmin表示X坐标&#xff0c;Ymin表示Y坐标 !Shape.extent.Xmin!类似计算面积 !shape.area!

突破游戏行业天花板,“技术外溢”成趋势

文 | 螳螂观察 作者 | 余一 受游戏版号发放的“放缓”、人口结构的调整&#xff0c;过去两年国内游戏行业过得并不算好。前不久据相关机构发布的数据显示&#xff0c;2022年中国游戏市场实际销售收入2658.84亿元&#xff0c;同比减少306.29亿元&#xff0c;下降10.33%。且游戏…

创建个人博客(在文章的列表页,根据文章标题和文章内容实现搜索)

1. 在视图文件增加搜索表单&#xff1a; 在文章列表页的视图文件中&#xff0c;增加一个搜索表单&#xff0c;包含一个文本搜索框和一个提交按钮 <% form_tag articles_path, method: :get do %><% text_field_tag :title, params[:title], placeholder: "搜索…

海康视频插件VideoWebPlugin在vue中的实现

一,将js文件放在public文件下 二,在index中全局引入 三.在视频页面写方法,创建实例,初始化,我写的是1*4屏的 <template><!--视频窗口展示--><div idplayWnd classNameplayWnd refplayWnd styleleft: 0; bottom: 0;height: 902px;width: 60vw></div>&…

Eureka 学习笔记2:EurekaClient

版本 awsVersion ‘1.11.277’ EurekaClient 接口实现了 LookupService 接口&#xff0c;拥有唯一的实现类 DiscoveryClient 类。 LookupService 接口提供以下功能&#xff1a; 获取注册表根据应用名称获取应用根据实例 id 获取实例信息 public interface LookupService<…

HTTP杂谈之Referer和Origin请求头再探

一 关于Referer和Origin的汇总 1) 知识是凌乱的,各位看官看个热闹即可2) 内容不断更新1、理解有盲区,需要及时纠正2、内容交叉有重复,需要适当删减3、扩展视野3) 以下内容都与Referer和Origin请求头有关联 nginx防盗链 HTTP杂谈之Referrer-Policy响应头 iframe标签referre…

物联网|可变参数的使用技巧|不一样的点灯实验|访问外设的寄存器|操作寄存器实现点灯|硬件编程的基本流程-学习笔记(11)

文章目录 可变参数的使用技巧第三阶段-初级实验Lesson5:不一样的点灯实验---学习I/O的输出 ☆点灯的电路图分析1 一起看看点灯的电路图Tip1:另一种点灯的电路Tip1:如何访问外设的寄存器2 STM32F407中操作GPIO的方法 通过直接操作寄存器实现点灯实验Tip1:硬件编程的基本流程 2代…

HTML5+CSS3小实例:带标题的3D多米诺人物卡片

实例:带标题的3D多米诺人物卡片 技术栈:HTML+CSS 效果: 源码: 【html】 <!DOCTYPE html> <html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"><meta name="viewport" content…

【信号去噪】基于马氏距离和EDF统计(IEE-TSP)的基于小波的多元信号去噪方法研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Linux 终端生成二维码

1、安装qrencode [rootnode1 script]# yum -y install qrencode2、输出正常的 [rootnode1 aihuidi]# echo https://blog.csdn.net/weixin_43822878?t1|qrencode -o - -t utf83、输出彩色的 [rootnode1 aihuidi]# qrencode -t utf8 -s 1 https://blog.csdn.net/weixin_4382…

魔法上网端口号被占用通过端口号找到进程并且杀掉进程随笔

Windows11系统由于魔法上网被异常关闭导致再次启动的时候报出端口号被占用问题记录以前忘记的通过端口杀掉进程相关操作。 在命令行&#xff08;winr,输入cmd&#xff09;中输入: netstat -ano 可以看到本机正在使用的ip地址和端口号如图&#xff1a; 在命令行&#xff08;wi…

算法题--找规律(构建乘积数组、剪绳子、圆圈中最后剩下的数字)

目录 找规律 构建乘积数组 原题链接 解析 核心思想 答案 剪绳子 原题链接 解析 核心思想 答案 圆圈中最后剩下的数字 原题链接 解析 核心思想 答案 找规律 需要通过列举多个示例&#xff0c;从多个示例的输入到输出中得到规律去普遍化。 构建乘积数组 给定…

【娱乐圈明星知识图谱2】信息抽取

目录 1. 项目介绍 2. 信息抽取介绍 3. ChatGPT 信息抽取代码实战 4. 信息抽取主逻辑 5. 项目源码 1. 项目介绍 利用爬虫项目中爬取的大量信息 【娱乐圈明星知识图谱1】百科爬虫_Encarta1993的博客-CSDN博客娱乐圈明星知识图谱百度百科爬虫百度百科爬虫百度百科爬虫百度百…

redisson分布式锁学习

什么是分布式锁? 当有多个线程并发访问同一共享数据时,如果多个线程同时都去修改这个共享数据,且修改操作不是原子操作,就很有可能出现线程安全问题&#xff0c;而产生线程安全问题的根本原因是缺乏对共享数据访问的同步和互斥。 为了解决这个问题&#xff0c;通常我们的做法…

【Golang 接口自动化07】struct转map的三种方式

目录 背景 struct转map 使用json模块 使用reflect模块 使用第三方库 测试 总结 资料获取方法 背景 我们在前面介绍过怎么使用net/http发送json或者map数据&#xff0c;那么它能不能直接发送结构体数据呢&#xff1f;我们今天一起来学习结构体struct转map的三种方法&am…

PHM的设备故障模型如何构建?

预测性维护与健康管理&#xff08;Prognostics Health Management&#xff0c;PHM&#xff09;是现代工业中的一个关键概念&#xff0c;它旨在通过使用数据和先进的分析技术&#xff0c;实现设备故障的早期预测和预防&#xff0c;从而最大限度地提高设备的可用性和可靠性。而在…

如何运行疑难解答程序来查找和修复Windows 10中的常见问题

如果Windows 10中出现问题&#xff0c;运行疑难解答可能会有所帮助。疑难解答人员可以为你找到并解决许多常见问题。 一、在控制面板中运行疑难解答 1、打开控制面板&#xff08;图标视图&#xff09;&#xff0c;然后单击“疑难解答”图标。 2、单击“疑难解答”中左上角的…

大牛练成记:用JavaScript徒手写出一个日期选择插件

&#x1f3c6;作者简介&#xff0c;黑夜开发者&#xff0c;全栈领域新星创作者✌&#xff0c;阿里云社区专家博主&#xff0c;2023年6月csdn上海赛道top4。 &#x1f3c6;本文已收录于专栏&#xff1a;100个JavaScript的小应用。 &#x1f389;欢迎 &#x1f44d;点赞✍评论⭐收…