---
id: database-postgres
title: "PostgreSQL cho vận hành"
shortTitle: "PostgreSQL"
track: database
trackTitle: "Database"
order: 1
level: core
status: published
estimatedMinutes: 45
prerequisites:
  - webserver-edge
next: database-mongo
reviewedAgainst: 2026-10-12
sources:
  - title: "PostgreSQL role attributes"
    url: https://www.postgresql.org/docs/current/role-attributes.html
  - title: "pg_dump"
    url: https://www.postgresql.org/docs/current/app-pgdump.html
  - title: "MySQL — mysqldump"
    url: https://dev.mysql.com/doc/refman/8.4/en/mysqldump.html
---

# PostgreSQL cho vận hành

Bài này không dạy thiết kế bảng. Người vận hành cần mở được connection, tạo role đủ quyền, và giữ một file backup restore được. Lab dùng Docker để khỏi cài Postgres lên máy. Image `postgres:17` khớp mốc trong README.

MySQL/MariaDB làm cùng việc, khác lệnh. Phần cuối ghi lệnh tương ứng. SQL Server, Cassandra, DynamoDB, và Oracle nằm ở các bài sau.

## Để làm gì

Đứng trước một Postgres đã chạy: đăng nhập, tạo role cho app, dump một database, restore sang database khác. Biết connection string app đang dùng trỏ vào đâu.

## Gồm những gì

- Role, không dùng superuser cho app.
- `pg_dump` dạng custom (`-Fc`) và `pg_restore`.
- Biến `PGPASSWORD` chỉ trong lab. Production dùng file password hoặc secret của platform.
- Bảng lệnh MySQL tương đương: `CREATE USER`, `mysqldump`.

Không gồm index, query plan, replication.

## Dùng khi nào

App báo không connect, hoặc bạn cần bản dump trước khi sửa schema. Managed service (Azure Database, RDS, Cloud SQL) vẫn là Postgres. Portal tạo server xong, bạn vẫn dùng `psql` và `pg_dump` nếu firewall cho IP của bạn.

## Kiến thức cốt lõi

Postgres gọi user là role. Role có attribute `LOGIN` thì đăng nhập được. Superuser đi qua hết kiểm tra quyền. App không cần superuser. Tạo role login, cấp `CONNECT` trên database và quyền trên schema `public`.

Connection string dạng URL:

```text
postgres://app:secret@127.0.0.1:5432/appdb
```

Host, port, database, user, password. App và tool admin phải trỏ cùng một host. "Works on my machine" thường là nhầm `localhost` trong container với `localhost` trên host.

`pg_dump -Fc` ghi custom format. `pg_restore` đọc format đó, restore được từng bảng, và không cần đọc bằng editor. Plain SQL (`pg_dump` không `-Fc`) mở được bằng editor, nhưng restore từng bảng kém hơn custom format. Docs `pg_dump` mô tả `-Fc`.

Dump không thay backup đã kiểm tra restore. File dump chưa từng `pg_restore` thì chưa biết nó lành.

MySQL: user và host là một cặp (`app`@`%`). `mysqldump` mặc định ra SQL. `--single-transaction` với InnoDB để dump không khoá lâu. MariaDB dùng cùng họ lệnh này.

## Lệnh và code mẫu

```bash
mkdir -p "$HOME/devops-lab/database"
docker run -d --name lab-pg \
  -e POSTGRES_PASSWORD=lab \
  -p 5432:5432 \
  postgres:17
```

Đợi vài giây cho server nhận connection.

```bash
docker exec -it lab-pg psql -U postgres -c "CREATE DATABASE appdb;"
docker exec -it lab-pg psql -U postgres -d appdb -c \
  "CREATE ROLE app LOGIN PASSWORD 'secret';"
docker exec -it lab-pg psql -U postgres -d appdb -c \
  "GRANT CONNECT ON DATABASE appdb TO app;"
docker exec -it lab-pg psql -U postgres -d appdb -c \
  "GRANT USAGE, CREATE ON SCHEMA public TO app;"
```

Từ Postgres 15, role thường không còn quyền `CREATE` trên schema `public`. `GRANT` ở trên là cố ý.

Dump và restore:

```bash
docker exec lab-pg pg_dump -U postgres -Fc appdb > "$HOME/devops-lab/database/appdb.dump"
docker exec -i lab-pg psql -U postgres -c "CREATE DATABASE appdb2;"
docker exec -i lab-pg pg_restore -U postgres -d appdb2 --no-owner < "$HOME/devops-lab/database/appdb.dump"
```

MySQL, nếu bạn có server, cùng ý:

```sql
CREATE USER 'app'@'%' IDENTIFIED BY 'secret';
GRANT SELECT, INSERT, UPDATE, DELETE ON appdb.* TO 'app'@'%';
```

```bash
mysqldump --single-transaction -u root -p appdb > appdb.sql
```

## Thực hành

1. Chạy container và các lệnh `CREATE`.
2. `docker exec -it lab-pg psql -U app -d appdb -c 'SELECT current_user;'`. Phải ra `app`.
3. Dump, tạo `appdb2`, restore.
4. `docker exec lab-pg psql -U postgres -c '\l'` và thấy cả `appdb` lẫn `appdb2`.
5. Xong lab: `docker rm -f lab-pg`.

## Kết quả mong đợi

- `current_user` là `app`, không phải `postgres`.
- File `appdb.dump` không phải text SQL thuần (custom format).
- `\l` liệt kê `appdb2`.
- Container đã xoá, port 5432 không còn process lab.

## Checklist

- [ ] App dùng role riêng, không dùng superuser.
- [ ] Đọc được host, port, database trong connection string.
- [ ] Dump bằng `-Fc` và restore bằng `pg_restore`.
- [ ] Restore thử một lần trước khi coi file dump là backup.
- [ ] Chỉ ra lệnh MySQL tương ứng với `pg_dump`.

## Lỗi thường gặp

**App dùng user `postgres`.** Một SQL injection hoặc một secret lộ là toàn quyền. Tách role.

**`pg_dump` plain rồi mở file, thấy trống, tưởng database trống.** Dump của database mới tạo chỉ có vài lệnh schema. Nhìn cuối file, hoặc đếm bảng bằng `psql \dt` trước khi dump.

**Restore đè lên database production vì nhầm `-d`.** `pg_restore -d` ghi vào database bạn chỉ tên. Đọc lại tên trước khi Enter. Lab chỉ dùng `appdb2`.

**Postgres 15: `permission denied for schema public`.** Role login chưa được `GRANT` trên schema. Câu `GRANT USAGE, CREATE` trong bài là để hết lỗi đó.

## Nguồn

1. [Role attributes](https://www.postgresql.org/docs/current/role-attributes.html) — PostgreSQL, `LOGIN`, superuser.
2. [pg_dump](https://www.postgresql.org/docs/current/app-pgdump.html) — `-Fc` và các format.
3. [mysqldump](https://dev.mysql.com/doc/refman/8.4/en/mysqldump.html) — MySQL 8.4, `--single-transaction`.

## Tiếp theo

[MongoDB cho vận hành](./02-mongo.md)
