ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

国产化环境PostgreSQL时区报错Asia/Beijing的解决与运维

国产化环境PostgreSQL时区报错Asia/Beijing的解决与运维 中标麒麟上启动 PostgreSQL 突然抛出一句invalid value for parameter timeZone: Asia/Beijing第一次遇到的人十有八九会愣一下明明配置写的是北京系统也能正常显示时间数据库凭什么不认这个问题我在国产化项目交付现场踩过两次第一次花了半天排查第二次三分钟定位。实际上这不是什么复杂故障就是 PostgreSQL 的时区识别机制和系统时区数据之间没对上。这篇文章把报错原因、解决步骤、连带坑位一次讲透照着做就能收工顺便把国产化环境下几个高频配套问题一起处理掉。1. 报错现场先搞清楚自己属于哪种触发场景1.1 三种最常见的配置触发方式同样是这条报错不同人遇到的触发路径不一样但根源基本一致。我在现场见到最多的三种场景建议你对号入座。第一种直接在postgresql.conf里写死了时区参数。很多人习惯在配置里加上一行timezone Asia/Beijing然后执行pg_ctl restart或者systemctl restart postgresql启动阶段直接报错数据库起不来。第二种没动数据库配置文件但应用侧 JDBC 连接串里带了时区参数。连接串写成这样jdbc:postgresql://127.0.0.1:5432/mydb?timeZoneAsia/Beijing这种报错通常不在数据库服务端日志里而是应用启动后第一次获取连接时报出来经典表现是连接池初始化异常错误堆栈里就带着invalid value for parameter timeZone。第三种用 Docker 跑 PostgreSQL启动容器时给环境变量传了TZAsia/Beijing。严格说PG 官方镜像的TZ环境变量走的是容器系统时区设置理论上不会直接把值塞给 PostgreSQL 的timezone参数但如果你的启动脚本里写了类似-c timezoneAsia/Beijing的追加参数或者自定义配置覆盖了postgresql.conf一样会触发。1.2 拆分这行报错到底在说什么invalid value for parameter timeZone: Asia/Beijing这句话信息量其实挺大。timeZone是 PostgreSQL 的运行时配置参数控制数据库内部timestamptz类型数据的展示和计算时区。当服务器解析这个参数时需要在系统时区数据库里找到对应的时区定义找不到就直接抛 invalid value而不是退回到某个默认值。换句话说PostgreSQL 不认为Asia/Beijing是一个合法时区标识符。注意它用的是不合法这个判断跟不常见或者拼写错误不是一个概念。即使你敲成asia/beijing或Asia/beijing这种大小写变体只要系统时区库里没有对应条目结果一样报错。先把这个问题复现出来进入 psql 或者用pgAdmin执行SELECT name FROM pg_timezone_names WHERE name LIKE Asia/%;如果你在结果里看到了Asia/Shanghai但怎么翻都找不到Asia/Beijing那问题定位就基本结束了不是 PostgreSQL 坏了是它认的这个时区名字在系统里压根不存在。2. 为什么 PostgreSQL 死活不认 Asia/Beijing2.1 PostgreSQL 的时区数据其实来自操作系统PostgreSQL 本身不内置完整的世界时区库它依赖操作系统提供的时区数据文件在 Linux 上就是/usr/share/zoneinfo/目录。这个目录由tzdata软件包维护PostgreSQL 启动时读取该目录把所有合法的时区名加载进来形成一个白名单。pg_timezone_names视图查询到的内容本质就是系统时区数据库的映射结果。所以问题就变成了你系统里的tzdata包到底有没有提供Asia/Beijing这个条目。中标麒麟系统基于 CentOS/RHEL 7 系列内核和软件源体系它默认安装的tzdata版本基本和 CentOS 7 同步。而Asia/Beijing在较新版本的时区数据库里已经被移除只剩Asia/Shanghai作为 UTC8 的正式代表。具体从哪个版本起删除的不同发行版有差异但中标麒麟的软件源里通常没有这个条目这是个很现实的事实。你可以直接验证ls /usr/share/zoneinfo/Asia/如果你的输出里只有Shanghai、Urumqi之类没有Beijing那就妥了。也可以查找整个时区库find /usr/share/zoneinfo/ -name *Beijing*结果如果为空说明系统层面就提供不了这个时区标识PostgreSQL 报错也就不难理解了。2.2 IANA 时区数据库命名规则里的门道时区数据库的正式名称叫 IANA Time Zone Database也叫 tzdata。它有一套命名约定格式是地区/城市比如Asia/Shanghai、America/New_York。这套名字不只是为了好看它有严格的规则城市必须能代表该时区内一个稳定、有明确历史时间记录的地点。北京虽然在时区上和上海同属东八区但 tzdata 选用的是上海作为东八区的代表城市。这个逻辑听着有点绕但在时区数据库演进过程中Asia/Beijing曾经短暂存在过后来因为城市代表性和历史数据完整性问题被官方从标准库里移除了。更关键的是移除后没有任何发行版会主动把它加回去因为大家都跟着上游 tzdata 走。所以你哪怕换个 Linux 发行版很大概率也查不到这个时区名。2.3 别碰 CST、PRC、GMT8 这几个写法踩过这个坑以后很多人会想那我写PRC行不行写GMT8行不行这些写法都有隐患。CST是最迷惑人的缩写它可能是美国中部时间、中国标准时间、古巴标准时间的共用缩写。PostgreSQL 里输入CST大多数情况下会解析成美国中部时间跟北京时间差了 14 个小时属于典型的改了等于没改还更糟。PRC虽然在很多 Linux 系统里能被解析成东八区但这个标识比较老属于历史遗留写法在部分 tzdata 版本和新版本 PostgreSQL 里会报错或者行为不一致不建议用。GMT8这类带偏移量的写法PostgreSQL 的某些版本里无法直接解析因为它要求的是带冒号的偏移格式比如08:00。08:00确实能用但它把时区固化为固定偏移绕开了夏令时等规则如果哪天你的系统标准变了这个配置不会跟着变。最稳妥、最标准、PostgreSQL 官方文档明确推荐的写法是timezone Asia/Shanghai不要纠结上海还是北京对东八区来说两者代表的偏移和历史规则完全一致。3. 实操解决三种方案按优先级来3.1 方案一直接改 PostgreSQL 参数最快止血如果你的数据库还能启动优先用ALTER SYSTEM改这种方式会把配置写入postgresql.auto.conf优先级低于postgresql.conf但高于默认值使用起来最省心不用手动去翻配置文件。ALTER SYSTEM SET timezone Asia/Shanghai; SELECT pg_reload_conf();执行完以后在同一个会话里验证SHOW timezone;正常会输出Asia/Shanghai。再用一句 SQL 验证实际时间SELECT now();拿输出时间和北京时间对一下一致就说明生效了。如果ALTER SYSTEM不方便执行或者你想改得更彻底直接编辑postgresql.conf找到timezone那一行改成Asia/Shanghai然后重启或重载配置。注意ALTER SYSTEM改完以后如果你再编辑postgresql.conf里面加一个timezone两者的优先级关系可能会让你混淆实际规则是postgresql.conf里的设置会覆盖ALTER SYSTEM写入的postgresql.auto.conf。所以两处别同时配否则改完可能莫名其妙不生效。3.2 方案二改系统时区从根上消除隐患很多项目不止数据库在用时区中间件、应用服务都要看系统时间。只改 PostgreSQL 参数属于局部修复更推荐把系统时区也统一成Asia/Shanghai保证整个环境的时间口径一致。中标麒麟里执行timedatectl set-timezone Asia/Shanghai验证一下date -R timedatectl输出里能看到0800和Asia/Shanghai字样就正常了。如果你是那种精简安装、连timedatectl都没有的环境可以直接用软链接方式ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime改完系统时区后重启 PostgreSQL让它重新加载系统时区数据systemctl restart postgresql再到 psql 里确认SHOW timezone;。注意SHOW timezone显示的是数据库会话级参数不一定是系统时区如果之前设置过timezone UTC之类的内容改系统时区并不会自动覆盖它。所以正确姿势是系统时区和数据库时区都检查两边统一到Asia/Shanghai。3.3 方案三Docker 部署 PostgreSQL 的时区设置现在很多环境用容器化部署centos7 docker 安装 postgres也是高频搜索词。Docker 方式处理时区有自己的特性分两种情况。如果还没启动容器在docker run时把TZ环境变量传进去docker run -d \ --name postgres \ -e TZAsia/Shanghai \ -e POSTGRES_PASSWORDyourpassword \ -p 5432:5432 \ postgres:12TZ环境变量会修改容器内系统时区PostgreSQL 在容器里读取到的时区数据也会随之变化。如果你用docker-compose对应配置是services: postgres: image: postgres:12 environment: - TZAsia/Shanghai - POSTGRES_PASSWORDyourpassword ports: - 5432:5432 volumes: - pgdata:/var/lib/postgresql/data如果容器已经跑起来了又不想重建进容器操作docker exec -it postgres bash ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime然后重启容器让数据库服务重新加载时区配置。这里有个关键提醒容器里的数据目录是通过 volume 挂载的但postgresql.conf在数据目录里。如果你的timezone参数在之前已经写成了Asia/Beijing光改容器系统时区不够还得按方案一把参数改掉。Docker 镜像默认的timezone参数是空值不设置时跟系统走但你的启动脚本如果追加过-c timezone那就会被强制覆盖容器环境变量救不了你。4. 时区问题搞定后紧接着要面对的四个现实坑4.1 时间还是差 8 小时先分清 timestamp 和 timestamptz改完时区后应用反馈时间还是不对这是另一个高频问题。本质上是timestamp和timestamptz的区别没搞清楚。timestamp without time zone类型存储的就是字面时间不带时区语义比如你存2024-06-01 10:00:00不管数据库时区变量怎么变查出来都是2024-06-01 10:00:00。而timestamptz内部存储的是 UTC 时刻展示时会根据当前会话时区转换。所以在改完timezone参数后如果你发现历史数据整体偏移了 8 小时先查表结构里到底是哪种类型。确认方法SELECT column_name, data_type FROM information_schema.columns WHERE table_name 你的表名;如果字段类型是timestamp without time zone时间不变是正常的如果应用之前一直按北京时间展示那是应用层做了额外转换跟数据库没关系。如果字段类型是timestamptz改时区后时间显示变化是预期行为因为存储的 UTC 时刻没变只是展示时区变了。还有一种情况是应用连接串里单独设置了sessionTimeZone它会把数据库会话的时区临时覆盖掉。排查这种问题去连接配置里找有没有类似?timeZoneAsia/Beijing或者sessionTimeZoneAsia/Shanghai的参数有的话改成标准写法。4.2 视图导出schema 下所有视图要怎么完整导出时区问题处理完业务继续走马上会碰到下一个需求导出指定 schema 下的视图。这句话在热搜里排在挺前面说明大家做完基础环境修复后就开始琢磨数据迁移和备份了。PostgreSQL 的pg_dump导 schema 结构是支持的命令示例pg_dump -U postgres -d mydb -n myschema -s myschema_schema.sql-n指定 schema-s只导结构不导数据。但这个命令导出的是整个 schema 下的所有对象包括表、函数、序列、视图。如果你只想导视图标准pg_dump没有直接按对象类型过滤的参数。有个替代思路查询information_schema.views拿到视图清单然后从完整 schema 导出文件里筛选出CREATE VIEW语句。更简单一点的是用元数据直接生成重建视图的 SQLSELECT CREATE OR REPLACE VIEW || table_schema || . || table_name || AS || view_definition || ; FROM information_schema.views WHERE table_schema myschema;把这条 SQL 的输出存成.sql文件在新环境执行前注意检查视图依赖的表、扩展是否已经存在。如果视图里用了postgis之类的扩展函数目标库要先CREATE EXTENSION否则建视图会报function does not exist。4.3 国产化环境日常运维挂载光驱和重置密码中标麒麟这类系统在政务、金融项目里经常是离线环境软件包都得靠光盘或 ISO 分发所以中标麒麟如何挂载光驱一直是热门搜索。命令本身不复杂mount /dev/cdrom /mnt ls /mnt如果光驱识别不到先看设备名lsblk fdisk -l看到类似/dev/sr0的设备再执行mount /dev/sr0 /mnt用完别忘了卸载umount /mnt如果是 ISO 镜像文件用 loop 方式挂载mount -o loop /path/to/xxx.iso /mnt挂载目录里通常有Packages目录里面的 rpm 包可以直接离线安装这也是国产化环境装 PostgreSQL 依赖库的主要途径。至于中标麒麟忘记密码怎么更改重置思路跟 CentOS/RHEL 一致。重启系统在 GRUB 启动界面按e进入编辑找到linux16开头的那一行在末尾加上rd.break然后按Ctrlx启动。系统会进入紧急模式此时根文件系统是只读的先执行mount -o remount,rw /sysroot chroot /sysroot passwd root如果rd.break不行可以试试在linux16行替换成init/bin/bash同样能进到单用户环境改密码。改完执行touch /.autorelabelSELinux 环境下需要后重启。注意千万别在客户生产环境乱试先确认系统版本和备份策略。4.4 时区配置与 NTP 同步别打架最后提醒一个容易被忽略的点。改完系统时区和数据库时区后如果还出现时间不对检查一下 NTP 是否正常。时区解决的是在哪个时区显示的问题NTP 解决的是实际时间是否准确的问题。两者不是一回事但项目上经常一起出故障。查看 NTP 状态timedatectl status注意NTP synchronized那一行如果显示no说明系统没有自动同步时间那么即使时区正确时间也可能有漂移。手动同步一次ntpdate -u ntp.aliyun.com或者开启 chronysystemctl start chronyd systemctl enable chronyd数据库侧也设置一下时间同步检查SELECT now();和操作系统时间对比差异如果持续扩大优先排查 NTP 网络连通性和防火墙规则。在我处理过的国产化数据库交付项目里这类时区报错几乎成了标准开胃菜但说实话它带来的最大价值不是那几行命令而是逼着你把系统时区、数据库时区、应用连接时区三条链路全部捋清楚。Asia/Beijing这个事情本质上是标准时区命名和用户习惯之间的偏差一旦理解了 PostgreSQL 从系统加载时区数据的机制以后遇到invalid value for parameter就不会慌顺着pg_timezone_names查一遍解决方案自然就出来了。
返回列表