极客时间已完结课程限时免费阅读

14 | 前端技术应用(一):如何透明地支持数据库分库分表?

14 | 前端技术应用(一):如何透明地支持数据库分库分表?-极客时间

14 | 前端技术应用(一):如何透明地支持数据库分库分表?

讲述:宫文学

时长15:29大小14.17M

从今天开始,我们正式进入了应用篇,我会用两节课的时间,带你应用编译器的前端技术。这样,你会把学到的编译技术和应用领域更好地结合起来,学以致用,让技术发挥应有的价值。还能通过实践加深对原理的理解,形成一个良好的循环。
这节课,我们主要讨论,一个分布式数据库领域的需求。我会带你设计一个中间层,让应用逻辑不必关心数据库的物理分布。这样,无论把数据库拆成多少个分库,编程时都会像面对一个物理库似的没什么区别。
接下来,我们先了解一下分布式数据库的需求和带来的挑战。

分布式数据库解决了什么问题,又带来了哪些挑战

随着技术的进步,我们编写的应用所采集、处理的数据越来越多,处理的访问请求也越来越多。而单一数据库服务器的处理能力是有限的,当数据量和访问量超过一定级别以后,就要开始做分库分表的操作。比如,把一个数据库的大表拆成几张表,把一个库拆成几个库,把读和写的操作分离开等等。我们把这类技术统称为分布式数据库技术。
分库分表(Sharding)有时也翻译成“数据库分片”。分片可以依据各种不同的策略,比如我开发过一个与社区有关的应用系统,这个系统的很多业务逻辑都是围绕小区展开的。对于这样的系统,按照地理分布的维度来分片就很合适,因为每次对数据库的操作基本上只会涉及其中一个分库。
假设我们有一个订单表,那么就可以依据一定的规则对订单或客户进行编号,编号中就包含地理编码。比如 SDYT 代表山东烟台,BJHD 代表北京海淀,不同区域的数据放在不同的分库中:
通过数据库分片,我们可以提高数据库服务的性能和可伸缩性。当数据量和访问量增加时,增加数据库节点的数量就行了。不过,虽然数据库的分片带来了性能和伸缩性的好处,但它也带来了一些挑战。
最明显的一个挑战,是数据库分片逻辑侵入到业务逻辑中。过去,应用逻辑只访问一个数据库,现在需要根据分片的规则,判断要去访问哪个数据库,再去跟这个数据库服务器连接。如果增加数据库分片,或者对分片策略进行调整,访问数据库的所有应用模块都要修改。这会让软件的维护变得更复杂,显然也不太符合软件工程中模块低耦合、把变化隔离的理念。
所以如果有一种技术,能让我们访问很多数据库分片时,像访问一个数据库那样就好了。数据库的物理分布,对应用是透明的。
可是,“理想很吸引人,现实很骨感”。要实现这个技术,需要解决很多问题:
首先是跨库查询的难题。如果 SQL 操作都针对一个库还好,但如果某个业务需求恰好要跨多个库,比如上面的例子中,如果要查询多个小区的住户信息,那么就要在多个库中都执行查询,然后把查询结果合并,一般还要排序。
如果我们前端显示的时候需要分页,每页显示一百行,那就更麻烦了。我们不可能从 10 个分库中各查出 10 行,合并成 100 行,这 100 行不一定排在前面,最差的情况,可能这 100 行恰好都在其中一个分库里。所以,你可能要从每个分库查出 100 行来,合并、排序后,再取出前 100 行。如果涉及数据库表跨库做连接,你想象一下,那就更麻烦了。
其次就是跨库做写入的难题。如果对数据库写入时遇到了跨库的情况,那么就必须实现分布式事务。所以,虽然分布式数据库的愿景很吸引人,但我们必须解决一系列技术问题。
这一讲,我们先解决最简单的问题,也就是当每次数据操作仅针对一个分库的时候,能否自动确定是哪个分库的问题。解决这个问题我们不需要依据别的信息,只需要提供 SQL 就行了。这就涉及对 SQL 语句的解析了,自然要用到编译技术。

解析 SQL 语句,判断访问哪个数据库

我画了一张简化版的示意图:假设有两张表,分别是订单表和客户表,它们的主键是 order_id 和 cust_id:
我们采用的分片策略,是依据这两个主键的前 4 位的编码来确定数据库分片的逻辑,比如:前四位是 SDYT,那就使用山东烟台的分片,如果是 BJHD,就使用北京海淀的分片,等等。
在我们的应用中,会对订单表进行一些增删改查的操作,比如会执行下面的 SQL 语句:
//查询
select * from orders where order_id = 'SDYT20190805XXXX'
select * from orders where cust_id = 'SDYT987645'
//插入
insert into orders (order_id,...其他字段) values( "BJHD20190805XXXX",...)
//修改
update orders set price=298.00 where order_id='FJXM20190805XXXX'
//删除
delete from orders where order_id='SZLG20190805XXXX'
我们要能够解析这样的 SQL 语句,根据主键字段的值,决定去访问哪个分库或者分表。这就需要用到编译器前端技术,包括词法分析、语法分析和语义分析。
听到这儿,你可能会质疑:“解析 SQL 语句?是在开玩笑吗?”你可能觉得这个任务太棘手,犹豫着是否要忍受业务逻辑和技术逻辑混杂的缺陷,把判断分片的逻辑写到应用代码里,或者想解决这个问题,又或者想自己写一个开源项目,帮到更多的人。
无论你的内心活动如何,应用编译技术,能让你有更强的信心解决这个问题。那么如何去做呢?要想完成解析 SQL 的任务,在词法分析和语法分析这两个阶段,我建议你采用工具快速落地,比如 Antlr。你要找一个现成的 SQL 语句的语法规则文件。
GitHub 中,那个收集了很多示例 Antlr 规则文件的项目里,有两个可以参考的规则:一个是PLSQL的(它是 Oracle 数据库的 SQL 语法);一个是SQLite的(这是一个嵌入式数据库)。
实际上,我还找到 MySQL workbench 所使用的一个产品级的规则文件。MySQL workbench 是一个图形化工具,用于管理和访问 MySQL。这个规则文件还是很靠谱的,不过它里面嵌了很多属性计算规则,而且是 C++ 语言写的,我嫌处理起来麻烦,就先弃之不用,暂且采用 SQLite 的规则文件来做示范。
先来看一下这个文件里的一些规则,例如 select 语句相关的语法:
factored_select_stmt
: ( K_WITH K_RECURSIVE? common_table_expression ( ',' common_table_expression )* )?
select_core ( compound_operator select_core )*
( K_ORDER K_BY ordering_term ( ',' ordering_term )* )?
( K_LIMIT expr ( ( K_OFFSET | ',' ) expr )? )?
;
common_table_expression
: table_name ( '(' column_name ( ',' column_name )* ')' )? K_AS '(' select_stmt ')'
;
select_core
: K_SELECT ( K_DISTINCT | K_ALL )? result_column ( ',' result_column )*
( K_FROM ( table_or_subquery ( ',' table_or_subquery )* | join_clause ) )?
( K_WHERE expr )?
( K_GROUP K_BY expr ( ',' expr )* ( K_HAVING expr )? )?
| K_VALUES '(' expr ( ',' expr )* ')' ( ',' '(' expr ( ',' expr )* ')' )*
;
result_column
: '*'
| table_name '.' '*'
| expr ( K_AS? column_alias )?
;
我们可以一边看这个语法规则,一边想几个 select 语句做一做验证。你可以思考一下,这个规则是怎么把 select 语句拆成不同的部分的。
SQL 里面也有表达式,我们研究一下它的表达式的规则:
expr
: literal_value
| BIND_PARAMETER
| ( ( database_name '.' )? table_name '.' )? column_name
| unary_operator expr
| expr '||' expr
| expr ( '*' | '/' | '%' ) expr
| expr ( '+' | '-' ) expr
| expr ( '<<' | '>>' | '&' | '|' ) expr
| expr ( '<' | '<=' | '>' | '>=' ) expr
| expr ( '=' | '==' | '!=' | '<>' | K_IS | K_IS K_NOT | K_IN | K_LIKE | K_GLOB | K_MATCH | K_REGEXP ) expr
| expr K_AND expr
| expr K_OR expr
| function_name '(' ( K_DISTINCT? expr ( ',' expr )* | '*' )? ')'
| '(' expr ')'
| K_CAST '(' expr K_AS type_name ')'
| expr K_COLLATE collation_name
| expr K_NOT? ( K_LIKE | K_GLOB | K_REGEXP | K_MATCH ) expr ( K_ESCAPE expr )?
| expr ( K_ISNULL | K_NOTNULL | K_NOT K_NULL )
| expr K_IS K_NOT? expr
| expr K_NOT? K_BETWEEN expr K_AND expr
| expr K_NOT? K_IN ( '(' ( select_stmt
| expr ( ',' expr )*
)?
')'
| ( database_name '.' )? table_name )
| ( ( K_NOT )? K_EXISTS )? '(' select_stmt ')'
| K_CASE expr? ( K_WHEN expr K_THEN expr )+ ( K_ELSE expr )? K_END
| raise_function
;
你可能会觉得 SQL 的表达式的规则跟其他语言的表达式规则很像。比如都支持加减乘除、关系比较、逻辑运算等等。而且从这个规则文件里,你一下子就能看出各种运算的优先级,比如你会注意到,字符串连接操作“||”比乘法和除法的优先级更高。所以,研究一门语言时积累的经验,在研究下一门语言时仍然有用。
有了规则文件之后,接下来,我们用 Antlr 生成词法分析器和语法分析器:
antlr -visitor -package dsql.parser SQLite.g4
在这个命令里,我用 -package 参数指定了生成的 Java 代码的包是 dsql.parser。dsql 是分布式 SQL 的意思。接着,我们可以写一点儿程序测试一下所生成的词法分析器和语法分析器:
String sql = "select order_id from orders where cust_id = 'SDYT987645'";
//词法分析
SQLiteLexer lexer = new SQLiteLexer(CharStreams.fromString(sql));
CommonTokenStream tokens = new CommonTokenStream(lexer);
//语法分析
SQLiteParser parser = new SQLiteParser(tokens);
ParseTree tree = parser.sql_stmt();
//输出lisp格式的AST
System.out.println(tree.toStringTree(parser));
这段程序的输出是 LISP 格式的 AST,我调整了一下缩进,让它显得更像一棵树:
(sql_stmt
(factored_select_stmt
(select_core select
(result_column
(expr
(column_name
(any_name order_id))))
from (table_or_subquery
(table_name
(any_name orders)))
where (expr
(expr
(column_name
(any_name cust_id)))
=
(expr
(literal_value
('SDYT987645'))))))
从 AST 中,我们可以清晰地看出这个 select 语句是如何被解析成结构化数据的,再继续写点儿代码,就能获得想要的信息了。
接下来的任务是:对于访问订单表的 select 语句,要在 where 子句里找出 cust_id="客户编号"或 order_id="订单编号"这样的条件,从而能够根据客户编号或订单编号确定采用哪个分库。
怎么实现呢?很简单,我们用 visitor 模式遍历一下 AST 就可以了:
public String getDBName(String sql) {
//词法解析
SQLiteLexer lexer = new SQLiteLexer(CharStreams.fromString(sql));
CommonTokenStream tokens = new CommonTokenStream(lexer);
//语法解析
SQLiteParser parser = new SQLiteParser(tokens);
ParseTree tree = parser.sql_stmt();
//以lisp格式打印AST
System.out.println(tree.toStringTree(parser));
//获得select语句的要素,包括表名和where条件
SQLVisitor visitor = new SQLVisitor();
SelectStmt select = (SelectStmt) visitor.visit(tree);
String dbName = null;
if (select.tableName.equals("orders")) {
if (select.whereExprs != null) {
for (WhereExpr expr : select.whereExprs) {
//根据cust_id或order_id来确定库的名称
if (expr.columnName.equals("cust_id") || expr.columnName.equals("order_id")) {
//取编号的前4位,即区域编码
String region = expr.value.substring(1, 5);
//根据区域编码,获取库名称
dbName = region2DB.get(region);
break;
}
}
}
}
return dbName;
}
获取表名和 where 子句条件的代码在 SQLVisitor.java 中。因为已经有了 AST,抽取这些信息是不难的。你可以点开我在文稿中提供的链接,查看示例代码。

我们的示例离实用还有多大差距?

目前,我们已经初步解决了数据库访问透明化的问题。当然,这只是一个示例,如果要做得严密、实用,我们还要补充一些工作。
我们需要做一些语义分析工作,确保 SQL 语句的合法性。语法分析并不能保证程序代码完全合法,我们必须进行很多语义的检查才行。
我给订单表起的名字,是 orders。如果你把表名称改为 order,那么必须用引号引起来,写成’order’,不带引号的 order 会被认为是一个关键字。因为在 SQL 中我们可以使用 order by 这样的子句,这时候,order 这个表名就会被混淆,进而被解析错误。这个语法解析程序会在表名的地方出现一个 order 节点,这在语义上是不合法的,需要被检查出来并报错。
如果要检查语义的正确性,我们还必须了解数据库的元数据。否则,就没有办法判断在SQL 语句中是否使用了正确的字段,以及正确的数据类型。除此之外,我们还需要扩展到能够识别跨库操作,比如下面这样一个 where 条件:
order_id = 'FJXM20190805XXXX' or order_id = 'SZLG20190805XXXX'
分析这个查询条件,可以知道数据是存在两个不同的数据库中的。但是我们要让解析程序分析出这个结果,甚至让它针对更加复杂的条件,也能分析出来。这就需要更加深入的语义分析功能了。
最后,解析器的速度也是一个需要考虑的因素。因为执行每个 SQL 都需要做一次解析,而这个时间就加在了每一次数据库访问上。所以,SQL 解析的时间越少越好。因此,有的项目就会尽量提升解析效率。阿里有一个开源项目 Druid,是一个数据库连接池。这个项目强调性能,因此他们纯手写了一个 SQL 解析器,尽可能地提升性能。
总之,要实现一个完善的工具,让工具达到产品级的质量,有不少工作要做。如果要支持更强的分布式数据库功能,还要做更多的工作。不过,你应该不会觉得这事儿有多么难办了吧?至少在编译技术这部分你是有信心的。
在这里,我还想讲一讲 SQL 防注入这个问题。SQL 注入攻击是一种常见的攻击手段。你向服务器请求一个 url 的时候,可以把恶意的 SQL 嵌入到参数里面,这样形成的 SQL 就是不安全的。
以前面的 SQL 语句为例,这个 select 语句本来只是查询一个订单,订单编号“SDYT20190805XXXX”作为参数传递给服务端的一个接口,服务端收到参数以后,用单引号把这个参数引起来,并加上其他部分,就组装成下面的 SQL 并执行:
//原来的SQL
select * from orders where order_id = 'SDYT20190805XXXX'
如果我们遇到了一个恶意攻击者,他可能把参数写成“SDYT20190805XXXX’;drop table customers; --”。服务器接到这个参数以后,仍然把它拿单引号引起来,并组装成 SQL,组装完毕以后就是下面的语句:
//被注入恶意SQL
select * from orders where order_id = 'SDYT20190805XXXX'; drop table customers; --'
如果你看不清楚,我分行写一下,这样你就知道它是怎么把你宝贵的客户信息全都删掉的:
//被注入恶意SQL
select * from orders where order_id = 'SDYT20190805XXXX';
drop table customers; // 把顾客表给删了
--' //把你加的单引号变成了注释,这样SQL不会出错
所以 SQL 注入有很大的危害。而我们一般用检查客户端传过来的参数的方法,看看有没有 SQL 语句中的关键字,来防止 SQL 注入。不过这是比较浅的防御,有时还会漏过一些非法参数,所以要在 SQL 执行之前,做最后一遍检查。而这个时候,就要运用编译器前端技术来做 SQL 的解析了。借此,我们能检查出来异常:明明这个功能是做查询的,为什么形成的 SQL 会有删除表的操作?
通过这个例子,我们又分析了一种场景:开发一个安全可靠的系统,用编译技术做 SQL 分析是必须做的一件事情。

课程小结

今天,我带你利用学到的编译器前端技术,解析了 SQL 语句,并针对分布式数据库透明查询的功能做了一次概念证明。
SQL 是程序员经常打交道的语言。有时,我们会遇到需要解析 SQL 语言的需求,除了分布式数据库场景的需求以外,Hibernate 对 HQL 的解析,也跟解析 SQL 差不多。而且,最近有一种技术,能够通过 RESTful 这样的接口做通用的查询,其实也是一种类 SQL 的子语言。
当然了,今天我们只是基于工具做解析。一方面,有时候我们就是需要做个原型系统或者最小的能用的系统,有时间有资源了,再追求完美也不为过,比如追求编译速度的提升。另一方面,你能看到 MySQL workbench 也是用 Antlr 来作帮手的,在很多情况下,Antlr 这样的工具生成的解析器足够用,甚至比你手写的还要好,所以,我们大可以节省时间,用工具做解析。
可能你会觉得,实际应用的难度似乎要低于学习原理的难度。如果你有这个感觉,那就对了,这说明你已经掌握了原理篇的内容,所以日常的一些应用根本不是问题,你可以找出更多的应用场景来练练手。

一课一思

你在工作中,是否遇到过其他需要解析 SQL 的场景?另外,当你阅读了 SQL 的规则文件之后,是否发现了它跟 Java 这样的语言规则的不同之处?是更加简单还是更复杂?欢迎在留言区写下你的发现。
最后,感谢你的阅读,如果这篇文章让你有所收获,也欢迎你将它分享给更多的朋友。
分享给需要的人,Ta购买本课程,你将得18
生成海报并分享

赞 7

提建议

上一篇
13 | 继承和多态:面向对象运行期的动态特性
下一篇
15 | 前端技术应用(二):如何设计一个报表工具?
unpreview
 写留言

精选留言(17)

  • xiaoma2008
    2019-11-14
    如果老师对分布式数据库比较熟的话,希望老师能出个分布式查询的课程!

    作者回复: 我最近其实在构思写一个分布式数据访问的工具。跟MyCat的定位会不大相同,是想整体上把数据访问这件事情,对应用透明化。野心有点大。 因为现在很多用到数据库的应用,其水平扩展能力都困在了数据库这个点上。这个问题不解决,所谓云原生就是在忽视房间里的大象。 我看资料,已经有一些分布式数据库的商业产品。但要像使用一个单一的数据库一样的使用它,还是有点困难。 我整理一下这方面的思路,会跟大家分享的。

    共 2 条评论
    15
  • David
    2019-10-01
    关于编译技术还有什么经典的应用场景吗?老师帮我们拓展一下想象力。

    作者回复: 每个领域都会有这样的场景。 无论是上层的应用软件(如工资表软件),还是下层的基础软件(如数据库软件)。只要想让软件的功能有一定灵活性的、通用性的,都需要编译技术。 曾经在Lisp圈里(参见《黑客与画家》),有人说,每个软件做到极致的时候,里面都会包含一个拙劣的Lisp实现。他的意思是,每个软件做到极致,都要有灵活定制的能力,也就是需要编译技术的支持。 具体来说,每个领域不一样,所以举例子也只能举自己熟悉领域的例子。 我熟悉的领域: 1.企业软件:比如ERP软件等。定制能力是否强大,就是优秀的企业软件和平庸的企业软件的区别。现在的企业软件在实施的时候,成本往往很高,就是这方面比较差。我目前手头在做的一门语言,就是在解决这个领域的问题。 2.中间件/基础软件:这里肯定需要。例子太多了。 3.GIS,也算一种基础软件吧,也有编程能力。 其他同学,有的熟悉游戏领域,有的在AI领域,各自都有不同的场景。

    6
  • sugar
    2020-04-17
    我来说说工作中sql解析的场景吧:一般后端同学都有自己雪藏的一大坨复杂的sql祖传代码,作为写新sql参考也好、拿来给带的实习生做演示也好,读起来肯定是带换行带缩进才比较舒服;而到了执行的时候,linux各种终端 ssh隧道之间复制粘贴,肯定最希望的还是去掉一切非必要的空格、换行符以及缩进制表符这样的1-line-sql,这个诉求我个人是长期存在的,目前为止在大多数ide里我没发现100%解决这个需求场景的sql formatter,况且绝大多数ide中sql格式化的插件都有各种小毛病,用起来让人不爽,自己写一个 想怎么hack怎么hack。这算是个不错的自己写sql parser的场景吧~
    展开
    5
  • janey
    2019-09-17
    在编译器层面实现分布式数据查询,可以理解为把多表查询提前了吗?只是看起来就一个select语句

    作者回复: 你说的多表,是指同一个表在不同服务器上的分片吗? 如果一个SQL是涉及多个分片的,解析了SQL以后,是可以针对每个分片重新生成SQL的。但这个需要对语义更深的理解,要把SQL语句变成关系代数的运算(选择、投影、笛卡尔积、Union等),这样就知道如何正确的重组成多个SQL了。

    3
  • coconut
    2021-01-07
    老师,想要满足 高性能地解析SQL到语法树 这个需求,我尝试用antlr和yacc golang写了demo测试,antlr差不多性能降了300+倍,goyacc下降了10倍左右。 如果还想提高性能,还有什么方式么?我想到的 1.调研sql parser,如druid 2.尝试其他语言 (估计不会有啥提升)
    展开

    作者回复: 你能动手测量实际的性能,非常好!在有些应用场景中,SQL解析的性能确实很重要,因为它会叠加到每次服务请求的时间上,影响到系统响应的速度。 在《编译原理实战课》中,专门有讲MySQL的编译器的内容。你可以参考MySQL编译器的实现方法,简单概括一下它的特点: 1.词法分析器是手写的,这样会提供一定的灵活性。 2.语法分析器是用bison(yacc)实现的,你可以参考它的语法定义文件,看看跟自己做的定义文件的区别。mysql的例子证明,哪怕是生产级的产品,用工具来做语法分析仍然是可行的。 另外,在分析V8等编译器的时候,也提到了一些解析速度优化的方法,因为javascript需要解析后才能执行,解析速度慢就影响页面体验,所以采用了词法解析查字典等技术,把性能压榨到极致。

    2
  • Richie
    2019-09-22
    老师能不能多讲点,关于SQL解析的内容,更深,更细

    作者回复: SQL的部分展开确实挺多的。 要不然跟极客时间合作一本书,收集大家的需求,把一些细节更多的展开? 或者后面做课程升级的时候,考虑扩展一下这部分。

    2
  • 书忆江南
    2020-07-18
    SparkSQL也是用Antlr4来进行SQL语法的解析的
    1
  • reverse
    2020-03-27
    注意;SQLite 的地址是 :https://github.com/antlr/grammars-v4/blob/master/sql/sqlite/SQLite.g4

    作者回复: 感谢分享!

    1
  • 农村合作社
    2022-11-16 来自广东
    好像没有代码链接,我补一下: https://github.com/RichardGong/PlayWithCompiler/blob/master/lab/db/src/main/playdb/SQLVisitor.java
    1
  • dll
    2022-07-23
    对于防sql注入安全检查的具体code老师有没有资料能告知一下,谢谢
  • dll
    2022-07-23
    老师这节课的内容是掌握了本章节讲到的生成代码解析sql,增加防注入功能,模拟一个根据主键前四个字母定位数据库位置,调用对应数据的sql查询就可以吗
  • ifelse
    2021-10-15
    规则文件,看起来感觉陌生,不知道啥意思,只知道是一条条规则
  • Nick
    2021-03-25
    “作者回复: 我最近其实在构思写一个分布式数据访问的工具。跟MyCat的定位会不大相同,是想整体上把数据访问这件事情,对应用透明化。野心有点大。 因为现在很多用到数据库的应用,其水平扩展能力都困在了数据库这个点上。这个问题不解决,所谓云原生就是在忽视房间里的大象。 我看资料,已经有一些分布式数据库的商业产品。但要像使用一个单一的数据库一样的使用它,还是有点困难。 我整理一下这方面的思路,会跟大家分享的” 宫老师,我们很想听 您 关于 分布式数据访问工具 的分享。
    展开

    作者回复: 奥?我还挖过这样一个坑呀:-( 好吧,我确实关心这个方向,那我一定整理出一篇加餐来! 总体来说,数据库这个瓶颈,还有围绕数据库的计算模式,有点让人失望。我希望能见到革命性的创新出现,而不是现在的修修补补。

  • 超越杨超越
    2020-11-10
    对老师讲的这部分还是没有太理解。 采用编译技术对SQL进行解析之后呢,结果是什么呢?遇到合法的SQL就执行,非法的SQL语句就直接报错么? 请问采用类似于正则匹配之类的方式,可以做到么?

    作者回复: 我再帮你理一下思路: 首先,你要理解本节课所提到的场景。在构建大型的系统的时候,经常需要把数据分片,也就是把逻辑上是同一个表的数据,物理上存放到不同的数据库服务器上。但是在编程的时候呢,又不想关心底层数据物理存储的细节,从而让业务逻辑与数据存储策略解耦。于是,就有一些中间件来完成这个任务。它们要能够读懂SQL语句,并把不同的SQL语句导向到不同的数据库服务器。 那怎么读懂SQL语句呢?这就需要编译技术的帮助了。首先,通过语法分析,把一个SQL语句变成结构化的AST,这样你就知道select子句、from子句、where子句等分别是什么,等等。接着,你再根据DSL中定义的规则,判断出该SQL应该发往哪个数据库服务器。 你说的正则匹配,也是可以的,不过正则文法的描述能力差一些。对于比较复杂的语句,正则文法是无能为力的。即使是用正则文法的方式,其实也一样是经历了一个解析过程,也就是编译过程。

  • 冯选刚
    2020-01-23
    学到了。

    作者回复: Great! 希望应用篇能够帮大家把编译原理跟实际应用更好地结合起来!

  • zhj
    2019-11-05
    SQLVisitor代码在哪里了

    作者回复: https://github.com/RichardGong/PlayWithCompiler/blob/master/lab/db/src/main/playdb/SQLVisitor.java 我在文稿中加一下链接...

    1
  • 52rock
    2019-10-03
    查找delete没有where的SQL

    作者回复: 嗯。这种delete语句肯定比较危险。