PostgreSQL 连接

PostgreSQL 在国内不如 MySQL 普及,但在技术圈口碑极好。JSON 支持、数组类型、全文搜索、地理信息扩展,这些高级特性让它在复杂业务场景下表现亮眼。

驱动安装

GORM 的 PostgreSQL 驱动:

go get -u gorm.io/driver/postgres

底层依赖 github.com/lib/pq,这是 Go 社区最成熟的 PostgreSQL 驱动。

基本连接

package main

import (
    "fmt"
    "gorm.io/driver/postgres"
    "gorm.io/gorm"
)

func main() {
    dsn := "host=localhost user=gorm password=gorm dbname=gorm port=5432 sslmode=disable"
    db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
    if err != nil {
        panic("连接失败")
    }
    fmt.Println("连接成功")
}

DSN 格式

PostgreSQL 的 DSN 有两种格式。

键值对格式

host=localhost port=5432 user=gorm password=gorm dbname=gorm sslmode=disable

每个参数用空格分隔,可读性好。参数顺序无关。

URI 格式

postgres://gorm:gorm@localhost:5432/gorm?sslmode=disable

类似 MySQL 的格式,习惯 URL 风格的可以用这个。

参数详解

参数说明默认值
host数据库地址localhost
port端口5432
user用户名环境变量 USER
password密码
dbname数据库名用户名
sslmodeSSL 模式disable
TimeZone时区UTC
connect_timeout连接超时秒数0(无限制)

sslmode 选项

  • disable - 不使用 SSL
  • require - 必须使用 SSL,但不验证证书
  • verify-ca - 使用 SSL 并验证证书
  • verify-full - 使用 SSL 并验证证书和主机名

生产环境建议至少用 require

PostgreSQL 特定配置

postgres.Config 提供专属配置:

import "gorm.io/driver/postgres"

pgConfig := postgres.Config{
    DSN:                  dsn,
    PreferSimpleProtocol: true,
}

db, err := gorm.Open(&pgConfig, &gorm.Config{})

PreferSimpleProtocol

PostgreSQL 默认使用 prepared statement 协议。某些场景下(比如 PgBouncer 连接池)会有问题,设为 true 改用简单协议。

Schema 支持

PostgreSQL 有 Schema 的概念,类似命名空间。默认连接到 public schema:

dsn := "host=localhost user=gorm password=gorm dbname=gorm search_path=public"

指定其他 schema:

dsn := "host=localhost user=gorm password=gorm dbname=gorm search_path=myschema"

或者连接后动态切换:

db.Exec("SET search_path = myschema")

时区配置

PostgreSQL 存储时间时会保留时区信息。DSN 中指定时区:

dsn := "host=localhost user=gorm password=gorm dbname=gorm TimeZone=Asia/Shanghai"

Go 代码中处理时间:

type Event struct {
    ID        uint
    CreatedAt time.Time
}

event := Event{}
db.First(&event)
fmt.Println(event.CreatedAt.Local())

JSON 类型

PostgreSQL 原生支持 JSON 和 JSONB,GORM 可以直接映射:

type User struct {
    ID      uint
    Profile datatypes.JSON
}

user := User{
    Profile: datatypes.JSON(`{"name": "张三", "age": 25}`),
}
db.Create(&user)

需要导入:

import "gorm.io/datatypes"

查询 JSON 字段:

var user User
db.Where("profile->>'name' = ?", "张三").First(&user)

JSONB 支持索引,查询性能更好,推荐使用:

type User struct {
    ID      uint
    Profile datatypes.JSON `gorm:"type:jsonb"`
}

数组类型

PostgreSQL 支持数组,GORM 通过 lib/pq 支持:

import "github.com/lib/pq"

type Article struct {
    ID    uint
    Tags  pq.StringArray `gorm:"type:text[]"`
}

article := Article{
    Tags: pq.StringArray{"Go", "GORM", "PostgreSQL"},
}
db.Create(&article)

查询数组元素:

db.Where("tags @> ARRAY[?]::text[]", "Go").Find(&articles)

自增主键

PostgreSQL 使用序列(Sequence)实现自增。GORM 默认使用 SERIAL 类型:

type User struct {
    ID   uint `gorm:"primaryKey"`
    Name string
}

生成的 SQL:

CREATE TABLE "users" ("id" SERIAL,"name" text)

手动指定序列名:

type User struct {
    ID uint `gorm:"primaryKey;autoIncrement;sequence:user_id_seq"`
}

UUID 主键

PostgreSQL 原生支持 UUID,非常适合分布式系统:

import "github.com/google/uuid"

type User struct {
    ID   uuid.UUID `gorm:"type:uuid;primaryKey;default:gen_random_uuid()"`
    Name string
}

需要先启用扩展:

CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

或者用 gen_random_uuid()(PostgreSQL 13+):

CREATE EXTENSION IF NOT EXISTS "pgcrypto";

连接池

和 MySQL 一样,需要配置连接池:

sqlDB, _ := db.DB()
sqlDB.SetMaxOpenConns(50)
sqlDB.SetMaxIdleConns(10)
sqlDB.SetConnMaxLifetime(time.Hour)

PostgreSQL 默认 max_connections 是 100,连接池配置要留余量。

已有连接复用

import (
    "database/sql"
    "gorm.io/driver/postgres"
)

func ConnectFromExisting(sqlDB *sql.DB) (*gorm.DB, error) {
    return gorm.Open(postgres.New(postgres.Config{
        Conn: sqlDB,
    }), &gorm.Config{})
}

常见错误

角色不存在

ERROR: role "gorm" does not exist

先创建角色:

CREATE ROLE gorm WITH LOGIN PASSWORD 'gorm';

数据库不存在

ERROR: database "gorm" does not exist

创建数据库:

CREATE DATABASE gorm OWNER gorm;

SSL 连接失败

ERROR: no pg_hba.conf entry for host

检查 pg_hba.conf 配置,允许对应主机的连接。开发环境可以临时改 sslmode=disable

扩展不存在

ERROR: could not open extension control file

UUID 等扩展需要先安装:

CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

与 MySQL 的差异

从 MySQL 迁移到 PostgreSQL,注意这些差异:

特性MySQLPostgreSQL
字符串引号单引号或双引号只能用单引号
标识符引号反引号 `双引号 "
布尔类型TINYINT(1)BOOLEAN
自增AUTO_INCREMENTSERIAL/IDENTITY
字符串连接CONCAT()||
限制结果LIMITLIMIT + OFFSET

GORM 会处理大部分差异,但写原生 SQL 时要注意。

小结

PostgreSQL 功能强大,但配置细节和 MySQL 有差异。JSON、数组、UUID 这些特性用好了能简化很多业务逻辑。下一章看轻量级的 SQLite。