admin

  • 浅读《伊利亚特》

    ·

    真的是浅读。

    最近在和孩子一起读《伊利亚特》(版本),随意翻读了书中的一小段故事,恰好是关于阿基琉斯(Achilleus Ἀχιλλεύς,更多时候被翻译作阿喀琉斯)的,一下子就被吸引了。这一小段是关于,阿基琉斯在出生之后,因为他的妈妈忒提斯得知神的预言,她的孩子将死与特洛伊战争,所以,忒提斯就把阿基琉斯以女孩子的身份送入王宫,让他和宫女们一起玩耍长大。而奥德修斯为了找到预言中的阿基琉斯,他来到王宫,看到一众宫女,智慧或者说夹杂的奥德修斯于是心生一计,他将长矛盾牌和女孩子们喜欢的玩具放到一起,然后故意制造出被入侵的混乱假象,很多的宫女都在混乱中四处逃跑,而有一个红头发的孩子,并没有像其他人一样害怕和逃走,而是勇敢的拿起了长矛和盾牌要去战斗。奥德修斯明白,这就是他要找的阿基琉斯。

    最终,阿基琉斯带着自己的宿命去参加了特洛伊战争,立下了赫赫战功,也与预言最终死在了这场战争中。

    在看完上面这一小段之后,就深深被《伊利亚特》吸引了。

    极力避免预言中的悲剧,却发现始终无法逃脱。这似乎是众多希腊故事的基础设定之一。在看完上面的段落后,我又继续开始从头阅读伊利亚特。

    不和女神厄里斯(Eris)在忒提斯和珀琉斯的婚礼上,扔下了那颗“金苹果”之后,而最终所导致的一系列事件最终导致了特洛伊战争的发生。在婚礼上,厄里斯扔出的金苹果上刻着“献给最美丽的女神”,落在了赫拉、雅典娜、阿芙洛狄忒(也做阿佛洛狄忒,他的罗马名“维纳斯”更为人所熟知)的身边。为了分出谁是世间最美丽的女神,三位女神在赫尔墨斯的带领下,来到了,还自以为自己是一个普通牧民的帕里斯(Paris)身边,要求他来最终决定金苹果的归属。

    最终,在阿芙洛狄忒许诺帮他赢取世间万人垂怜的海伦芳心之后,帕里斯将金苹果判给了阿芙洛狄忒。

    那么,这个普通的牧民帕里斯是谁呢?他当然不是普通的牧民,他原本是特洛伊的王子,因为出生时被预言,他将会给特洛伊带来毁灭,所以,出生后,特洛伊的国王便忍痛命人将他送走,并命令将其杀死。不过,由于此人的仁慈,不忍心下手,最终自己收养了这个孩子,因为在平时劳作时,他们都将孩子放在篮子中,于是将孩子命名为帕里斯(Paris)。

    在帕里斯成年后,遇到了带着金苹果的阿芙洛狄忒。他的命运终于迎来了改变。后面,他终于回到了特洛伊王宫与父母相认,并最终辗转见到了海伦,并在爱神的帮助下,赢得了海伦的芳心。也最终给特洛伊带来了毁灭。

    如前所述,在希腊神话中有着大量的“悲剧”,这些悲剧似乎有一些模式:预言了悲剧的发生,然后做很多努力避免悲剧,最后发现都是徒劳。这一小段就有两个这种模式:

    • 忒提斯为了保护她的孩子阿基琉斯,将他送到宫中,当做女生与宫女一起,却还是被奥德修斯所识破,而带其一同参加特洛伊战争,并成为战争中的大英雄,也最终死于这场战争。
    • 特洛伊国王为了避免因自己的孩子帕里斯给特洛伊带来的毁灭,于是让人把刚出生不久的帕里斯带走并杀死,却不想,后来成为牧羊人的帕里斯,遇到了三位女神的“金苹果”问题,而最终给特洛伊带来毁灭。

    今天就简单的写到这里,真的是非常有意思的故事。故事本身引人入胜,又包含了古希腊人对于命运、战争、爱情、美丽的理解。

  • 这是一个系列文章,旨在了解如何使用Flex/Lex和Yacc/Bison进行词法和语法解析。这个系列,分成了几个部分,包括

    • flex的基本用法
    • 使用flex/bison实现一个简单的计算器
    • 实现一个带有条件判断与循环的计算程序

    了解这个系列需要一定的编译原理知识作为背景知识,了解程序如何从字符串先解析成Token,而后使用语法解析器生成解析树,最后执行该解析树。

    概述

    lex/flex可以按照“词法文件”的定义,将文本解析成单个的Token,然后通过执行“词法文件”中定义的Action来完成一些操作,一般,flex的输出会通过函数/变量将结果传递给yacc/bison进行进一步的语法解析。为了简化,本文将仅通过独立的“词法文件”完成一些操作,以了解flex的基础使用。

    这里完成的程序是一个简单的“count”程序,输入是一个文件,程序输出文件中包含的字符数、词语数、以及行数。

    安装flex并编写词法文件

    1. 安装lex: yum/apt-get install flex

    2. 编写如下词法文档:

    %{                                       //
            int characters = 0;              //    %{ ... }% 之间的部分是"Declarations"
            int words = 0;                   //    Declarations 部分声明的变量,是可以在全局使用的
            int lines = 0;                   //    例如,在该示例的main程序中,就通过extern声明的方式
    %}                                       //    使用了这些变量
    %%                                       //
    \n      {                                //    从这里开始是Translations阶段
                    ++lines;                 //    这里定了Token,以及遇到了Token之后
                    ++characters;            //    应该采取/执行什么,例如这里遇到了\n字符
            }                                //    则,将lines/characters变量都加1
    [ \t]+          characters += yyleng;    //
    [^ \t\n]+ {                              //    注释部分的文本需要删除,程序才能正常编译 
                    ++words;                 //    删除注释的vim命令:1,$s/\/\/.*$//g 
                    characters += yyleng;    //
            }                                //
                                             //
    %%

    直接使用如上代码的话,后面就会在gcc编译的时候遇到如下错误:

    $ lex zzx.l
    $ gcc lex.yy.c wc.c -o wc.out
    /tmp/cc1SPYm2.o:In function yylex':
    lex.yy.c:(.text+0x42f):undefined reference toyywrap'
    /tmp/cc1SPYm2.o:In function input':
    lex.yy.c:(.text+0xf73):undefined reference toyywrap'
    collect2: ld returned 1 exit status
    

    如果你也遇到了这个错误,不用担心,你并不孤单,在Stackoverflow上看到解决该失败的的答案一共有150点赞(up),就知道大家都一样了(参考@Stackoverflow)。因为默认的,lex生成的词法解析程序中,在最后是需要调用的yywrap函数的(关于yywrap),如果不打算提供该函数,则可以使用lex选项 %option noyywrap 禁用该调用。那么上面的代码就需要修改为:

    $ cat zzx.l 
    %{
            int characters = 0;
            int words = 0;
            int lines = 0;
    %}
    %option noyywrap
    %%
    \n      {
                    ++lines;
                    ++characters;
            }
    [ \t]+          characters += yyleng;
    [^ \t\n]+ {
                    ++words;
                    characters += yyleng;
            }
    
    %%

    编写入口函数并调用yylex

    词法文件需要使用工具flex将其编译生成一个c语言文件,然后再使用gcc将其编译成一个可执行文件。编译前,我们需要先编写一个简单的main函数. 再编写一个程序的入口函数(main),并调用yylex()就可以了。具体如下:

    $ cat wc.c
    #include <stdio.h>
    
    int yylex(void);
    
    int main(void)
    {
            extern int characters, words, lines;
    
            yylex();
            printf("%d characters, ", characters);
            printf("%d words, ", words);
            printf("%d lines\n", lines);
            return 0;
    }

    这里需要注意:在程序中,我们通过调用yylex()完成了实际的词法解析过程,并获得执行结果。这是一个非常简单的示例,实际过程比这要更加复杂,在词法文件中,每一次rule解析完成后,再起action部分,通常都会有return语句结束本次yylex调用,所以会是一个反复调用yylex的过程。

    编译并执行

    $ lex zzx.l    
    $ gcc lex.yy.c wc.c -o wc.out
    $ chmod +x wc.out
    $ cat s.txt
    this is a input file.
    this is a input file.
    $ ./wc.out < zzx.l
    404 characters, 36 words, 18 lines
    $ ./wc.out < s.txt
    44 characters, 10 words, 2 lines

    好了,至此,我们就完成一个词法解析的任务,因为这个任务不涉及任何语法(yyac)解析,所以比较适合初学者学习词法解析工具lex。

    补充关于Definitions

    为了再略微增强该示例的,这里对上面的示例又做了一个小调整,新增一行“Definitions”,有时候为了增强可读性,会对一些expression定义一个名称,如下,将\n定义为NL:

    %{                                        //
            int characters = 0;               //   %{ ... }% 之间的部分是"Declarations"
            int words = 0;                    //   Declarations 部分声明的变量,是可以在全局使用的
            int lines = 0;                    //   例如,在该示例的main程序中,就通过extern声明的方式
    %}                                        //   使用了这些变量
                                              //
    NL \n                                     //   这里新增了一行,这是一行 Definitions
                                              //   将\n用字母NL定义,所以下面的\n也就可以使用NL
                                              //   试想,如果表达式很复杂用这种方式,可读性会增强很多
    %%                                        //
    NL      {                                 //   从这里开始是Translations阶段
                    ++lines;                  //   这里定了Token,以及遇到了Token之后
                    ++characters;             //   应该采取/执行什么,例如这里遇到了\n字符
            }                                 //   则,将lines/characters变量都加1
    [ \t]+          characters += yyleng;     //
    [^ \t\n]+ {                               //   注释部分的文本需要删除,程序才能正常编译        
                    ++words;                  //   删除注释的vim命令:1,$s/\/\/.*$//g 
                    characters += yyleng;     //
            }                                 //
                                              //
    %%           

    自此,我们就了解一个词法解析文件的几个主要部分:Definitions、Declarations、rule(以及rule对应的Action)。

    参考资料:

    更多说明

    • Flex / Lex程序通常与Yacc/Bison一起使用,flex负责词法解析,bison则负责语法解析
    • flex与bison接口的函数,就是上面的调用 yylex()函数,该函数每次基于某个规则(rule)解析到一个新的Token的时候,则会执行对应的“Action”(也就是每个Token后面的代码)部分的代码。例如,上面的程序中会执行++lines++words代码。
    • 在rule action部分,我们看到使用了一个yyleng的“变量”用于获取当前被解析的原始字符串的长度。类似的“变量”有:yyleng、yytext、yyin等,完整的列表可以参考:Values Available To the User。另外,这些“变量”并不是真的变量,大部分都是一些“宏”,例如,yytext的真实定义可能是这样的:#define yytext (((struct yyguts_t*)yyscanner)->yytext_r)。了解这一点,有利于理解这些”变量”并不能在外部直接引用。
      • yyin yylex函数处理的字符串来源,默认情况是标准输入,在你的程序,例如可以定义为一个打开的文件描述符(在Posix中,一起都是文件)
      • yylength 用于记录,当前读取的Token的长度
      • yytext 用于记录当前读取的文本
    • 一般的,flex的“Action部分”,会包含一个return,例如如果遇到一个整数,可能会看到类似这样的代码:return INTEGER; 这时候,yylex()遇到一个对应的字符就会返回INTEGER
    • 在实践中,则是按照如下方式实现:
      • 在 yacc/bison的语法文件中定义Token,例如整数为 INTEGER,语法为 %token INTEGER
      • 使用yacc/bison命令生成对应的头文件,头文件则会包含关于 INTEGER的预定义:#define INTEGER 257
      • 只需要在flex词法文件中包含该头文件,就可以使用这里的预定义 INTEGER
      • 那么较为完整的代码看起来就是这样
    cat cal.y
    ...
    %token INTEGER
    ...
    cat cal.tab.h //这是bison生成的文件
    ...
    #define INTEGER 257
    ...
    cat cal.l
    ...
    [[:digit:]]+  { return INTEGER; }
    ...
    • 我们在考虑另一个问题:在lex的rule action可以使用本地的“变量”(其实是“宏”),也会通过return语句给yyparse()中调用yylex时,返回当前Token的类型。如果一个Token是一个[:digit:]+的时候,我们除了需要知道这个Token是一个整数之外,至少yyparse()还需要知道这个是一个什么整数,具体数值是多少,当然并不是所有的token都需要,一般identifier都是需要的。而,前面的yytext都是yylex本地的“变量”。这时候,通常会使用yylvalyylval是一个由yacc/bison定义的变量(默认是int类型),用于存储词法解析需要传递给yyparse的数据,在yacc/bison的语句处理的Action阶段,可以使用变量,以获得词法解析阶段的一些值。例如,一个Token是一个整数、字符串(并非keyword)的时候,我们会将对应的值存储在yylval中。所以,yylval通常会被定义为一个联合体(union类型),用于存储不同类型的值。

    关于这几个概念的更详细细致的解释可以参考最前面提到的“IBM的z/OS系统的文档中关于lex和yacc的介绍”(参考:Tutorial on using lex and yacc)。

  • This content is password-protected. To view it, please enter the password below.

  • 从XtraBackup的RC版本到1.0版本,一直在使用这个产品进行数据库备份。最近在群里面,依旧有一些在讨论MySQL的备份到底是应该使用物理备份还是逻辑备份。在本文中,我们将比较这两种备份方式,并提供有关它们的概要介绍。

    当前, MySQL 数据库备份,有两种主要的备份方式,即 mysqldump(逻辑备份)和 XtraBackup(物理备份)。

    mysqldump vs Xtrabackup

    mysqldumpXtraBackup
    归属Oracle MySQL(是MySQL的一部分)Percona(是一家非常了不起的MySQL开源服务商)
    备份文件格式SQL(文本)物理数据文件(二进制)
    使用场景小到中型数据库,定期备份和还原,跨版本迁移大型数据库,高性能备份和还原,避免表锁定
    优点– 备份以文本格式存储,易于查看和编辑。– 高性能备份和还原,适用于大型数据库。
    – 跨 MySQL 版本兼容性较好。– 备份期间不会锁定表,对生产环境影响小。
    – 可备份指定数据库、表或查询结果。– 支持增量备份,可节省存储空间和备份时间。
    缺点– 对于大型数据库,备份和还原速度相对较慢。– 备份文件通常较大。(通常压缩率要低一些)
    – 在备份期间,可能会锁定表,影响生产环境查询。– 不容易查看备份内容,因为是二进制备份。
    原理通过导出 SQL 语句来备份数据和结构。备份 InnoDB 存储引擎的物理数据文件和日志文件。
    增量支持不直接支持。(可以通过备份binlog来实现)支持增量备份,可减少备份数据的大小。

    一些其他的重要差异

    两者除了上述差异,根据经验一些重要的建议如下:

    • mysqldump是逻辑备份,在恢复的时候一方面跨版本的效果会更好,因为都是SQL语句;同时,也可能会因为字符集、数据库参数配置(如SQL_MODE等),导致恢复的时候失败或者出现一致性的问题。而XtraBackup是物理备份,数据一致性总是可以保障,但是跨版本恢复能力比较弱。
    • mysqldump的备份与恢复的是官方自带的产品,所以也是被广泛使用的产品,但是因为他在大数据量(例如超过几百GB)时备份的性能、恢复的性能较差,所以,XtraBackup会是很好的补充。
    • 在实践中,XtraBackup也是广泛被使用的,稳定性有比较好的保障,但是配置与使用的成本略微要高一些。在构建自己的复制方案的时候,对于大型的生产系统中,比较建议使用。

    概述mysqldump(逻辑备份)

    概要介绍: mysqldump 是 MySQL 数据库备份的一种经典工具,它通过导出 SQL 语句来备份数据库中的数据和结构。这种备份方式被称为逻辑备份,因为它备份的是数据的逻辑表示,而不是物理数据文件。以下是 mysqldump 的一些关键特点:

    • 适用性: mysqldump 适用于小型到中型的 MySQL 数据库,或者数据库大小不超过几百 GB 的情况。
    • 备份内容: mysqldump 会生成包含 CREATE TABLE 和 INSERT 语句的 SQL 文件,其中包括数据表结构和数据内容。
    • 优点:
      • 数据备份以文本格式存储,易于查看和编辑。
      • 跨 MySQL 版本的兼容性较好,可以在不同版本之间迁移备份数据。
      • 具有备份数据库、表或特定查询结果的灵活性。
    • 缺点:
      • 对于大型数据库,备份和还原速度相对较慢。
      • 在备份期间,数据库可能会锁定表,影响生产环境的查询。
      • 逻辑备份的还原速度相对较慢。

    概述XtraBackup(物理备份)

    概要介绍: XtraBackup 是一种高性能的 MySQL 物理备份工具,主要用于备份 InnoDB 存储引擎的数据文件和二进制日志文件。以下是 XtraBackup 的一些关键特点:

    • 适用性: XtraBackup 适用于大型 MySQL 数据库,对备份性能和恢复速度有高要求的情况。
    • 备份内容: XtraBackup 备份的是数据库的物理数据文件,包括 InnoDB 存储引擎的数据和日志文件。
    • 优点:
      • 高性能备份和还原,特别适用于大型数据库。
      • 备份期间不会锁定表,对生产环境的影响较小。
      • 支持增量备份,可以节省存储空间和备份时间。
    • 缺点:
      • 备份文件通常较大。
      • 不容易查看备份内容,因为它是二进制备份。

    建议

    根据你的数据库需求和性能要求,选择适当的备份方式:

    1. mysqldump: 适用于小型数据库或需要定期备份和还原的数据库,以及对备份文件易于查看和编辑的情况。
    2. XtraBackup: 适用于大型数据库或对备份性能和恢复速度有高要求的数据库,尤其是在需要避免表锁定和支持增量备份的情况下。
    3. 在某些情况下,可以结合使用两种备份方式,以满足不同的需求。这也是一种常用策略。
    4. 无论选择哪种备份方式,都应定期测试备份恢复过程,以确保备份的可用性和完整性。这一点非常重要。

    最终的选择取决于数据库的特定需求和性能要求。根据实际情况,你可以灵活地使用 mysqldump 和 XtraBackup 来满足你的数据库备份和恢复需求。

  • 周末早上探访净慈寺

    ·

    净慈寺

    大概因为杨万里的《晓出净慈寺送林子方》,也让这个禅寺更有一些不同。加上寺院里不时传出悠扬的钟声–“南屏晚钟”(西湖十景之一)更让西湖多添了一份韵味。

    (more…)
  • SQL Server中的Schema、dbo

    ·

    MySQL已经流行了非常长时间,已经有一代数据库从业者最初就是从MySQL起接触数据库。再重新了解其他数据库的时候,虽然底层原理很多类似,但是上层实现、接口与使用上还是有非常大的不同。本文,就简单的介绍一下SQL Server中的Schema、dbo概念。

    参考阅读:

    Schema or dbo or Database?

    这是一个问题。也有很多人做了很多深入的讨论,关于这一点可以参考这篇文章,以及这篇文章后面的100个评论: Why Use Schemas?

    有几个观点吧:

    • 使用Schema的想法是”美好的”,但是实际的情况往往并不理想,甚至是还不如统一使用dbo。文章作者说,随着时间推移,他看到的实际情况是,使用了多个Schema之后,往往不同Schema中存储的内容并是逻辑上在一起的,而是由于各种历史原因,各个业务的数据表都会混杂在一起,这时候,由于修改应用的成本太高,大家也往往也不会去移动Schema下的表,去让他们在逻辑上更加合理。所以,基于此,作者并不赞同使用Schema,而是倾向于在需要的时候使用不同的Database去隔离逻辑上没什么关系的数据表。
    • 很多的第三方的应用,都是使用一个数据库、一个用户访问所有的数据表,因此Schema方式虽然很理想,但是现实世界并不常用。
    • 另一方观点是在于,在一个设计较好的数据库中,Schema可以很好的对不同的数据表进行分组,例如,用户相关的表,库存相关的表,店铺相关的表等。另外,在基于这样设计下,权限系统也会更加好管理。另外,否则可能会使用大量的前缀的方式(例如,user_开头代表用户,shop_开头代表店铺相关业务)来区分这些表,看起来并不整齐,整体的授权管理也会比较难一些。
    • 比较多人,认为虽然多Schema的设计,前期是比较理想的,但是随着业务和需求的变化,表的业务属性也会发生变化,按照多Schema设计来说,这时候是需要将表移动到合适的Schema下面,但是因为代码的SQL中都带有Schema,这就到这个”移动”的操作变得异常困难,所以也就会最终一定会破坏当初使用Schema的初衷。
    • 很多人认为,所有表放在dbo中,从业务角度来说,其实问题是最少的。
    • 有人提到,他这样使用schema,将所有的基础表都放在dbo中,但是某些特殊用户的表或者对象放在特定的schema中。例如,一些特定用途的视图、存储过程等。这样,更加便于权限管理。(个人注:这似乎是一种非常适合schema的场景)
    • (Mark Broadbent)提到,相比Oracle数据库的Schema,SQL Server权限系统设计更加面向的是Database,而不是Schema。用户通常都有多个Schema的访问权限。
    • Ben Thul提到,在大量存储过程的权限管理过程中,schema应该是非常有效的。这和前面的观点是有一定的相似的。Brent Ozar则认为,权限管理更多是通过用户角色组来管理,就可以了。Ben Thul也反驳到,当你已经有100个存储过程的时候,当写101个存储过程,如果没有schema,那么还是要单独管理这个存储过程的访问权限,这时候如果通过以schema为单位的权限管理,确实是高效的。
    • 有来曾经在MSFT PMs相关项目的员工(Clifford Dibble),回忆schema设计的初衷如下:
      • 在单个数据库中,提供一种命名空间隔离的方式
      • 在使用GRANT语句授权时,大大简化对一组对象进行授权管理
      • 微软公司内部”WinFS”团队需要该功能
      • 与ANSI/ISO SQL有更好的兼容性
      • SQL Server自己需要一个系统使用schema:sys
      • 让用户删除更加简单(有一些明确的客户需要)
      • 允许让角色组作为schema的owner
      • 允许多个用户都有某个特定的默认schema
    • vinod jain在2021年给出回答(是的,这个帖子是2010年发的,这个讨论延续了11年)有一定代表性,也是两种观点的一个中间形态:
      • 所有的核心表全部都放在dbo中
      • 其他的辅助功能表都独立放在不同的schema中,例如结构记录变更时用到的临时表和数据、导入导出时的中间表、用户日志表