超全mysql转换postgresql数据库方案

Posted 爱码代码的林子哥

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了超全mysql转换postgresql数据库方案相关的知识,希望对你有一定的参考价值。

写在前文:

近期由于公司业务产品发展需要,要求项目逐渐国产化:(1)项目国产操作系统部署;(2)数据库国产化;国产数据库最终选型为highgo(瀚高),该数据库基于pg开发,所以要求先将mysql适配到postgresql数据库;

一、初识postgresql

1.1 docker安装postgresql

1.1.1 镜像拉取

dockerhub官网选取自己想安装的版本(https://hub.docker.com/_/postgres/tags),我这里选取的是13.9版本;

拉取镜像到本地

docker pull postgres:13.9

1.1.2 执行镜像安装postgresql

docker run --name 1.postgres \\
--restart=always \\
-e POSTGRES_PASSWORD='jY%kng8cc&' \\
-p 5432:5432 \\
-v /data/postgresql:/var/lib/postgresql/data \\
-d postgres:13.9

1.1.3 创建数据库,设置默认查询模式

-- 建库
CREATE DATABASE "test"
WITH
  OWNER = "testuser" -- 数据库用户
;

-- 创建模式
CREATE SCHEMA "test" AUTHORIZATION "test";

-- 设置默认查询模式 pg连接时默认使用public这个schmel,想让项目连接时使用自己创建的模式需要修改一下默认查询模式
ALTER ROLE testuser SET search_path="test";

1.2 postgresql学习

这里放上一个学习地址,大家可以参照性学习,边使用边学习

https://www.sjkjc.com/postgresql/psql-commands/

1.3 项目中引入postgresql数据库

1.3.1 版本问题

postgresql-42.2.10(支持PostgreSQL 42)

  • JDK 8 - JDBC 4.2 Support for JDBC4 methods is not complete, but the majority of methods are implemented.

pg驱动版本:springboot2.5.14中默认集成的是42.2.25

1.3.2 添加maven依赖

<dependency>
   <groupId>org.postgresql</groupId>
   <artifactId>postgresql</artifactId>
   <scope>runtime</scope>
</dependency>

1.3.3 配置文件修改

# 1.postgres为容器名称,也可以直接指定ip
pring.datasource.url=jdbc:postgresql://1.postgres:5432/test?autoReconnect=true&autoReconnectForPools=true&useUnicode=true&characterEncoding=utf8&createDatabaseIfNotExist=true&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull
spring.datasource.username=testuser
spring.datasource.password=test
spring.datasource.driver-class-name=org.postgresql.Driver

二、 MYSQL和PG基础语法差异汇总整理

2.1 数据结构对比

mysql

postgresql

TINYINT

SMALLINT

SMALLINT

SMALLINT

MEDIUMINT

INTEGER

BIGINT

BIGINT

FLOAT

REAL

DOUBLE

DOUBLE PRECISION

BOOLEAN

BOOLEAN

TINYTEXT

TEXT

TEXT

TEXT

MEDIUMTEXT

TEXT

LONGTEXT

TEXT

BINARY(n)

BYTEA

VARBINARY(n)

BYTEA

TINYBLOB

BYTEA

BLOB

BYTEA

MEDIUMBLOB

BYTEA

LONGBLOB

BYTEA

DATE

DATE

TIME

TIME [WITHOUT TIME ZONE]

DATETIME

TIMESTAMP [WITHOUT TIME ZONE]

TIMESTAMP

TIMESTAMP [WITHOUT TIME ZONE]

AUTO_INCREMENT

SERIAL , BIGSERIAL

column ENUM (value1, value2, […]

column VARCHAR(255) NOT NULL, CHECK (column IN (value1, value2, […])) pg可以自定义数据类型实现类似效果: CREATE TYPE mood AS ENUM ('sad','ok','happy'); CREATE TABLE person ( current_mood mood ... )

2.2 基础语法差异对比

语法差异

mysql

postgresql

是否相同

分页

select * from t1 limit 2,2;

select * from tbl limit 2 offset 2;

插入数据时:如果不存在则insert,存在则update

replace实现

upsert

大小写兼容

通过配置可兼容

表字段或表名为大写时,字段或表名必须添加双引号

if(), case when

if(), case when 条件1 then 符合值 else 不符合值 end;

case when 条件1 then 符合值 else 不符合值 end;

round(字段,小数位数)

round(字段,小数位数)

round(case(‘字段’ as numeric),小数位数)

null值判断

支持 ifnull(),NVL(),COALESCE()

支持COALESCE()

Update-单表更新

相同

相同(不可全表更新)

update-更新单表多个字段

相同

相同

update-更新并返回

select tem1,tem2 from update test set tem1 = '',tem2 = ''

UPDATE test SET tem1 = '',tem2 = '' RETURNING tem2,tem2;

Update表关联更新

相同

相同

Insert-单行插入

相同

相同

Insert-插入指定字段

相同

相同

insert-插入多行

相同

相同

insert-插入并返回

不支持

INSERT INTO() RETURNING did

Insert-插入,存在则更新

INSERT INTO () VALUE() ON DUPLICATE KEY UPDATE name = EXCLUDED.name

INSERT INTO distributors ( did , dname ) VALUES ( 9 , ' Antwerp Design' ) ON CONFLICT (did)DO UPDATE SET name = EXCLUDED.name

insert-不存在插入,存在更新

replace实现

upsert语句

SELECT

相同

相同

DELETE

DELETE FROM table

DELTE FROM table(不可全表删除)

DELETE

DELETE FROM table WHERE

DELETE FROM table WHERE

DELETE-删除并返回

不支持

DELETE FROM table WHERE RETURNING * ;

INDEX-add

支持alter,create创建

支持create

INDEX-delete

支持alter,drop

支持drop

字符串常量

支持单双引号

支持双引号

插入数据时自增主键

写法一:insert into t1(name) values(‘zhangshan’); 写法二:insert into t1(id, name) values(null, ‘zhangshan’);

insert into t1(name) values(‘zhangshan’);

库名长度

无强制限制

库名、表名限制命名长度,建议表名及字段名字符总长度小于等于63。

三、MYSQL数据结构转换PG数据结构

3.1 mysql数据结构转换PG数据结构

网上有很多转换工具,有些需要收费,这里借助一个最简单最常用的工具-navicat premium(我这里使用的是16版本)转换mysql数据结构到PG的数据结构

点击database选择你想转换的数据库,这里选择postgreSQL,下面选择版本

至此,一份pg的数据结构就保存完整了(注意:此时转换出来的数据结构会存在一些错误,还需要额外手动处理一些问题)

3.2 MYSQL转换PG数据结构存在的问题及解决方案

3.2.1 默认值丢失问题default

(1)时间字段的CURRENT_TIMESTAMP默认值丢失

解决方案:

-- 从mysql默认表information_schema中获取默认为CURRENT_TIMESTAMP列的信息
SELECT TABLE_NAME,column_name,column_default,extra FROM information_schema.columns
WHERE  table_schema = 'bigdata-web' and column_default is not null AND column_default = 'CURRENT_TIMESTAMP';

-- 拼接所有时间字段默认为CURRENT_TIMESTAMP的alter 语句,提取到脚本中执行
SELECT
CONCAT("ALTER TABLE \\"bigdata-web\\".\\"",TABLE_NAME, "\\" ALTER COLUMN ","\\"",column_name,"\\"", " SET DEFAULT ", column_default, ";")
FROM information_schema.columns
WHERE
table_schema = 'bigdata-web' and column_default is not null AND column_default = 'CURRENT_TIMESTAMP';

(2)时间字段类型的on update CURRENT_TIMESTAMP,PG中无该使用方式

解决方案:

利用触发器和pg扩展函数实现更新数据时更新时间字段值

-- 安装pg扩展函数moddatetime(使用pg的useradmin用户)
create extension moddatetime;
ALTER FUNCTION "moddatetime"() OWNER TO "test用户";


-- 触发器语句:create trigger gmt_modified_timestamp_trigger before update on test_ly for each row execute procedure moddatetime(gmt_modified);
-- 查询所有设置了on update CURRENT_TIMESTAMP的列
SELECT TABLE_NAME,COLUMN_NAME,EXTRA,DATA_TYPE FROM information_schema.columns
WHERE  table_schema = 'bigdata-web' and  column_default is not null AND column_default != ''  AND column_default = 'CURRENT_TIMESTAMP' AND EXTRA = 'on update CURRENT_TIMESTAMP';

-- 拼接处理默认值为on update CURRENT_TIMESTAMP字段类型的默认值,添加触发器的语句
SELECT CONCAT("create trigger ", COLUMN_NAME, "_trigger ","before update on \\"", TABLE_NAME, "\\" for each row execute procedure moddatetime(\\"",COLUMN_NAME,"\\");") FROM information_schema.columns
WHERE  table_schema = 'bigdata-web' and  column_default is not null AND column_default != ''  AND column_default = 'CURRENT_TIMESTAMP' AND EXTRA = 'on update CURRENT_TIMESTAMP';

(3)其他默认值可以参考上面拼接处理,如字符串,数字

解决方案

-- 拼接所有默认值为字符串的alter语句
SELECT TABLE_NAME,column_name,column_default,DATA_TYPE,extra FROM information_schema.columns
WHERE  table_schema = 'bigdata-web' and  column_default is not null AND column_default != 'CURRENT_TIMESTAMP' AND DATA_TYPE = 'varchar' AND column_default != '';

SELECT 
CONCAT("ALTER TABLE \\"bigdata-web\\".\\"",TABLE_NAME, "\\" ALTER COLUMN ","\\"",column_name,"\\"", " SET DEFAULT '", column_default, "';")
FROM information_schema.columns
WHERE table_schema = 'bigdata-web' and  column_default is not null AND column_default != 'CURRENT_TIMESTAMP' AND DATA_TYPE = 'varchar' AND column_default != '';

-- 拼接默认值字段为enum的alter语句
SELECT * FROM information_schema.columns
WHERE  table_schema = 'bigdata-web' and  column_default is not null AND column_default != '' AND DATA_TYPE = 'enum';

SELECT 
CONCAT("ALTER TABLE \\"bigdata-web\\".\\"",TABLE_NAME, "\\" ALTER COLUMN ","\\"",column_name,"\\"", " SET DEFAULT '", column_default, "';")
FROM information_schema.columns
WHERE  table_schema = 'bigdata-web' and  column_default is not null AND column_default != '' AND DATA_TYPE = 'enum';

-- 查询默认值为数字的列,拼接alter语句 除了tinyint(1) TABLE_NAME,column_name,column_default,DATA_TYPE,extra 
SELECT * FROM information_schema.columns
WHERE  table_schema = 'bigdata-web' and  column_default is not null AND column_default != 'CURRENT_TIMESTAMP' AND DATA_TYPE IN('tinyint', 'int', 'bigint') AND column_default != ''
AND COLUMN_TYPE != 'tinyint(1)' and 
table_name = 't_user'
SELECT 
CONCAT("ALTER TABLE \\"bigdata-web\\".\\"",TABLE_NAME, "\\" ALTER COLUMN ","\\"",column_name,"\\"", " SET DEFAULT ", column_default, ";")
FROM information_schema.columns
WHERE  table_schema = 'bigdata-web' and  column_default is not null AND column_default != 'CURRENT_TIMESTAMP' AND DATA_TYPE IN('tinyit', 'int', 'bigint') AND column_default != ''
AND COLUMN_TYPE != 'tinyint(1)';

-- double
SELECT * FROM information_schema.columns
WHERE  table_schema = 'bigdata-web' and  column_default is not null AND DATA_TYPE IN('double') AND column_default != '';

SELECT 
CONCAT("ALTER TABLE \\"bigdata-web\\".\\"",TABLE_NAME, "\\" ALTER COLUMN ","\\"",column_name,"\\"", " SET DEFAULT ", column_default, ";")
FROM information_schema.columns
WHERE  table_schema = 'bigdata-web' and  column_default is not null AND DATA_TYPE IN('double') AND column_default != '';

-- decimal
SELECT * FROM information_schema.columns
WHERE  table_schema = 'bigdata-web' and  column_default is not null AND DATA_TYPE IN('decimal') AND column_default != '';

SELECT 
CONCAT("ALTER TABLE \\"bigdata-web\\".\\"",TABLE_NAME, "\\" ALTER COLUMN ","\\"",column_name,"\\"", " SET DEFAULT ", column_default, ";")
FROM information_schema.columns
WHERE  table_schema = 'bigdata-web' and  column_default is not null AND DATA_TYPE IN('decimal') AND column_default != '';

(4)MYSQL的tinyint(1)(业务代码中boolean值)转换为了int2

navicat工具转换映射时将mysql的bit(1)转换为了int2,需要处理该部分字段

-- tinyint
SELECT * FROM information_schema.columns
WHERE  table_schema = 'test-database'  AND COLUMN_TYPE = 'tinyint(1)'
ORDER BY TABLE_NAME
-- 批量转换语句拼接

3.2.2 自增id设置丢失

navicat工具转换时将自增id设置丢失了

解决方案:

(1)修改建表语句,使用SERIAL关键字

(2)修改已经创建的表的某个字段为自增

--1、在PostgreSQL当中,我们实现ID自增首先创建一个关联序列,以下sql语句是创建一个从1开始的序列:
CREATE SEQUENCE menu_id_seq START 1;
--2、设置该字段默认值nextval('menu_id_seq'::regclass)
ALTER TABLE menu ALTER COLUMN id SET DEFAULT nextval('menu_id_seq'::regclass);

四、业务代码中的语法差异转换

4.1 常见修改场景汇总

序号

场景

示例

1

mapper接口方法上使用注解编写sql语句

@SELECT("SELECT * FROM TEST")

2

mapper的xml文件中的语句

3

mybatis-plus使用的实体类相关特殊列名修改

4

mybatis-plus使用QueryWrapper条件构造相关列修改

5

代码中使用字符串拼接的sql语句

4.2 业务代码语法修改问题汇总

序号

差异关键字

差异描述

mysql示例

pg示例

1

limit

limit 0,1 改成 limit 1 offset 0

select * from t_user limit 0,1

select * from t_user limit 1 offset 0

2

字段大小写

查询字段为驼峰命名,加上双引号,select \\"startTime\\" form ...

-

-

3

ifnull

没有ifnull函数,改用COALESCE()函数,

select ifnull(avatar, 'aa'),"name" from t_user;

select COALESCE(avatar, 'aa'),"name" from t_user;

4

DATE_SUB()

没有DATE_SUB()时间计算函数,改用select now() + '1 seconds' 或 select now() + '-1 seconds'

select DATE_SUB(logintime,INTERVAL 1 DAY) from t_user;

select logintime + '1 days' from t_user;

5

别名大小写

查询字段的别名也需要用双引号包起来,select start_time as \\"startTime\\" from ...

-

-

6

正则表达式、REGEXP

正则表达式匹配,where taget_name ~ '^123$|asd'...,其中~为匹配正则表达式区分大小写,~*为不区分大小写,前面加叹号则为不匹配正则表达式如:!~

select * from t_user where name REGEXP '^adm*';

select * from t_user where name ~ '^adm*';

7

binary

mysql的where判断加上binary来区分大小写,where binary id = ‘abc’,在pg中是直接区分大小写的,将binary去掉就行

select * from t_user where binary name = 'ADMIN';

select * from t_user where name = 'ADMIN';

8

group_concat_max_len

set session group_concat_max_len=...在pg中没有,注释

-

-

9

GROUP_CONCAT()

GROUP_CONCAT()函数没有,使用array_to_string(array_agg(target_name), ',') from ... 代替

select GROUP_CONCAT(industry) from t_company group by province;

select array_to_string(array_agg(industry), ',') from t_company group by province;

10

ISNULL

ISNULL没有使用is null来进行判断

select * from t_user where ISNULL(avatar);

select * from t_user where avatar is null;

11

&&,||

&&和||没有这个符号,用and和or替换

select * from t_user where ISNULL(avatar) && realname = '日志管理员';

select * from t_user where avatar is null and realname = '日志管理员';

12

date_format()

date_format()函数用不了,换成 to_char,select to_char(create_time, 'yyyy-MM-dd hh24:mi:ss') from ...

select date_format(logintime, '%Y-%m-%d') from t_user;

select to_char(logintime, 'yyyy-MM-dd') from t_user;

13

if()

没有if()函数,改用 case when 条件 then 值 else 值 end

select if(name = 'admin', true, false) as isAdmin from t_user

select case when name = 'admin' then true else false end as isAdmin from t_user

14

FIND_IN_SET()

where FIND_IN_SET('123', user_ids) ... 使用不了,换成 where '123' = ANY(string_to_array(user_id, ',')) ...

select * from t_company where find_in_set('浙江省', address);

select * from t_company where '浙江省' = ANY(string_to_array(address, ','));

15

数字字符串比较、连表

mysql中能直接对数字和字符串进行=相等判断,pg不行,换成,'123' = cast(123 as VARCHAR) 或者 123 = cast('123' as INTEGER);或者123 = '123'::INTEGER

例如 select * from t_model_layout_task_record as r left join t_model_layout_form as f on r.formId = f.id,其中r.formId是vachar,f.id是int,这样连表是报错的,

改成:select * from t_model_layout_task_record as r left join t_model_layout_form as f on r.formId = cast(f.id as VARCHAR)

select * from t_model_layout_task_record as r left join t_model_layout_form as f on r.formId = f.id

select * from t_model_layout_task_record as r left join t_model_layout_form as f on r.formId = cast(f.id as VARCHAR)

16

SYSDATE()

没有 SYSDATE() 函数,换成 NOW()

select SYSDATE();

select now();

17

from_unixtime()

没有from_unixtime()函数,换成to_timestamp()

select from_unixtime(1673833489);

select to_timestamp(1673833489);

18

auto_increment

mysql查询information_schema.tables的auto_increment字段获取主键自增的值,而pg的information_schema.tables中不存在auto_increment。

pg通过该函数 pg_get_serial_sequence(‘库名.表名’, '自增字段名') 获取表的自增值

-

-

19

unix_timestamp()

没有unix_timestamp()函数,换成date_part('epoch', now())::integer,例如,select date_part('epoch', start_time)::integer from t_model_layout_task_record

select unix_timestamp(createtime) from t_user;

select date_part('epoch', createtime)::integer from t_user;

20

ON DUPLICATE KEY UPDATE

mysql的存在则更新写法,pg换成,

改成

INSERT INTO 表名(列1,列2...) VALUES ('值1', '值2', ...)ON CONFLICT(唯一或排除约束字段名) DO UPDATE SET 列1='值', 列2='值', ...;

如果是批量插入的话改成:

INSERT INTO 表名(列1,列2...) VALUES ('值1', '值2', ...)ON CONFLICT(唯一或排除约束字段名) DO UPDATE SET 列1=excluded.列1, 列2=excluded.列2, ...;

需要注意的是:

唯一或排除约束字段名必须是一个唯一索引或唯一联合索引,如果填写多个唯一索引则不生效,如果要生效的话,必须把他们建立成一个唯一联合索引。

insert into t_user(id,name,logintime,PASSWORD,createuser,updateuser) values(1,'test',now(),'test',1,1) on duplicate key update logintime = values(logintime);

insert into t_user(id,name,logintime,"PASSWORD",createuser,updateuser) values(1,'test',now(),'test',1,1) ON CONFLICT(id) DO UPDATE SET logintime=excluded.logintime;

21

instr()

没有instr()函数,改成like。例:

SELECT * FROM user WHERE INSTR(username,'2')>0

SELECT * FROM user WHERE username like '%2%'

select * from t_user where INSTR(name,'a')>0

select * from t_user where name like '%a%'

将 MySql 转换为 PostgreSQL

【中文标题】将 MySql 转换为 PostgreSQL【英文标题】:Convert MySql to PostgreSQL 【发布时间】:2010-11-27 01:23:14 【问题描述】:

我想从 MySQL 迁移到 PostgreSQL,这还不是一个实时站点,即将推出,并且架构在 MySQL 中已经准备就绪,但考虑到业务模型,我想要一个更好的可扩展数据库,因此要将 MySQL 转换为PostgreSQL,我是否需要逐个表手动完成,或者是否有我可以使用的 GUI 工具(或脚本)来自动转换表/文件值?

我的应用是用 PHP 编写的。

【问题讨论】:

我没有迁移到 PostgreSQL 的经验,所以我不能肯定,但在即将启动时切换数据库听起来有点糟糕。 是的,但最好在我上线之前立即进行切换,而不是在我拥有实时用户数据后进行切换,并且可能会遇到停机时间/问题。 我希望你有一个非常好的测试套件。 MySQL 往往比 PostgreSQL 更宽容/DWIPM(做我可能的意思)。 目前都是手动测试。逐页浏览,查看所有数据都正确记录在后端,没有任何问题。 使用 GROUP BY 测试任何查询 -- PostgreSQL 不支持 MySQL's hidden columns "feature"。还有许多其他细微的差异——更换数据库供应商是一项非常艰巨的任务。 【参考方案1】:

唯一为我做的工作是Navicat Premium,你可以获取 Navicat 并免费使用 14 天,这足以进行 DB 迁移...

只需创建2个连接(一个源mysql,另一个目标postgresql),然后选择Tools -> Data Transfer,就可以了!

我尝试过的其他工具: 使用 mysqldump 转储为与 postgresql 兼容 - 不起作用,因为 ansi sql - 不起作用,尝试 pgload - 不起作用,尝试使用 https://github.com/AnatolyUss/nmig Nmig 正确创建表,但数据丢失。

【讨论】:

你拯救了这一天。谢谢!一切都非常简单易行 谢谢!这是在 Windows 上唯一对我有用的东西【参考方案2】:
    我同意 Pekka 的观点:如果您即将推出,那么您最不想做的就是切换数据库系统。 如果幸运并且能够从当前数据库中导出符合要求的 SQL,Postgres 会理解它,并且几乎不需要额外的工作。可以导出 SQL 以重新创建数据库的结构及其表的内容。

【讨论】:

【参考方案3】:

MySQL 是一个高度可扩展的数据库,被互联网上一些最大和最活跃的网站所使用。我会有一些非常好的测试基准,表明 Postgres 在切换之前会给你带来显着的优势。

【讨论】:

看看这个:mysqlperformanceblog.com/2006/11/30/…

以上是关于超全mysql转换postgresql数据库方案的主要内容,如果未能解决你的问题,请参考以下文章

使用navicat将mysql数据转换为postgresql

如何使用 Postgresql 将行转换为列?

使用navicat将mysql数据转换为postgresql

使用navicat将mysql数据转换为postgresql

如何将 Postgresql 布尔值转换为 MySQL Tinyint?

如何将此 postgresql 滞后语句移植到 mysql?

(c)2006-2024 SYSTEM All Rights Reserved IT常识