暂无图片
暂无图片
4
暂无图片
暂无图片
暂无图片

Halo数据库正式兼容MySQL5.7

原创 JGJ 2023-10-07
15636

2个研发1个测试,从2022年9月初立项并开始开发,到2022年11月完成初版,然后继续改进和测试,到2023年2月初,终于拿出了一个正式版本,总计历时半年。

正式版本经过了全面的功能测试(包含各种常见SQL场景的上万条测试SQL)和tpcc / sysbench压测(7*24小时),稳定且性能良好,可以上线运行。

记得最初立项的时候,初步做了一些调研,网上的文章谈论MySQL和PostgreSQL之间的语法差异,一般都是说replace into,ignore,on duplicate key等几个,然后就是字段类型的差异,给人的感觉是差异很小,兼容起来很简单。

事实上,等到我们开始开发,同步开始全面研究MySQL语法,编写测试case时,发现两者之间的差异,远超网上的泛泛之言。

今天这个文章,想和大家分享我们Halo(基于PostgreSQL-14.5)兼容了MySQL哪些主要语法(至于具体的实现方式,一般都是通过改写词法语法文件,以及改写后续的执行流程等,总的来说挑战不小),供有兴趣的朋友参考。

下文中我们把兼容MySQL5.7的Halo数据库称为Halo-MySQL。


1、insert ignore

1)MySQL的“insert ignore”用法

插入的数据存在冲突(根据主键或者唯一索引判断)时,本次插入无效;没有主键冲突时,直接插入数据。

一般的写法为:

insert ignore into tb values(1, 1, 1, 'ww');

2)PostgreSQL无此用法,但是有与之对应的功能语法,一般的写法为:

insert into tb values(1, 1, 1, 'ww') on conflict (id) do nothing;

3)Halo-MySQL支持MySQL的“insert ignore”行为和语法细则,与MySQL完全一致。

2、replace into

1)MySQL的“replace into”用法

插入的数据存在冲突(根据主键或者唯一索引判断)时,则先删除此行数据,然后插入新的数据;否则,直接插入新数据。

一般的写法为:

replace into tb values(1, 1, 1, 'ww');

2)PostgreSQL 无此用法(replace into语法是MySQL独有的,且挺受欢迎)。

3)Halo-MySQL支持MySQL的“insert ignore”行为和语法细则,与MySQL完全一致。

3、on duplicate key

1)MySQL的“on duplicate”用法

根据主键id或唯一键来判断当前插入是否已存在。记录已存在时,只会更新on duplicate key update之后指定的字段。如果同时传递了主键和唯一键,以主键为判断存在依据,唯一键字段内容可以被修改。

一般的写法为:

insert into tb values(1, 1, 1, 'ww') on duplicate key update id = 3, class = 4, tel = 4, name = 'ww';

insert into tb values(2, 9, 'ww', 'yy') on duplicate key update class = values(class), name1 = values (name1), name2 = values (name2);

2)PostgreSQL 存在与之对应的功能“on conflict * do update”。

一般的写法为:

insert into tb values(1,1,1,'ww') on conflict (id) do update set id = 3, class = 4, tel = 4, name = 'ww';

insert into tb values(2, 9, 'ww', 'yy') on conflict (id) do update set class = excluded.class, name1 = excluded.name1, name2 = excluded.name2;

insert into tb values(2, 9, 'ww', 'yy') on conflict (id) do update set (class, name1, name2) = (excluded.class, excluded.name1, excluded.name2);

3)Halo-MySQL支持MySQL的“on duplicate”行为和语法细则,与MySQL完全一致。

4、value

insert / replace语句中用value而不是values的人估计很少,但既然是MySQL的合法语法,我们就得支持。以下一些语法差异类似。

1)MySQL的“insert/replace…values”用法

Value与values没有区别,可以混用,只是两者对不同语句插入数量的执行效率各不相同。

一般的写法为:

insert into tb value(1, 1, 1, 'ww');

replace into tb value(2, 2, 2, 'ww');

insert into tb value(3, 3, 3, 'ww'), (4, 4, 4, 'ww'), (5, 5, 5, 'ww');

MySQL的insert into或replace into语句中,values部分可以用value代替。

2)PostgreSQL无此用法。

3)Halo-MySQL支持MySQL的“value”行为和语法细则,与MySQL完全一致。

5、单双引号

看起来不起眼的单双引号,给我们造成了不小的麻烦。

1)MySQL的“单双引号”用法

引用字符串常量时候需要用一对英文单引号或者双引号。在一般使用的时候单引号和双引号没有什么区别。

一般的写法为:

select "This is Tom's dog, his name is \"Jack\"";

select 'This is Tom"s dog, his name is \'Jack\'';

2)PostgreSQL 也有单双引号,其中只能用单引号表示值,双引号是表示系统标识符的(比如表名或者字段名)。MySQL则使用反单引号表示系统标识符。

一般的写法为:

select "name" from "tb" where "id" = 1;

3)Halo-MySQL支持MySQL的“单双引号”行为和语法细则,与MySQL完全一致。

为了支持MySQL的单双引号,包括下面的backtick符号,我们费了一点心思。这是最初项目开始时完全没有想到的。

6、backtick符(俗称反单引号)

1)MySQL的“`”用法

MySQL“官方”用法中,系统标识符(库名,表名,字段名)一般都用‘`‘符号括起来,不过开发人员写SQL时为了省事一般都不写‘`’。

一般的写法为:

insert into `tb` (id,name,age) values(1, 'ww', 'yy');

insert into `db`.`tb`(id, name, `age`) values(6, 'ww', 'yy');

select count(*) as c from `tb`;

2)PostgreSQL 没有此符号,系统标识符可以用双引号“”括起来(不过目的是为了区分大小写)。

一般的写法为:

insert into "tb" (id, name, age) values(1, 'ww', 'yy');

insert into "db"."tb"(id, name, "age") values(6, 'ww', 'yy');

select count(*) as c from "tb";

3)Halo-MySQL支持MySQL的“`”行为和语法细则,与MySQL完全一致。

7、转义字符

1)MySQL的“转义字符”

利用反斜杠“\“转义。

一般的写法为:

insert into tb values ('kate\'', 28);

2)PostgreSQL中实现转义有两种方式,一是在postgresql.conf配置文件中设置,这样所有字符型字段的值会自动被转义,另一种是在需要转义的字段值前面加上字符‘E’,表示当前字段的值需要转义(其他没有‘E’前缀的字段值不用转义)。

一般的写法为:

insert into tb values ('kate\'', 28);

insert into tb values (E'kate\'', 28);

3)Halo-MySQL支持MySQL的“转义字符”行为和语法细则,与MySQL完全一致。这里主要是发现了MySQL和PostgreSQL对‘\f’的转移处理不同。有兴趣的朋友可以写两个SQL试试。

8、limit

1)MySQL的“limit”用法

Limit限制查询结果的条数。limit a,b; 其中a为起始索引,从0开始,b为获取数据长度。一般的用法为:

select * from tb limit 5;

select * from tb limit 10, 15;

2)PostgreSQL也有limit语法。不同的是,limit a offset b;其中:b为起始索引,a为获取数据长度(与MySQL相反!)。

select * from tb limit 5;

select * from tb limit 15 offset 10;

3)Halo-MySQL支持MySQL的“limit”行为和语法细则,与MySQL完全一致。

9、Prepare1(后面还有prepare2)

1)MySQL的“prepare”用法

prepare、execute、deallocate统称为PREPARE STATEMENT,习惯称其为预处理语句。

一般的用法为:

prepare stmt1 from 'insert into tb(c1) values(?)';

set @a=**;

execute stmt1 using @a;

deallocate prepare stmt1;

2)PostgreSQL也有相应的预处理语句。

一般的写法为:

prepare stmt1 (inttext, bool, numeric)as insert into tb values($1);

execute stmt1(**);

deallocate prepare stmt1;

3)Halo-MySQL支持MySQL的“prepare”行为和语法细则。

Prepare1的用法总体上和select等都归类为“Query”,其通信协议中的command类型都是3。下面还有另一种prepare。

10、Prepare2

Prepare还有一种用法,在jdbc中称其为服务端prepare(JDBC的url连接串中可以设置)。这是一种更高级(主要是性能更好)的prepare。在客户端和服务端的通信协议中有专门的命令类型。这种prepare也有配套的execute等。

MySQL和PostgreSQL都支持这种prepare,但是差别非常大,从通信过程,到通信协议,到后端的执行流程,差别都很大。我们一度想放弃兼容这种prepare,但无奈tpcc和sysbench都不答应,所以只能硬着头皮肯,最后实现了高度兼容(说明,不是完全兼容),tpcc,sysbench,Mybatis等跑下来都没有问题。

11、delete/update order by…limit

1) MySQL的“delete/update…order by…limit”

删除/更新指定行数。

一般的用法为:

update tb SET name='www' where id = 1999 order by id limit 1;

delete from tb where name = 'www' order by id LIMIT 1;

2)PostgreSQL不支持这样的语法。

3)Halo-MySQL支持MySQL的“delete/update…order by…limit”语法。另外,MySQL中delete/update等支持很复杂的语法(一般来说实际业务应用中都很少使用这些复杂语法),我们现在也只是把我们认为更有实用价值的limit支持了,其他的暂时不支持。

12、聚合函数配合order by

1) MySQL的“select count(*) from order by”

其实Order by在这里没有任何意义,但是MySQL就是存在这样的语法。

一般的写法为:

select id, count(*) as count_all from tb group by id order by id limit 5;

select id, sum(id) as sum_all from tb group by id order by id limit 5;

2) PostgreSQL不支持这样的语法。

3) Halo-MySQL支持MySQL的“select count(*)…order by…limit”语法。这也是一个有点“怪异”的用法,不过有人提出来了,我们也得支持。

13、复杂的order by

1) MySQL的“select count(*) from order by”用法示例:

select * from tb1 order by field(name1, 'aaa3'), id;

select * from tb1 order by if(isnull(name2), 0, 1), id;

select * from tb1 order by isnull(name1), isnull(name2), id;

2) PostgreSQL不支持这样的语法。其实主要是不支持这些函数。

3) Halo-MySQL支持MySQL这样的语法。主要就是添加了相应得自定义函数。

14、正则表达式

1)MySQL的“正则表达式”

正则表达式用来匹配文本的特殊的串,将一个模式(正则表达式)与一个文本串进行比较。一般的用法为:

select id, name from tb where name regexp '^st';

select id, name from tb where name regexp binary '^st';

select id, name from tb where name regexp 'gy$';

select id, name from tb where name regexp 'k{0,}';

2)PostgreSQL使用正则表达式时需要使用关键字“~”(或“~*”),且PG仅支持字符串类型的字段使用正则表达式匹配(不支持int类型得字段使用正则表达式,但MySQL支持int等类型)。

一般的写法为:

select id, name from tb where name ~* '^st';

select id, name from tb where name ~ '^st';

select id, name from tb where name ~ 'gy$';

select id, name from tb where name ~ 'k{0,}';

3)Halo-MySQL部分兼容MySQL的正则表达,可以支持字符串类型的匹配,整形字段的匹配暂不支持。

15、null的排序

1)MySQL的NULL字段在排序(升序)时默认放在最前。

2)PostgreSQL的NULL字段在排序(升序)时默认放在最后。

3)Halo-MySQL支持MySQL的NULL排序规则,升序排序时放在最前,降序排序时放在最后。

16、set@**用户变量

相对PostgreSQL,MySQL中用户变量有独特的存在。在普通SQL中可以使用用户变量替代值,在prepare的execute语句中,在函数或存储过程中更是无处不在。且用户变量还能像Java,Python,C/C++中的变量一样写运算表达式,这可着实为难了我们。

“@**用户变量”用法示例:

set @s = 'SELECT SQRT(POW(?,2) + POW(?,2)) AS hypotenuse';

set @c = 6;

select * from tb where id = @c;

2)PostgreSQL不支持这种用法。PostgreSQL中基本没有用户变量的概念,PostgreSQL中用set设置的值和MySQL用set设置的用户变量用途不同。

3)Halo-MySQL支持MySQL用“set @**=**”在服务端设置用户变量。行为和MySQL稍有一点差异,不支持在sql语句中使用“@arg1 = @arg1 + 1“类似的运算表达式做法。

17、autocommit

MySQL支持在服务端设置auto_commit为on或者off,PostgreSQL服务端auto_commit只能是on,必须依赖客户端来实现auto_commit为on或者off。

Halo-MySQL支持在服务端设置auto_commit为on或者off。对于设置auto_commit为on的情况,与PostgreSQL服务端原生机制相同;对于设置auto_commit为off的情况,这种场景下业务代码一般在写请求之前都会显式使用“begin”或“start transaction”开启一个事务,此时服务端的auto_commit自动变为off,正好符合业务要求,如果是手工操作,则要求先人工显式的添加“begin”或“start transaction”语句。

好了,这次就先把这些我们已经发现,且认为是非常常用,且我们目前已经实现好了的兼容语法展示出来。

另外我们还整理出了几十处我们认为不太常用的语法差异。后续根据市场和客户的反馈再来实现。分享文章后续推出。

欢迎大家指出问题,相互交流。

谢谢大家。

「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论