全是坑!!从 MySQL 到 PostgreSQL

0、前言

原项目框架 SpringBoot + MybatisPlus + Mysql

1、切换流程

1.1、项目引入postgresql驱动包

由于我们要连接新的数据库,理所当然的要引入该数据库的驱动包,这与mysql驱动包类似

<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
</dependency>
1.2、修改jdbc连接信息

之前用的是mysql协议,现在改成postgresql连接协议

spring:
  datasource:
    # 修改驱动类
    driver-class-name: org.postgresql.Driver
    # 修改连接地址
    url: jdbc:postgresql://数据库地址/数据库名?currentSchema=模式名&useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=false

postgres相比mysql多了一层模式的概念, 一个数据库下可以有多个模式。 这里的模型名等价于以前的mysql的数据库名。如果不指定默认是public。

这时切换流程基本就改造完了,无非就是代码修改下连接信息。但是你以为到这就结束了?

一堆坑还在后面呢,毕竟是两个完全不同数据库在语法层面还有很多差别,接下来就是修改代码里的sql语法踩坑

2、踩坑记录

2.1、TIMESTAMPTZ类型与LocalDateTime不匹配

异常信息:

PSQLException: Cannot convert the column of type TIMESTAMPTZ to requested type java.time.LocalDateTime.

如果postgres表的字段类型是TIMESTAMPTZ ,但是java对象的字段类型是LocalDateTime, 这时会无法转换映射上。postgres表字段类型应该用timestamp 或者 java字段类型用Date

2.2、参数值不能用双引号

错误例子:

 WHERE name = "jay"   ===>    WHERE name = 'jay'

这里参数值"jay" 应该改成单引号 'jay'

2.3、字段不能用``包起来

错误例子

 WHERE `name` = 'jay'  ==>    WHERE name = 'jay'

这里的字段名name不能用``选取

2.4、json字段处理语法不同
-- mysql语法: 
WHERE keywords_json->'$.name' LIKE CONCAT('%', ?, '%')

-- postgreSQL语法:
WHERE keywords_json ->>'name' LIKE CONCAT('%', ?, '%')

获取json字段子属性的值mysql是用 -> '$.xxx'的语法去选取的, 而 postgreSQL 得用 ->>'xx' 语法选择属性

2.5、convert函数不存在

postgreSQL没有convert函数,用CAST函数替换

-- mysql语法: 
select convert(name, DECIMAL(20, 2))

-- postgreSQL语法:
select CAST(name as DECIMAL(20, 2))
2.6、force index 语法不存在
-- mysql语法
select xx FROM user force index(idx_audit_time)

mysql可以使用force index强制走索引, postgres没有,建议去掉

2.7、ifnull 函数不存在

postgreSQL没有ifnull函数,用COALESCE函数替换

异常信息

cause: org.postgresql.util.PSQLException: ERROR: function ifnull(numeric, numeric) does not exist
2.8、date_format 函数不存在

异常信息

Cause: org.postgresql.util.PSQLException: ERROR: function date_format(timestamp without time zone, unknown) does not exist

postgreSQL没有date_format函数,用to_char函数替换

替换例子:

// %Y => YYYY 
// %m  =>   MM
// %d   =>  DD
// %H => HH24
// %i => MI
// %s => SS
to_char(time,'YYYY-MM-DD') => DATE_FORMAT(time,'%Y-%m-%d')
to_char(time,'YYYY-MM') => DATE_FORMAT(time,'%Y-%m')
to_char(time,'YYYYMMDDHH24MISS') => DATE_FORMAT(time,'%Y%m%d%H%i%s')
2.9、group by语法问题

异常信息

Cause: org.postgresql.util.PSQLException: ERROR: column  "r.name" must appear in the GROUP BY clause or be used in an  aggregate function

postgreSQL 的 selectd的字段必须是group by的字段里的 或者使用了聚合函数。mysql则没有这个要求,非聚合列会随机取值

错误例子

select name, age, count(*)
from user 
group by age, score

这时 select name 是错误的, 应为group by里没有这个字段,要么加上,要么变成select min(name)

2.10、事务异常问题

异常信息

# Cause: org.postgresql.util.PSQLException: ERROR: current transaction is aborted, commands ignored until end of transaction block

; uncategorized SQLException; SQL state [25P02]; error code [0]; ERROR: current transaction is aborted, commands ignored until end of transaction block; nested exception is org.postgresql.util.PSQLException: ERROR: current transaction is aborted, commands ignored until end of transaction block

Postgres数据库中,同一事务中如果某次数据库操作中出错的话,那这个事务以后的数据库操作都会出错。正常来说不会有这种情况,但是如果有人去捕获了事务异常后又去执行数据库操作就会导致这个问题。mysql貌似不会有这个问题

下面就是错误的代码例子:靠异常去走逻辑。解决办法就是不要靠数据库的异常去控制逻辑,手动判断。

2.11 类型转换异常 (大头)

这个可以说是最坑的, 因为mysql是支持自动类型转换的。在表字段类型和参数值之间如果类型不一样也会自动进行转换。而postgreSQL是强数据类型,字段类型和参数值类型之间必须一样否则就会抛出异常。

这时候解决办法一般有两种

  • 手动修改代码里的字段类型和传参类型保证 或者 postgreSQL表字段类型,反正保证双方一一对应

  • 添加自动隐式转换函数,达到类似mysql的效果

布尔值和int类型类型转换错误

1、select查询时的转换异常信息
Cause: org.postgresql.util.PSQLException: ERROR: operator does not exist: smallint = boolean
SELECT   xx fom xx    WHERE   enable = ture

错误原因:enable字段是smallint类型查询却传了一个布尔值类型

2、update更新时的转换异常信息
Cause: org.postgresql.util.PSQLException: ERROR: column "name" is of type smallint but expression is of type boolean
update from xx set name = false  where  name = true

错误原因:在update/insert赋值语句的时候,字段类型是smallint,但是传参却是布尔值类型

解决办法:

postgres数据库添加boolean <-> smallint 的自动转换逻辑

-- 创建函数1  smallint到boolean到转换函数
CREATE OR REPLACE FUNCTION "smallint_to_boolean"("i" int2)
  RETURNS "pg_catalog"."bool" AS $BODY$
 BEGIN
  RETURN (i::int2)::integer::bool;
 END;
 $BODY$
LANGUAGE plpgsql VOLATILE
-- 创建赋值转换1
create cast (SMALLINT as BOOLEAN) with function smallint_to_boolean as ASSIGNMENT;

-- 创建函数2    boolean到smallint到转换函数
CREATE OR REPLACE FUNCTION "boolean_to_smallint"("b" bool)
  RETURNS "pg_catalog"."int2" AS $BODY$
 BEGIN
  RETURN (b::boolean)::bool::int;
 END;
 $BODY$
LANGUAGE plpgsql VOLATILE
  
-- 创建隐式转换2
create cast (BOOLEAN as SMALLINT) with function boolean_to_smallint as implicit;

如果想重来可以删除掉上面创建的函数和转换逻辑

-- 删除函数
drop function smallint_to_boolean
-- 删除转换
drop  CAST (SMALLINT as BOOLEAN)

主要不要乱添加隐式转换函数,可能导致   Could not choose a best candidate operator 异常 和 # operator is not unique 异常 就是在操作符比较的时候有多个转换逻辑不知道用哪个了,死循环了

3、PostgreSQL辅助脚本

3.1、批量修改timestamptz脚本

批量修改表字段类型 timestamptz 为 timestamp, 因为我们说过前者无法与LocalDateTime对应上

ps:

  • timestamp without time zone 就是 timestamp

  • timestamp with time zone 就是 timestamptz

DO $$
DECLARE
    rec RECORD;
BEGIN
    FOR rec IN SELECT table_name, column_name,data_type
               FROM information_schema.columns
               where table_schema = '要处理的模式名' 
               AND data_type = 'timestamp with time zone'
    LOOP
        EXECUTE 'ALTER TABLE ' || rec.table_name || ' ALTER COLUMN ' || rec.column_name || ' TYPE timestamp';
    END LOOP;
END $$;
3.2、批量设置时间默认值脚本

批量修改模式名下的所有字段类型为timestamp的并且字段名为 create_time 或者 update_time的字段的默认值为 CURRENT_TIMESTAMP

-- 注意 || 号拼接的后面的字符串前面要有一个空格
DO $$
DECLARE
    rec RECORD;
BEGIN
    FOR rec IN SELECT table_name, column_name,data_type
               FROM information_schema.columns
               where table_schema = '要处理的模式名' 
                 AND data_type = 'timestamp without time zone'
                 -- 修改的字段名
          and column_name in ('create_time','update_time')
    LOOP
         EXECUTE 'ALTER TABLE ' || rec.table_name || ' ALTER COLUMN ' || rec.column_name || ' SET DEFAULT CURRENT_TIMESTAMP;';
    END LOOP;
END $$;

4、注意事项

1、将数据表从mysql迁移postgres 要注意字段类型要对应不要变更(*

2、原先是 tinyint的就变samllint类型,不要是bool类型,有时代码字段类型可能对应不上

3、如果java字段是LocalDateTime原先mysql时间类型到postgres后不要用TIMESTAMPTZ类型

4、mysql一般用tinyint类型和java的Boolean字段对应并且在查询和更新时支持自动转换,但是postgres是强类型不支持,如果想无缝迁移postgres内部就新增自动转换的隐式函数,但是缺点是每次部署postgres后都要去执行一次脚本。

如果不想这样,只能修改代码的所有表对象的字段类型和传参类型保证与postgres数据库的字段类型对应,但是有些依赖的框架底层自己操作数据库可能就无法修改源码了,只能修改数据库表字段类型了

<think>好的,我现在需要解决用户的问题:在将MySQL数据结构同步到PostgreSQL时,如何避免字段名需要加双引号的问题,避免大小写敏感导致的错误。首先,我得回顾用户之前的提问和我的回答,确保这次回答能连贯且覆盖用户的需求。 用户之前的问题是关于PostgreSQL中字段需要加双引号才能查询的情况,我解释了大小写敏感的根源,并给出了规范命名、修改现有对象、统一查询写法等解决方案。现在用户的新问题是如何在数据同步过程中避免这个问题,特别是从MySQL迁移到PostgreSQL时。 首先,我需要明确MySQLPostgreSQL在标识符处理上的差异。MySQL默认是不区分大小写的,而PostgreSQL默认将未加引号的标识符换为小写,但保留带引号的大小写。因此,当从MySQL迁移时,如果MySQL的表或字段名有大写字母,PostgreSQL会要求使用双引号,否则会报错。 接下来,用户的需求是避免在同步后的PostgreSQL中需要加双引号。这意味着需要确保迁移后的PostgreSQL中的标识符都是小写的,或者符合PostgreSQL的大小写处理规则。 我需要考虑几种可能的解决方案: 1. **同步工具配置**:使用数据同步工具时,检查是否有选项可以强制将标识符换为小写。例如,pgLoader有这样的配置选项,或者在ETL工具中设置换规则。 2. **预处理MySQL结构**:在迁移前,将MySQL中的表名和字段名统一换为小写,这样在PostgreSQL中就不需要引号了。这可能涉及到修改MySQL的结构,或者在导出时处理DDL语句。 3. **使用迁移工具自动处理**:一些数据库迁移工具能够自动处理大小写问题,例如在生成PostgreSQL的DDL时自动换为小写并去掉引号。 4. **自定义脚本换**:如果使用自定义脚本进行迁移,可以在生成PostgreSQL的DDL时,将标识符换为小写,并确保不使用双引号。 然后,我需要参考用户提供的引用内容。用户提到单引号用于值,双引号用于关键字,比如表名和字段名,这与之前的回答一致。因此,在迁移过程中,必须确保PostgreSQL的DDL不使用双引号包裹标识符,或者标识符本身是小写的。 另外,用户引用中还提到了数据类型换的方法,这可能与数据迁移时的列类型处理相关,但当前问题主要集中在字段名的大小写处理上,所以暂时可以忽略数据类型换的部分。 接下来,我需要考虑用户可能没有明确提到的深层需求。例如,用户可能希望迁移过程尽可能自动化,不需要手动修改每个字段名;或者用户可能希望保留原有的字段名大小写,但在查询时不需要使用双引号。但根据用户的问题,主要目标是在同步后避免使用双引号,因此最佳方案是确保迁移后的字段名都是小写,且不加双引号。 可能的解决方案步骤: 1. **导出MySQL结构并换为小写**:使用工具或脚本导出MySQL的表结构,并将所有表名和字段名换为小写,同时移除任何引号。 2. **使用支持大小写换的同步工具**:例如pgLoader,配置它在导入时自动将标识符换为小写,不添加双引号。 3. **修改PostgreSQL的DDL**:在创建表时,确保所有标识符都是小写且不加双引号,这样查询时就不需要引号了。 4. **测试验证**:在迁移后,检查PostgreSQL中的表结构,确认字段名是小写,并且可以不加双引号查询。 可能遇到的问题: - MySQL中有区分大小写的字段名,例如`FirstName`和`firstname`被视为不同,但在迁移到PostgreSQL时会被换为小写,导致冲突。需要检查是否有这样的情况,并处理冲突。 - 某些工具可能在导出时自动添加双引号,需要确保工具配置正确,不添加双引号。 需要提到的工具和具体配置,例如pgLoader的配置选项,或者使用sed/awk脚本处理SQL文件中的标识符。 此外,用户可能需要批量处理现有的迁移任务,或者设置自动化的流程,因此在回答中应提供可扩展的方法,例如脚本示例或工具推荐。 最后,确保回答结构清晰,分步骤说明,并给出具体的命令或配置示例,方便用户操作。同时,链接到之前的相关问题,如批量修改字段名的方法,以提供更多参考。</think>### MySQLPostgreSQL数据结构同步避坑指南 针对MySQLPostgreSQL标识符大小写规则差异导致的字段加双引号问题,提供以下系统性解决方案: --- #### 一、核心解决思路 **目标**:在同步过程中**强制统一标识符为全小写**,避免PostgreSQL强制要求双引号。需从**数据结构定义**和**数据传输工具配置**两个层面入手。 --- #### 二、同步工具配置方案 ##### 方案1:使用pgLoader工具(推荐) pgLoader支持自动换标识符大小写,通过配置文件指定换规则: ```conf LOAD DATABASE FROM mysql://user:pass@mysql_host/dbname INTO postgresql://user:pass@pg_host/dbname WITH include drop, create tables, reset sequences, schema only, alter table names to lowercase, alter column names to lowercase; ``` > 关键参数说明: > - `alter table names to lowercase`:表名小写 > - `alter column names to lowercase`:字段名小写 > 效果:生成的PostgreSQL表结构自动使用全小写标识符[^1] ##### 方案2:使用AWS DMS/Schema Conversion Tool 1. 在AWS Database Migration Service中配置换规则: ```sql -- 编辑换规则JSON "TransformationRules": "[ { \"rule-type\": \"transform-case\", \"schema-name\": \"%\", \"table-name\": \"%\", \"object-locator\": { \"table-name\": \"%\" }, \"transform-rule\": \"lowercase\" } ]" ``` 2. 同步完成后验证表名和字段名均为小写 --- #### 三、手动预处理方案 ##### 步骤1:导出MySQL表结构 ```bash mysqldump -u root -p --no-data dbname > schema.sql ``` ##### 步骤2:换标识符为小写 使用`sed`命令批量处理: ```bash # 换表名 sed -E 's/CREATE TABLE `([A-Za-z]+)`/CREATE TABLE \L\1/g' schema.sql > schema_lower.sql # 换字段名 sed -E 's/`([A-Za-z_]+)`/\L\1/g' schema_lower.sql > final_schema.sql ``` ##### 步骤3:导入PostgreSQL ```bash psql -U postgres -d dbname -f final_schema.sql ``` --- #### 四、ORM框架适配方案 在应用层通过ORM工具强制小写映射: ```python # SQLAlchemy示例 class User(Base): __tablename__ = 'user' id = Column('id', Integer, primary_key=True) # 显式指定列名小写 created_at = Column('created_at', TIMESTAMP) ``` --- #### 五、验证与调试 1. **检查PostgreSQL系统表** ```sql -- 查看字段实际存储名 SELECT column_name FROM information_schema.columns WHERE table_name = 'your_table'; ``` > 正确结果应显示全小写字段名,如`user_id` 2. **测试未加双引号查询** ```sql SELECT created_at FROM user WHERE id = 1; ``` 若返回结果正常,则同步成功 --- ### 最佳实践对比表 | 方法 | 适用场景 | 优点 | 缺点 | |------|---------|------|------| | pgLoader | 全库迁移 | 自动化程度高,支持复杂类型换 | 需学习配置语法 | | 手动预处理 | 少量表迁移 | 完全控制输出结果 | 易出错,不适合大型系统 | | ORM映射 | 应用层适配 | 不修改数据库结构 | 需修改代码,维护成本高 | --- ### 扩展优化建议 1. **同步后批量修复工具** 使用`psql`执行批量修改: ```sql -- 生成所有字段重命名语句 SELECT 'ALTER TABLE '||table_name||' RENAME COLUMN "'||column_name||'" TO '||lower(column_name)||';' FROM information_schema.columns WHERE table_schema = 'public' AND column_name ~ '[A-Z]'; ``` > 执行输出结果可自动修改所有带大写字母的字段名[^2] 2. **监控大小写敏感查询** 在`postgresql.conf`中启用日志: ```conf log_statement = 'all' log_line_prefix = '%t [%p]: ' ``` 通过日志分析触发双引号报错的SQL语句 --- ### 相关问题 1. 如何将MySQL的JSON类型字段正确迁移到PostgreSQL? 2. 在PostgreSQL中如何实现类似MySQL的`ON UPDATE CURRENT_TIMESTAMP`功能? 3. 使用pgLoader迁移数据时如何处理自增主键冲突?
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值