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 (int, text, 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”语句。
好了,这次就先把这些我们已经发现,且认为是非常常用,且我们目前已经实现好了的兼容语法展示出来。
另外我们还整理出了几十处我们认为不太常用的语法差异。后续根据市场和客户的反馈再来实现。分享文章后续推出。
欢迎大家指出问题,相互交流。
谢谢大家。




