news 2026/4/18 3:11:26

项目终于从 MySQL 切换 PostgreSQL,踩了太多的坑

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
项目终于从 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到转换函数 CREATEORREPLACEFUNCTION"smallint_to_boolean"("i" int2) RETURNS"pg_catalog"."bool"AS $BODY$ BEGIN RETURN (i::int2)::integer::bool; END; $BODY$ LANGUAGE plpgsql VOLATILE -- 创建赋值转换1 createcast (SMALLINTasBOOLEAN) withfunction smallint_to_boolean as ASSIGNMENT; -- 创建函数2 boolean到smallint到转换函数 CREATEORREPLACEFUNCTION"boolean_to_smallint"("b"bool) RETURNS"pg_catalog"."int2"AS $BODY$ BEGIN RETURN (b::boolean)::bool::int; END; $BODY$ LANGUAGE plpgsql VOLATILE -- 创建隐式转换2 createcast (BOOLEANasSMALLINT) withfunction 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脚本

批量修改表字段类型timestamptztimestamp, 因为我们说过前者无法与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 INSELECT 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;'; ENDLOOP; END $$;

4、注意事项

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

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

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

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

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/16 15:52:24

基于YOLOv10的杂草检测系统(12种)(YOLOv10深度学习+YOLO数据集+UI界面+Python项目源码+模型)

一、项目介绍 摘要 本项目基于YOLOv10目标检测算法开发了一套高效的杂草识别系统&#xff0c;专门用于检测和分类12种常见杂草物种。系统通过深度学习技术实现了对农田杂草的精准识别&#xff0c;为精准农业和智能除草提供了技术支持。项目使用包含3319张标注图像的数据集&am…

作者头像 李华
网站建设 2026/4/9 23:53:57

PHP 8.7引入了哪些隐藏函数?99%开发者还没发现的秘密

第一章&#xff1a;PHP 8.7引入的新函数概述PHP 8.7 作为 PHP 语言持续演进的重要版本&#xff0c;引入了一系列实用且高效的新内置函数&#xff0c;旨在提升开发效率、增强类型安全并简化常见编程任务。这些函数覆盖了字符串处理、数组操作、类型判断以及异步支持等多个方面&a…

作者头像 李华
网站建设 2026/4/17 22:21:40

PHP插件开发新纪元:如何在低代码浪潮中打造不可替代的技术壁垒

第一章&#xff1a;PHP插件开发新纪元&#xff1a;低代码浪潮下的技术突围在数字化转型加速的背景下&#xff0c;PHP插件开发正迎来一场由低代码平台驱动的技术变革。传统开发模式中&#xff0c;开发者需手动编写大量重复代码以实现基础功能&#xff0c;而如今&#xff0c;低代…

作者头像 李华
网站建设 2026/3/31 23:55:35

【PHP与工业通信协议深度整合】:实现秒级数据上传的终极方案

第一章&#xff1a;PHP与工业通信协议融合的背景与意义 随着工业自动化与信息化深度融合&#xff0c;传统工业控制系统正逐步向智能化、网络化方向演进。在这一背景下&#xff0c;将广泛应用于Web开发的PHP语言引入工业通信领域&#xff0c;成为连接企业资源计划&#xff08;ER…

作者头像 李华
网站建设 2026/4/17 4:27:36

PHP温控系统部署避坑指南(5大常见故障与修复方案)

第一章&#xff1a;PHP智能家居温度控制概述在现代物联网&#xff08;IoT&#xff09;应用中&#xff0c;智能家居系统逐渐成为家庭自动化的重要组成部分。其中&#xff0c;温度控制作为核心功能之一&#xff0c;直接影响居住舒适度与能源效率。PHP 作为一种广泛使用的服务器端…

作者头像 李华
网站建设 2026/4/17 6:04:15

依图科技医疗影像分析:HeyGem生成放射科医生讲解视频

依图科技医疗影像分析&#xff1a;HeyGem生成放射科医生讲解视频 在三甲医院的放射科诊室外&#xff0c;一位患者紧皱眉头盯着手中的CT报告——“右肺上叶磨玻璃结节&#xff0c;直径约6mm”——这些术语像密码一样难以解读。他反复翻看&#xff0c;却始终无法判断这是否意味着…

作者头像 李华