PostgreSQL 与 MySQL 的快速启动与连接

前言

在本地开发和测试时,经常需要一个临时的数据库环境。使用 Docker 可以在几秒钟内启动一个干净的数据库实例,用完即删,不会污染本地环境。

这篇文章将详细介绍如何使用 Docker 启动 PostgreSQL 和 MySQL,包括各种配置选项、数据持久化、连接方式等。


PostgreSQL

快速启动

最简单的方式,一行命令启动:

docker run -d \
    --name postgres-test \
    -e POSTGRES_PASSWORD=postgres \
    -p 5432:5432 \
    postgres:15-alpine

参数说明:

参数 说明
-d 后台运行
--name postgres-test 容器名称
-e POSTGRES_PASSWORD=postgres 设置密码(必填)
-p 5432:5432 端口映射
postgres:15-alpine 使用 Alpine 版本(更小)

完整配置启动

docker run -d \
    --name postgres-dev \
    -e POSTGRES_USER=myuser \
    -e POSTGRES_PASSWORD=mypassword \
    -e POSTGRES_DB=mydb \
    -e PGDATA=/var/lib/postgresql/data/pgdata \
    -e TZ=Asia/Shanghai \
    -p 5432:5432 \
    -v postgres_data:/var/lib/postgresql/data \
    --restart unless-stopped \
    postgres:15-alpine

环境变量说明

环境变量 说明 默认值
POSTGRES_USER 超级用户名 postgres
POSTGRES_PASSWORD 超级用户密码 必填
POSTGRES_DB 默认创建的数据库 与用户名相同
PGDATA 数据目录 /var/lib/postgresql/data
POSTGRES_INITDB_ARGS initdb 额外参数 -
POSTGRES_HOST_AUTH_METHOD 认证方式 scram-sha-256

连接数据库

方式一:使用 psql 命令行

# 进入容器内使用 psql
docker exec -it postgres-dev psql -U myuser -d mydb

# 在容器内执行 SQL
docker exec -it postgres-dev psql -U myuser -d mydb -c "SELECT version();"

方式二:从宿主机连接

首先安装 psql 客户端:

# macOS
brew install libpq
brew link --force libpq

# Ubuntu
sudo apt-get install postgresql-client

# CentOS
sudo yum install postgresql

然后连接:

psql -h localhost -p 5432 -U myuser -d mydb
# 输入密码:mypassword

方式三:使用连接字符串

# 标准格式
psql "postgresql://myuser:mypassword@localhost:5432/mydb"

# 带 SSL(本地测试通常不需要)
psql "postgresql://myuser:mypassword@localhost:5432/mydb?sslmode=disable"

方式四:代码连接示例

Go

package main

import (
    "database/sql"
    "fmt"
    "log"

    _ "github.com/lib/pq"
)

func main() {
    // 连接字符串
    connStr := "host=localhost port=5432 user=myuser password=mypassword dbname=mydb sslmode=disable"
    
    // 或使用 URL 格式
    // connStr := "postgresql://myuser:mypassword@localhost:5432/mydb?sslmode=disable"

    db, err := sql.Open("postgres", connStr)
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    // 测试连接
    err = db.Ping()
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println("Connected to PostgreSQL!")

    // 查询版本
    var version string
    err = db.QueryRow("SELECT version()").Scan(&version)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("Version:", version)
}

Python

import psycopg2

# 连接参数
conn = psycopg2.connect(
    host="localhost",
    port=5432,
    database="mydb",
    user="myuser",
    password="mypassword"
)

# 或使用连接字符串
# conn = psycopg2.connect("postgresql://myuser:mypassword@localhost:5432/mydb")

cursor = conn.cursor()
cursor.execute("SELECT version()")
print(cursor.fetchone())

cursor.close()
conn.close()

Node.js

const { Pool } = require('pg');

const pool = new Pool({
    host: 'localhost',
    port: 5432,
    database: 'mydb',
    user: 'myuser',
    password: 'mypassword',
});

pool.query('SELECT version()', (err, res) => {
    console.log(res.rows[0]);
    pool.end();
});

初始化脚本

可以在启动时自动执行 SQL 脚本:

# 创建初始化脚本目录
mkdir -p ./init-scripts

# 创建初始化 SQL
cat > ./init-scripts/01-init.sql << 'EOF'
-- 创建额外的数据库
CREATE DATABASE testdb;

-- 创建用户
CREATE USER testuser WITH PASSWORD 'testpass';

-- 授权
GRANT ALL PRIVILEGES ON DATABASE testdb TO testuser;

-- 连接到 testdb 创建表
\c testdb

CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(100) UNIQUE NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

INSERT INTO users (name, email) VALUES 
    ('Alice', '[email protected]'),
    ('Bob', '[email protected]');
EOF

# 启动并挂载初始化脚本
docker run -d \
    --name postgres-init \
    -e POSTGRES_PASSWORD=postgres \
    -p 5432:5432 \
    -v $(pwd)/init-scripts:/docker-entrypoint-initdb.d \
    postgres:15-alpine

💡 /docker-entrypoint-initdb.d 目录下的 .sql.sh 文件会按字母顺序自动执行。

数据持久化

使用命名数据卷(推荐)

# 创建数据卷
docker volume create pg_data

# 启动并使用数据卷
docker run -d \
    --name postgres-persist \
    -e POSTGRES_PASSWORD=postgres \
    -p 5432:5432 \
    -v pg_data:/var/lib/postgresql/data \
    postgres:15-alpine

使用本地目录

# 创建本地目录
mkdir -p ./pgdata

# 启动(注意权限问题)
docker run -d \
    --name postgres-local \
    -e POSTGRES_PASSWORD=postgres \
    -p 5432:5432 \
    -v $(pwd)/pgdata:/var/lib/postgresql/data \
    postgres:15-alpine

备份与恢复

# 备份整个数据库
docker exec postgres-dev pg_dump -U myuser mydb > backup.sql

# 备份为压缩格式
docker exec postgres-dev pg_dump -U myuser -Fc mydb > backup.dump

# 恢复
docker exec -i postgres-dev psql -U myuser mydb < backup.sql

# 恢复压缩格式
docker exec -i postgres-dev pg_restore -U myuser -d mydb < backup.dump

查看日志

# 查看实时日志
docker logs -f postgres-dev

# 查看最近 100 行
docker logs --tail 100 postgres-dev

MySQL

快速启动

docker run -d \
    --name mysql-test \
    -e MYSQL_ROOT_PASSWORD=root123 \
    -p 3306:3306 \
    mysql:8

完整配置启动

docker run -d \
    --name mysql-dev \
    -e MYSQL_ROOT_PASSWORD=rootpassword \
    -e MYSQL_DATABASE=mydb \
    -e MYSQL_USER=myuser \
    -e MYSQL_PASSWORD=mypassword \
    -e TZ=Asia/Shanghai \
    -p 3306:3306 \
    -v mysql_data:/var/lib/mysql \
    -v $(pwd)/mysql-conf:/etc/mysql/conf.d \
    --restart unless-stopped \
    mysql:8

环境变量说明

环境变量 说明 默认值
MYSQL_ROOT_PASSWORD root 密码 必填
MYSQL_DATABASE 默认创建的数据库 -
MYSQL_USER 创建的普通用户 -
MYSQL_PASSWORD 普通用户密码 -
MYSQL_ALLOW_EMPTY_PASSWORD 允许空密码 no
MYSQL_RANDOM_ROOT_PASSWORD 随机 root 密码 no

自定义配置

创建配置文件:

mkdir -p ./mysql-conf

cat > ./mysql-conf/my.cnf << 'EOF'
[mysqld]
# 字符集
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci

# 时区
default-time-zone='+08:00'

# 性能配置
max_connections=200
innodb_buffer_pool_size=256M

# 日志
slow_query_log=1
slow_query_log_file=/var/lib/mysql/slow.log
long_query_time=2

# 允许大数据包
max_allowed_packet=64M

[client]
default-character-set=utf8mb4
EOF

连接数据库

方式一:使用 mysql 命令行

# 进入容器内使用 mysql
docker exec -it mysql-dev mysql -u myuser -pmypassword mydb

# 使用 root 用户
docker exec -it mysql-dev mysql -u root -prootpassword

# 执行单条 SQL
docker exec -it mysql-dev mysql -u myuser -pmypassword -e "SELECT version();"

方式二:从宿主机连接

安装 MySQL 客户端:

# macOS
brew install mysql-client
echo 'export PATH="/opt/homebrew/opt/mysql-client/bin:$PATH"' >> ~/.zshrc

# Ubuntu
sudo apt-get install mysql-client

# CentOS
sudo yum install mysql

连接:

mysql -h 127.0.0.1 -P 3306 -u myuser -pmypassword mydb

# 或者交互式输入密码
mysql -h 127.0.0.1 -P 3306 -u myuser -p mydb

方式三:代码连接示例

Go

package main

import (
    "database/sql"
    "fmt"
    "log"
    "time"

    _ "github.com/go-sql-driver/mysql"
)

func main() {
    // DSN 格式: user:password@tcp(host:port)/dbname?params
    dsn := "myuser:mypassword@tcp(localhost:3306)/mydb?charset=utf8mb4&parseTime=True&loc=Local"

    db, err := sql.Open("mysql", dsn)
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    // 配置连接池
    db.SetMaxOpenConns(10)
    db.SetMaxIdleConns(5)
    db.SetConnMaxLifetime(time.Hour)

    // 测试连接
    err = db.Ping()
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println("Connected to MySQL!")

    // 查询版本
    var version string
    err = db.QueryRow("SELECT version()").Scan(&version)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("Version:", version)
}

Python

import pymysql

# 连接参数
conn = pymysql.connect(
    host='localhost',
    port=3306,
    user='myuser',
    password='mypassword',
    database='mydb',
    charset='utf8mb4'
)

cursor = conn.cursor()
cursor.execute("SELECT version()")
print(cursor.fetchone())

cursor.close()
conn.close()

Node.js

const mysql = require('mysql2');

const connection = mysql.createConnection({
    host: 'localhost',
    port: 3306,
    user: 'myuser',
    password: 'mypassword',
    database: 'mydb'
});

connection.query('SELECT version() as version', (err, results) => {
    console.log(results[0].version);
    connection.end();
});

初始化脚本

mkdir -p ./init-scripts

cat > ./init-scripts/01-init.sql << 'EOF'
-- 创建数据库
CREATE DATABASE IF NOT EXISTS testdb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- 创建用户并授权
CREATE USER IF NOT EXISTS 'testuser'@'%' IDENTIFIED BY 'testpass';
GRANT ALL PRIVILEGES ON testdb.* TO 'testuser'@'%';
FLUSH PRIVILEGES;

-- 使用数据库
USE testdb;

-- 创建表
CREATE TABLE IF NOT EXISTS users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(100) UNIQUE NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 插入数据
INSERT INTO users (name, email) VALUES 
    ('Alice', '[email protected]'),
    ('Bob', '[email protected]');
EOF

# 启动
docker run -d \
    --name mysql-init \
    -e MYSQL_ROOT_PASSWORD=root123 \
    -p 3306:3306 \
    -v $(pwd)/init-scripts:/docker-entrypoint-initdb.d \
    mysql:8

备份与恢复

# 备份单个数据库
docker exec mysql-dev mysqldump -u root -prootpassword mydb > backup.sql

# 备份所有数据库
docker exec mysql-dev mysqldump -u root -prootpassword --all-databases > all_backup.sql

# 恢复
docker exec -i mysql-dev mysql -u root -prootpassword mydb < backup.sql

MySQL 8.0 认证问题

如果遇到 Authentication plugin 'caching_sha2_password' cannot be loaded 错误:

# 方式一:修改用户认证方式
docker exec -it mysql-dev mysql -u root -p
ALTER USER 'myuser'@'%' IDENTIFIED WITH mysql_native_password BY 'mypassword';
FLUSH PRIVILEGES;

# 方式二:启动时使用旧认证
docker run -d \
    --name mysql-legacy \
    -e MYSQL_ROOT_PASSWORD=root123 \
    -p 3306:3306 \
    mysql:8 \
    --default-authentication-plugin=mysql_native_password

docker-compose 多数据库环境

创建一个包含 PostgreSQL 和 MySQL 的开发环境:

version: '3.8'

services:
  postgres:
    image: postgres:15-alpine
    container_name: dev-postgres
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: devdb
      TZ: Asia/Shanghai
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./init/postgres:/docker-entrypoint-initdb.d
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5

  mysql:
    image: mysql:8
    container_name: dev-mysql
    environment:
      MYSQL_ROOT_PASSWORD: root123
      MYSQL_DATABASE: devdb
      MYSQL_USER: devuser
      MYSQL_PASSWORD: devpass
      TZ: Asia/Shanghai
    ports:
      - "3306:3306"
    volumes:
      - mysql_data:/var/lib/mysql
      - ./init/mysql:/docker-entrypoint-initdb.d
      - ./mysql-conf:/etc/mysql/conf.d
    command: --default-authentication-plugin=mysql_native_password
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      timeout: 5s
      retries: 5

  # 可选:数据库管理界面
  adminer:
    image: adminer
    container_name: dev-adminer
    ports:
      - "8080:8080"
    depends_on:
      - postgres
      - mysql

volumes:
  postgres_data:
  mysql_data:

使用:

# 启动所有服务
docker-compose up -d

# 查看状态
docker-compose ps

# 查看日志
docker-compose logs -f postgres
docker-compose logs -f mysql

# 停止
docker-compose down

# 停止并删除数据
docker-compose down -v

访问 Adminer(数据库管理界面):http://localhost:8080


常用场景

场景一:单元测试数据库

每次测试前启动干净的数据库,测试后删除:

#!/bin/bash
# test-db.sh

# 启动测试数据库
docker run -d --rm \
    --name test-postgres \
    -e POSTGRES_PASSWORD=test \
    -e POSTGRES_DB=testdb \
    -p 15432:5432 \
    postgres:15-alpine

# 等待数据库就绪
echo "Waiting for database..."
sleep 3
until docker exec test-postgres pg_isready -U postgres; do
    sleep 1
done

# 运行测试
echo "Running tests..."
DATABASE_URL="postgresql://postgres:test@localhost:15432/testdb?sslmode=disable" go test ./...

# 停止数据库(--rm 会自动删除)
docker stop test-postgres

场景二:模拟生产数据

从生产环境导出数据,在本地测试:

# 1. 从生产导出(在生产服务器执行)
pg_dump -h prod-db -U app_user -Fc app_db > prod_backup.dump

# 2. 在本地启动数据库
docker run -d \
    --name local-prod-replica \
    -e POSTGRES_PASSWORD=local123 \
    -p 5432:5432 \
    postgres:15-alpine

# 3. 恢复数据
docker cp prod_backup.dump local-prod-replica:/tmp/
docker exec local-prod-replica pg_restore -U postgres -d postgres -C /tmp/prod_backup.dump

场景三:多版本测试

同时运行不同版本的数据库:

# PostgreSQL 13
docker run -d --name pg13 -e POSTGRES_PASSWORD=test -p 5413:5432 postgres:13-alpine

# PostgreSQL 14
docker run -d --name pg14 -e POSTGRES_PASSWORD=test -p 5414:5432 postgres:14-alpine

# PostgreSQL 15
docker run -d --name pg15 -e POSTGRES_PASSWORD=test -p 5415:5432 postgres:15-alpine

# MySQL 5.7
docker run -d --name mysql57 -e MYSQL_ROOT_PASSWORD=test -p 3357:3306 mysql:5.7

# MySQL 8.0
docker run -d --name mysql80 -e MYSQL_ROOT_PASSWORD=test -p 3380:3306 mysql:8

连接字符串速查

PostgreSQL

# 标准格式
postgresql://user:password@host:port/database?sslmode=disable

# 示例
postgresql://postgres:postgres@localhost:5432/mydb?sslmode=disable

# Go (lib/pq)
host=localhost port=5432 user=postgres password=postgres dbname=mydb sslmode=disable

# Go (pgx)
postgres://postgres:postgres@localhost:5432/mydb?sslmode=disable

MySQL

# 标准格式
user:password@tcp(host:port)/database?charset=utf8mb4&parseTime=True

# 示例
myuser:mypassword@tcp(localhost:3306)/mydb?charset=utf8mb4&parseTime=True&loc=Local

# JDBC 格式
jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=Asia/Shanghai

常见问题

Q1: 容器启动后立即退出

原因:可能是配置错误或端口冲突。

排查

# 查看日志
docker logs postgres-test

# 检查端口占用
lsof -i :5432

Q2: 权限不足无法写入数据

原因:绑定挂载的目录权限问题。

解决

# 使用命名数据卷代替绑定挂载
docker volume create my_data
docker run -v my_data:/var/lib/postgresql/data ...

# 或修改目录权限
sudo chown -R 999:999 ./pgdata  # PostgreSQL
sudo chown -R 999:999 ./mysql_data  # MySQL

Q3: 中文乱码

PostgreSQL

docker run -e POSTGRES_INITDB_ARGS="--encoding=UTF8" ...

MySQL

docker run ... mysql:8 --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci

Q4: 时区不对

# PostgreSQL
docker exec -it postgres-dev psql -U postgres -c "SET timezone = 'Asia/Shanghai'; SHOW timezone;"

# MySQL
docker exec -it mysql-dev mysql -u root -p -e "SET GLOBAL time_zone = '+08:00'; SELECT NOW();"

总结

数据库 镜像 默认端口 数据目录
PostgreSQL postgres:15-alpine 5432 /var/lib/postgresql/data
MySQL mysql:8 3306 /var/lib/mysql

使用 Docker 启动测试数据库的核心命令:

# PostgreSQL
docker run -d --name pg -e POSTGRES_PASSWORD=pass -p 5432:5432 postgres:15-alpine

# MySQL
docker run -d --name mysql -e MYSQL_ROOT_PASSWORD=pass -p 3306:3306 mysql:8

参考资料

版权声明: 如无特别声明,本文版权归 sshipanoo 所有,转载请注明本文链接。

(采用 CC BY-NC-SA 4.0 许可协议进行授权)

本文标题:《 Docker 系列——测试数据库部署 》

本文链接:http://localhost:3015/ai/Docker%E6%B5%8B%E8%AF%95%E6%95%B0%E6%8D%AE%E5%BA%93%E9%83%A8%E7%BD%B2.html

本文最后一次更新为 天前,文章中的某些内容可能已过时!