- Tác giả

- Name
- Nguyễn Đức Xinh
- Ngày xuất bản
- Ngày xuất bản
Nâng Cấp MySQL, MariaDB, PostgreSQL Trên AWS RDS: Quy Trình Tổng Hợp
Giới Thiệu
Nâng cấp database là loại việc mà ai cũng biết "sớm muộn gì cũng phải làm", nhưng thường bị hoãn cho tới khi AWS gửi email báo version sắp hết hỗ trợ, hóa đơn RDS bỗng có thêm dòng Extended Support, hoặc một query chạy mãi không xong mà nguyên nhân lại nằm ở optimizer của version cũ. Lúc đó việc nâng cấp trở thành việc gấp — và việc gấp thì dễ sai.
Bài này là bản tổng hợp một trang cho toàn bộ quy trình nâng cấp database trên AWS RDS, áp dụng cho cả MySQL, MariaDB và PostgreSQL: lý do nâng cấp, lịch hết hỗ trợ, cách chọn version, chuẩn bị, việc backend (Laravel) và frontend cần làm, cách thông báo cho các bên, verify và rollback. Mỗi phần có link sang bài chi tiết trong khoá Nâng Cấp Database Trên AWS RDS — nếu bạn chỉ có 15 phút, đọc bài này; nếu bạn sắp làm thật, đọc thêm các bài chi tiết.
Bài viết dựa trên một lần nâng cấp thật: RDS MariaDB 10.5 → 11.8 cho một hệ thống Laravel, downtime khoảng 3 phút, kèm một sự cố TLS ngay sau khi bật lại ứng dụng. Các con số về case study là số đo thật từ lần nâng cấp đó; phần MySQL và PostgreSQL là kiến thức chung, áp dụng cùng một quy trình.
Vì Sao Phải Nâng Cấp Database?
Có bốn lý do phổ biến, xếp theo mức độ "không thể né":
| Lý do | Ví dụ | Mức độ gấp |
|---|---|---|
| Hết standard support | MySQL 5.7 và 8.0, MariaDB 10.5, PostgreSQL 11–13 đều đã hết standard support trên RDS (xem bảng bên dưới) | Cao — tính phí Extended Support hoặc bị nâng cấp bắt buộc |
| AWS ép nâng minor | Pending maintenance kiểu "Required upgrade to mariadb 10.5.29" | Cao — có hạn chót |
| Lỗi hoặc giới hạn của optimizer | MariaDB trước 11.1 không dùng semi-join cho DELETE … WHERE id IN (SELECT …) |
Trung bình — tuỳ query |
| Tính năng, bảo mật, hiệu năng mới | MySQL 8.4 LTS, PostgreSQL 17/18, MariaDB 11 có cost model mới | Thấp — nhưng tích luỹ dần |
Lịch Hỗ Trợ Trên RDS (Tại Thời Điểm Viết)
Số liệu lấy bằng aws rds describe-db-major-engine-versions ngày 2026-11-06 — hãy chạy lại lệnh này khi bạn đọc bài, vì AWS cập nhật lịch định kỳ.
| Engine | Major version | Hết standard support | Hết Extended Support (tính phí) | Trạng thái |
|---|---|---|---|---|
| MySQL | 5.7 | 2024-02-29 | 2029-06-30 | ⚠️ Đang tính phí Extended Support |
| MySQL | 8.0 | 2026-07-31 | 2029-07-31 | ⚠️ Đang tính phí Extended Support |
| MySQL | 8.4 (LTS) | 2029-07-31 | 2032-07-31 | ✅ Đích nâng cấp khuyến nghị |
| MariaDB | 10.5 | 2026-02-28 | Không có | ⚠️ Bị lên lịch nâng cấp bắt buộc |
| MariaDB | 10.6 | 2026-12-31 | Không có | ⚠️ Sắp hết |
| MariaDB | 10.11 | 2028-02-29 | Không có | Còn hỗ trợ |
| MariaDB | 11.4 / 11.8 | 2029-05-31 / 2030-06-30 | Không có | ✅ Đích nâng cấp khuyến nghị |
| PostgreSQL | 11 / 12 / 13 | 2024-02-29 / 2025-02-28 / 2026-02-28 | 2027-03 / 2028-02 / 2029-02 | ⚠️ Đang tính phí Extended Support |
| PostgreSQL | 14 | 2027-02-28 | 2030-02-28 | Sắp hết — nên lên kế hoạch |
| PostgreSQL | 15 / 16 | 2028-02-29 / 2029-02-28 | 2031-02 / 2032-02 | Còn hỗ trợ |
| PostgreSQL | 17 / 18 | 2030-02-28 / 2031-02-28 | 2033-02 / 2034-02 | ✅ Đích nâng cấp khuyến nghị |
# Kiểm tra lịch hỗ trợ của engine bạn đang dùng (mysql | mariadb | postgres)
aws rds describe-db-major-engine-versions --engine mysql \
--query 'DBMajorEngineVersions[].[MajorEngineVersion,
SupportedEngineLifecycles[?LifecycleSupportName==`open-source-rds-standard-support`].LifecycleSupportEndDate|[0],
SupportedEngineLifecycles[?LifecycleSupportName==`open-source-rds-extended-support`].LifecycleSupportEndDate|[0]]' \
--output table
Giải thích:
- MySQL và PostgreSQL có RDS Extended Support: hết standard support thì instance vẫn chạy, nhưng AWS tự đăng ký Extended Support và tính phí theo vCPU-giờ (khá đắt với instance lớn). Đây là lý do nhiều team phát hiện cần nâng cấp qua hóa đơn.
- MariaDB không có Extended Support — hết standard support thì AWS lên lịch nâng cấp bắt buộc.
- Nếu đang ở MySQL 5.7 hoặc 8.0, PostgreSQL 13 trở xuống, bạn đã đang trả phí hoặc sắp phải trả.
Ví Dụ: Version Cũ Làm Query Chậm
Trong case study, một câu DELETE dùng IN (SELECT …) trên bảng 36 triệu dòng chạy hơn 5 phút rồi bị ngắt trên MariaDB 10.5. Sau khi nâng lên 11.8, cùng câu đó chạy 1 giây — không sửa một dòng SQL nào.
-- MariaDB 10.5: full scan bảng đích, subquery chạy lại cho từng dòng
EXPLAIN DELETE FROM order_items
WHERE id IN (SELECT id FROM (SELECT DISTINCT op.id
FROM products p
JOIN order_items op ON op.product_id = p.id
WHERE p.id IN (101, 102)) x);
-- id=1 PRIMARY order_items ALL rows=36,234,577
-- id=2 DEPENDENT SUBQUERY ...
-- MariaDB 11.8: cùng câu lệnh
-- id=1 PRIMARY <derived3> ALL
-- id=1 PRIMARY order_items eq_ref key=PRIMARY rows=1
Giải thích: trên 10.5 bảng đích bị quét toàn bộ (type = ALL, DEPENDENT SUBQUERY); trên 11.8 optimizer chuyển sang semi-join và tra theo khoá chính (eq_ref). Cách sửa tạm thời là viết lại bằng multi-table DELETE t FROM t JOIN (…) x ON x.id = t.id; cách sửa gốc là nâng version. Xem thêm về đọc EXPLAIN ở bài EXPLAIN Statement.
Bước 1: Xác Định Version Hiện Tại Và Version Đích
Đừng đoán version — hãy đọc. Có ba nguồn:
# 1) Hỏi thẳng server
mysql -h <db-host> -u <user> -p -e "SELECT VERSION();" # MySQL / MariaDB
psql -h <db-host> -U <user> -d <db> -c "SHOW server_version;" # PostgreSQL
# 2) Hỏi RDS: version hiện tại và các version được phép nâng lên
aws rds describe-db-instances --db-instance-identifier <db-instance> \
--query 'DBInstances[0].[Engine,EngineVersion,DBInstanceClass]' --output text
aws rds describe-db-engine-versions --engine postgres --engine-version 13.23 \
--query "DBEngineVersions[0].ValidUpgradeTarget[?IsMajorVersionUpgrade].EngineVersion" --output text
# 3) Hệ thống không truy cập được (on-premise, của đối tác): đọc header file dump
zcat dump.sql.gz | head -5
# MySQL/MariaDB: -- Server version 11.7.2-MariaDB
# PostgreSQL: -- Dumped from database version 15.6
Giải thích:
ValidUpgradeTargetlà danh sách version RDS cho phép nâng trực tiếp. Có lúc phải nâng qua một version trung gian (ví dụ MySQL 5.7 phải lên 8.0 trước rồi mới lên 8.4).- Header của
mysqldump/mariadb-dump/pg_dumpghi version của server — hữu ích khi cần đối chiếu với hệ thống không có quyền truy cập.
Khi chọn version đích, nhớ bốn quy tắc:
- Ưu tiên bản LTS / bản có hạn hỗ trợ xa nhất: MySQL 8.4, MariaDB 11.4/11.8, PostgreSQL 17/18. RDS chỉ cung cấp bản LTS cho MariaDB (bản short-term như 11.7 không có).
- Chọn theo tính năng cần có, không chỉ theo "mới nhất". Trong case study, MariaDB 10.11 vẫn còn hạn hỗ trợ nhưng chưa có semi-join cho
DELETE(có từ 11.1), nên đích phải là 11.4 hoặc 11.8. - MySQL 5.7 → 8.4 phải đi qua 8.0. Gộp hai lần nâng vào cùng một cửa sổ bảo trì chỉ khi đã rehearsal cả hai.
- Đồng bộ dev, staging, production. Đo hiệu năng trên dev bản cũ trong khi production chạy bản mới sẽ cho kết luận sai.
Chi tiết lifecycle và các lệnh AWS CLI: RDS Version Lifecycle Và Upgrade Path.
Bước 2: Đọc Breaking Changes Của Engine
Mỗi lần nâng major version đều có thay đổi phá vỡ tương thích. Những điểm hay gặp nhất:
| Engine | Thay đổi | Ảnh hưởng thường gặp |
|---|---|---|
| MySQL 5.7 → 8.0 | Auth mặc định caching_sha2_password; collation mặc định utf8mb4_0900_ai_ci; từ khoá mới (RANK, GROUPS…); bỏ query cache; RDS chạy pre-check và ghi log PrePatchCompatibility.log |
Client cũ không kết nối được; lỗi Illegal mix of collations; tên cột trùng từ khoá |
| MySQL 8.0 → 8.4 | mysql_native_password bị tắt mặc định; một số biến/tham số cũ bị bỏ |
Ứng dụng/driver cũ không đăng nhập được |
| MariaDB 10.x → 11.x | Cost model mới (11.0); semi-join cho UPDATE/DELETE (11.1); collation mặc định họ uca1400; RDS family 11.x bật require_secure_transport |
Execution plan thay đổi; bảng tạo không khai báo collation bị khác; app không TLS bị từ chối |
| PostgreSQL major (vd 13 → 17) | RDS dùng pg_upgrade; extension phải tương thích với version đích; thống kê planner không được mang sang (trước PG18); password_encryption mặc định scram-sha-256 (từ PG14); RDS bật rds.force_ssl mặc định từ PG15 |
Nâng cấp thất bại vì extension/replication slot; query chậm ngay sau nâng cấp nếu quên ANALYZE; client không SSL bị từ chối |
Riêng Với PostgreSQL: Pre-check Cần Làm
RDS chạy pg_upgrade --check trước khi nâng; nếu fail, instance giữ nguyên version cũ và lý do nằm trong log pg_upgrade_precheck.log. Tự kiểm trước sẽ đỡ mất một vòng:
-- Extension đang dùng và version: phải có bản tương thích với PostgreSQL đích
SELECT extname, extversion FROM pg_extension ORDER BY 1;
-- Logical replication slot phải được xoá trước khi nâng major
SELECT slot_name, slot_type, active FROM pg_replication_slots;
-- Prepared transaction còn treo sẽ làm pg_upgrade fail
SELECT gid, prepared FROM pg_prepared_xacts;
-- Cột dùng kiểu reg* (regproc, regclass…) không được pg_upgrade hỗ trợ
SELECT c.relname, a.attname, a.atttypid::regtype
FROM pg_attribute a JOIN pg_class c ON c.oid = a.attrelid
WHERE a.atttypid::regtype::text IN ('regproc','regprocedure','regoper','regoperator','regconfig','regdictionary')
AND c.relnamespace NOT IN (SELECT oid FROM pg_namespace WHERE nspname IN ('pg_catalog','information_schema'));
Giải thích: đây là các nguyên nhân fail hay gặp nhất của pg_upgrade trên RDS. Extension như PostGIS thường cần nâng lên bản mới nhất trên version cũ trước (ALTER EXTENSION postgis UPDATE;), rồi mới nâng engine.
Cách Kiểm Nhanh Nhất Cho Mọi Engine
So sánh default parameter của family cũ và family mới trước khi nâng:
OLD=mariadb10.5; NEW=mariadb11.8 # hoặc mysql8.0 / mysql8.4, postgres13 / postgres17
for fam in "$OLD" "$NEW"; do
aws rds describe-engine-default-parameters --db-parameter-group-family "$fam" \
--query 'EngineDefaults.Parameters[?ParameterValue!=`null`].[ParameterName,ParameterValue]' \
--output text | sort > "defaults-$fam.txt"
done
diff "defaults-$OLD.txt" "defaults-$NEW.txt"
Nếu đã làm bước này, case study đã không gặp sự cố require_secure_transport (xem phần Sự Cố bên dưới); với PostgreSQL, đây là cách phát hiện sớm rds.force_ssl. Chi tiết từng engine: Breaking Changes Khi Nâng MySQL, MariaDB, PostgreSQL.
Bước 3: Chọn Chiến Lược Nâng Cấp
| Chiến lược | Downtime | Độ an toàn | Khi nào dùng |
|---|---|---|---|
In-place (modify-db-instance) |
Vài phút đến vài chục phút | Rollback bằng restore snapshot | Dev/staging, hệ thống chịu được downtime ngắn |
| Rehearsal (restore snapshot → nâng thử) rồi in-place | Như in-place | Cao — biết trước thời gian và lỗi | Production, hoặc lần đầu nâng major |
| Blue/Green Deployments | Switchover ngắn (thường dưới 1 phút, kiểm tra theo engine) | Cao — môi trường mới chạy song song | Production cần downtime tối thiểu; có cho MySQL, MariaDB và PostgreSQL (PostgreSQL dựa trên logical replication, có thêm điều kiện riêng) |
Lưu ý quan trọng cho cả ba engine: RDS không cho hạ version tại chỗ. Rollback luôn là "restore snapshot sang instance mới rồi đổi endpoint". Nếu backup retention > 0, RDS tự chụp snapshot ngay trước và sau khi nâng major — đó là điểm rollback không mất dữ liệu (nhưng bị xoá theo retention; muốn giữ lâu thì copy sang manual snapshot).
Với PostgreSQL, thời gian downtime của pg_upgrade phụ thuộc nhiều vào số lượng object (bảng, index, schema) hơn là dung lượng; cơ sở dữ liệu có hàng chục nghìn bảng (ví dụ multi-tenant theo schema) có thể mất lâu hơn hẳn — đó là lý do phải rehearsal.
Chi tiết: Chiến Lược Nâng Cấp RDS: In-place, Rehearsal, Blue/Green. Về backup nói chung: Backup và Restore.
Bước 4: Chuẩn Bị Parameter Group Mới
Parameter group gắn với family (mysql8.4, mariadb11.8, postgres17…), nên nâng major luôn cần group mới. Ba việc cần làm:
- Mang theo các tham số đã tự chỉnh ở group cũ (lọc
--source user). - Ghim charset/collation, timezone theo chuẩn của ứng dụng để version mới không tự đổi.
- Rà các default thay đổi (bước 2) và quyết định giữ hành vi cũ hay đổi theo.
# Tham số đã tự chỉnh ở group cũ
aws rds describe-db-parameters --db-parameter-group-name <old-group> --source user \
--query 'Parameters[].[ParameterName,ParameterValue]' --output table
# Tạo group mới và đặt giá trị (dùng JSON vì có giá trị chứa dấu '=')
aws rds create-db-parameter-group --db-parameter-group-name <new-group> \
--db-parameter-group-family mariadb11.8 --description "app db mariadb 11.8"
aws rds modify-db-parameter-group --db-parameter-group-name <new-group> --parameters '[
{"ParameterName":"time_zone","ParameterValue":"Asia/Tokyo","ApplyMethod":"pending-reboot"},
{"ParameterName":"character_set_server","ParameterValue":"utf8mb4","ApplyMethod":"pending-reboot"},
{"ParameterName":"collation_server","ParameterValue":"utf8mb4_unicode_ci","ApplyMethod":"pending-reboot"},
{"ParameterName":"character_set_collations","ParameterValue":"utf8mb4=utf8mb4_unicode_ci","ApplyMethod":"pending-reboot"},
{"ParameterName":"require_secure_transport","ParameterValue":"0","ApplyMethod":"pending-reboot"}
]'
Giải thích:
character_set_collations(MariaDB 11.x) quyết định collation khi chỉ ghiCHARSET utf8mb4mà không ghi collation. Không ghim, MariaDB sẽ chọnutf8mb4_uca1400_ai_civà dễ gây lỗi1267 Illegal mix of collationskhi JOIN với cột cũ. Với MySQL 8.x, tương tự làcollation_server(mặc địnhutf8mb4_0900_ai_ci).require_secure_transport = 0chỉ nên dùng khi ứng dụng chưa cấu hình TLS. Lâu dài, hãy cấu hình TLS cho ứng dụng và bật lại.- PostgreSQL dùng tham số khác:
timezone(không phảitime_zone),rds.force_ssl(thay chorequire_secure_transport), và các extension cần nạp sẵn nhưshared_preload_libraries(pg_stat_statements,pg_cron…) phải được mang sang group mới, nếu không extension sẽ không hoạt động sau khi nâng.
Bước 5: Thông Báo Các Bên Liên Quan
Downtime database ảnh hưởng tới nhiều người hơn bạn nghĩ: team dev, QA đang test, các team khác dùng chung instance, batch/integration của đối tác, và người dùng cuối.
| Đối tượng | Nội dung cần báo | Khi nào |
|---|---|---|
| Team dev, QA, PM | Lý do, khung giờ, downtime dự kiến, việc không được làm (E2E, migrate, import) | Trước 1–3 ngày, nhắc lại trước 1 giờ |
| Team khác dùng chung DB | Danh sách schema/database bị ảnh hưởng, rủi ro tương thích | Trước khi chốt lịch |
| Đối tác có batch/integration | Khung giờ dừng, có cần chạy bù không | Theo hợp đồng/SLA |
| Khách hàng, người dùng cuối | Thời gian bảo trì, tính năng bị ảnh hưởng, kênh liên hệ | Trước theo SLA; banner trong app |
Mẫu tin nhắn xin phép nội bộ (Slack) nên có đủ: việc gì, vì sao, khi nào, ảnh hưởng ai, cần mọi người làm gì, rollback thế nào, và cách xác nhận (react ✅). Nếu lý do là Extended Support, ghi rõ chi phí phát sinh — đó là lập luận thuyết phục nhất với quản lý. Khi xong, báo lại trong cùng thread: version mới, thời gian downtime thực tế, sự cố nếu có, việc còn lại. Mẫu đầy đủ: Frontend Và Thông Báo Các Bên.
Bước 6: Đưa Ứng Dụng Vào Trạng Thái An Toàn (Laravel)
Trước khi bấm nâng cấp, ứng dụng phải ngừng ghi vào database một cách có kiểm soát. Thứ tự với Laravel (giống nhau cho MySQL, MariaDB, PostgreSQL):
# 1) Bật maintenance mode — API trả 503 thay vì 500
php artisan down --retry=60
# Cho phép team kiểm tra qua secret: php artisan down --secret="<token>"
# 2) Dừng queue worker (để job đang chạy kết thúc — cấu hình stopwaitsecs trong supervisor)
sudo supervisorctl stop "app-queue-worker:*"
# Nếu dùng Horizon: php artisan horizon:pause (hoặc horizon:terminate)
# 3) Tắt scheduler: comment dòng cron schedule:run (sao lưu crontab trước)
crontab -l > ~/crontab.backup
crontab -l | sed '/schedule:run/s/^/#UPGRADE# /' | crontab -
# 4) Kiểm tra không còn query dài trên DB
mysql -h <db-host> -u <user> -p -e \
"SELECT id, user, time, LEFT(info, 80) FROM information_schema.processlist
WHERE command <> 'Sleep' ORDER BY time DESC;"
psql -h <db-host> -U <user> -d <db> -c \
"SELECT pid, usename, now() - query_start AS duration, left(query, 80)
FROM pg_stat_activity WHERE state <> 'idle' AND pid <> pg_backend_pid() ORDER BY 3 DESC;"
Sau khi nâng cấp và verify xong, làm ngược lại: khôi phục cron, start worker, php artisan up.
Cấu hình TLS trong config/database.php khác nhau giữa hai driver — và đây chính là chỗ sự cố hay xảy ra sau nâng cấp:
'mysql' => [
// ...
'options' => extension_loaded('pdo_mysql') ? array_filter([
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), // vd /etc/ssl/rds-global-bundle.pem
]) : [],
],
'pgsql' => [
// ...
'sslmode' => env('DB_SSLMODE', 'prefer'), // 'require' / 'verify-full' cho production
],
Giải thích: với pgsql, sslmode=prefer (mặc định của Laravel) tự dùng SSL nếu server hỗ trợ, nên bật rds.force_ssl thường không làm app chết. Với mysql, nếu không cấu hình MYSQL_ATTR_SSL_CA, kết nối mặc định không mã hoá — server bật require_secure_transport là app bị từ chối ngay.
Lưu ý: bật maintenance mode trước khi bấm nâng cấp, không phải khi database đã bắt đầu downtime. Cách chắc chắn nhất là đưa
app-down/app-upvào chính script nâng cấp, để không phụ thuộc vào việc "nhớ ra lúc đang làm".
Chi tiết (multi-server, cache driver cho maintenance, Horizon): Laravel Khi Nâng Cấp Database.
Bước 7: Frontend Cần Làm Gì?
Người dùng không nên thấy màn hình trắng hay lỗi "Something went wrong" trong lúc bảo trì:
- Trước khi bảo trì: banner thông báo khung giờ, cho người dùng kịp lưu công việc.
- Trong khi bảo trì: interceptor (vd axios) bắt
503, hiển thị trang bảo trì với thời gian dự kiến, đọc headerRetry-Afterđể tự thử lại; khoá các nút ghi dữ liệu. - Giữ dữ liệu đang nhập: form dài nên lưu nháp vào
localStorageđể không mất khi request bị từ chối. - API chết hoàn toàn: chuẩn bị một trang tĩnh trên CDN/S3 để chuyển hướng.
// Interceptor bắt 503 khi backend đang ở maintenance mode
api.interceptors.response.use(
(res) => res,
(error) => {
if (error.response?.status === 503) {
const retryAfter = Number(error.response.headers['retry-after'] ?? 60);
maintenanceStore.show({ retryAfter }); // hiển thị màn hình bảo trì
}
return Promise.reject(error);
},
);
Bước 8: Thực Hiện Nâng Cấp
Lệnh giống nhau cho cả ba engine, chỉ khác version và parameter group:
aws rds modify-db-instance --db-instance-identifier <db-instance> \
--engine-version 11.8.9 --allow-major-version-upgrade \
--db-parameter-group-name <new-group> --apply-immediately
# MySQL: --engine-version 8.4.x --db-parameter-group-name <group-mysql8.4>
# PostgreSQL: --engine-version 17.x --db-parameter-group-name <group-postgres17>
# (thêm --option-group-name nếu dùng option group riêng)
# Theo dõi sự kiện thay vì đoán
aws rds describe-events --source-identifier <db-instance> --source-type db-instance \
--duration 60 --query 'Events[].[Date,Message]' --output text
Timeline thật của case study (MariaDB, instance ~400 GB gp3, single-AZ):
| Mốc | Sự kiện RDS |
|---|---|
| +0:00 | Pre-check bắt đầu và kết thúc |
| +0:25 | DB instance shutdown, backing up (snapshot trước nâng cấp) |
| +1:06 | Downtime bắt đầu |
| +1:43 | Engine version upgrade started |
| +3:46 | Engine version upgrade finished, accepting connections |
| +4:02 | Major version upgrade complete, post-upgrade backup |
Downtime thực tế khoảng 3 phút. Nâng MariaDB tại chỗ không dựng lại bảng InnoDB, nên thời gian phụ thuộc vào số bảng hơn là dung lượng. MySQL 5.7 → 8.0 thường lâu hơn vì phải nâng cấp data dictionary; PostgreSQL phụ thuộc số object như đã nói ở bước 3. Đừng coi bất kỳ con số nào là chuẩn — hãy rehearsal để biết con số của hệ thống bạn.
Multi-AZ không giúp giảm downtime khi nâng engine: primary và standby được nâng cùng lúc. Muốn downtime gần bằng 0, dùng Blue/Green.
Bước 9: Verify Sau Nâng Cấp
MySQL / MariaDB:
SELECT @@version, @@character_set_server, @@collation_server,
@@time_zone, @@require_secure_transport;
-- MariaDB 11.x: SELECT @@character_set_collations;
-- Chuỗi utf8mb4 "trần" đang ra collation nào?
SELECT COLLATION(CONVERT('x' USING utf8mb4));
-- Số bảng có giữ nguyên không?
SELECT table_schema, engine, COUNT(*) FROM information_schema.tables
WHERE table_schema = '<app_schema>' GROUP BY 1, 2;
-- Bảng MyISAM (nếu còn) nên CHECK TABLE
CHECK TABLE <app_schema>.<myisam_table>;
PostgreSQL:
SELECT version();
SHOW server_encoding; SHOW timezone; SHOW password_encryption;
SELECT name, setting FROM pg_settings WHERE name IN ('rds.force_ssl', 'shared_preload_libraries');
-- Extension có lên đúng version không? Nâng tiếp nếu còn bản mới
SELECT extname, extversion FROM pg_extension;
-- ALTER EXTENSION <ext> UPDATE;
-- Bảng nào chưa có thống kê (cần ANALYZE ngay)
SELECT relname, last_analyze, last_autoanalyze
FROM pg_stat_user_tables ORDER BY relname LIMIT 20;
# PostgreSQL: chạy ANALYZE theo từng giai đoạn để có thống kê "đủ dùng" thật nhanh
vacuumdb -h <db-host> -U <user> -d <db> --analyze-in-stages --jobs=4
Sau đó kiểm tra từ phía ứng dụng, không chỉ từ client của bạn: php artisan migrate:status, gọi một API có đọc DB, chạy E2E. Cuối cùng cập nhật thống kê cho optimizer — ANALYZE TABLE với MySQL/MariaDB (trong case study mất 34 phút cho ~330 bảng, chạy sau khi đã bật lại ứng dụng), vacuumdb --analyze-in-stages với PostgreSQL (bắt buộc trước PG18, vì pg_upgrade không mang thống kê sang). Đo lại các query đã từng tối ưu thủ công (STRAIGHT_JOIN, FORCE INDEX, hint), vì optimizer mới có thể chọn plan khác.
Sự Cố Thường Gặp Và Cách Xử Lý
| Engine | Triệu chứng | Nguyên nhân | Cách xử lý |
|---|---|---|---|
| MySQL/MariaDB | SQLSTATE[HY000] [3159] Connections using insecure transport are prohibited |
Family mới bật require_secure_transport |
Đặt require_secure_transport=0 (dynamic) hoặc cấu hình TLS cho app |
| MySQL/MariaDB | [1129] Host '…' is blocked because of many connection errors |
Kết nối lỗi liên tục vượt max_connect_errors |
FLUSH HOSTS; sau khi sửa nguyên nhân gốc |
| MySQL | [2054] The server requested authentication method unknown to the client |
MySQL 8 dùng caching_sha2_password, driver cũ không hỗ trợ |
Nâng driver, hoặc đổi auth plugin của user |
| MySQL/MariaDB | 1267 Illegal mix of collations |
Collation mặc định mới khác collation cột cũ | Ghim collation_server/character_set_collations; COLLATE trong JOIN |
| PostgreSQL | Nâng cấp fail, instance giữ version cũ | Extension không tương thích, replication slot, prepared transaction | Đọc pg_upgrade_precheck.log, xử lý theo pre-check ở bước 2 |
| PostgreSQL | no pg_hba.conf entry for host …, SSL off |
rds.force_ssl=1 trên family mới |
Bật SSL phía client (sslmode=require) hoặc đặt rds.force_ssl=0 |
| PostgreSQL | password authentication failed sau khi đổi mật khẩu |
scram-sha-256 với client quá cũ |
Nâng driver/libpq |
| Tất cả | Query nhanh trước đây giờ chậm | Execution plan đổi, hoặc thiếu thống kê | ANALYZE / vacuumdb --analyze-in-stages, đọc EXPLAIN, chỉnh hint |
Sự cố thật trong case study là hai dòng đầu, nối tiếp nhau: app bị từ chối vì không dùng TLS (3159), và chính các lần thử lại liên tục đó làm IP app server bị chặn (1129). Ứng dụng mất kết nối khoảng 7 phút ngay sau khi tắt maintenance mode — lâu hơn cả downtime của database. Bài học: verify phải bao gồm "ứng dụng kết nối được", và hãy diff default parameter trước khi nâng. Toàn bộ câu chuyện: Case Study: MariaDB 10.5 Lên 11.8. Kỹ năng xử lý sự cố nói chung: Troubleshooting MySQL.
Checklist Tóm Tắt
Trước khi nâng:
- [ ] Đã biết version hiện tại, version đích, lịch hết hỗ trợ (và chi phí Extended Support nếu có).
- [ ] Đã đọc breaking changes và diff default parameter của hai family.
- [ ] PostgreSQL: đã kiểm extension, replication slot, prepared transaction, kiểu
reg*. - [ ] Đã tạo parameter group mới (carry-over + charset/timezone + TLS +
shared_preload_libraries). - [ ] Đã có điểm rollback (backup retention > 0 hoặc manual snapshot) và đã rehearsal nếu là production.
- [ ] Đã thông báo và được xác nhận; đã chuẩn bị banner/trang bảo trì frontend.
Trong khi nâng:
- [ ]
php artisan down, dừng worker/Horizon, tắt cronschedule:run. - [ ] Không còn query dài (
processlist/pg_stat_activity). - [ ] Chạy
modify-db-instance, theo dõidescribe-events.
Sau khi nâng:
- [ ] Verify version, charset/collation (hoặc encoding), timezone, TLS, extension, số bảng.
- [ ] Ứng dụng kết nối được (
migrate:status, API thật), rồi mớiphp artisan up, khôi phục cron, start worker. - [ ]
ANALYZE TABLEhoặcvacuumdb --analyze-in-stages, đo lại query quan trọng, chạy E2E. - [ ] Báo kết quả cho các bên; cập nhật Terraform/IaC để môi trường khác dùng cùng version.
Script hoàn chỉnh gom các bước trên (plan → params → snapshot → app-down → upgrade → verify → app-up → analyze): Runbook Nâng Cấp RDS.
Tổng Kết
Nâng cấp database trên RDS về mặt kỹ thuật chỉ là một lệnh modify-db-instance, dù bạn dùng MySQL, MariaDB hay PostgreSQL. Phần khó nằm ở mọi thứ xung quanh: biết mình đang ở đâu trong lịch hỗ trợ (và có đang trả phí Extended Support hay không), chọn đúng version theo tính năng cần có, phát hiện default thay đổi trước khi nó làm sập ứng dụng, đưa ứng dụng vào trạng thái an toàn, cho người dùng thấy thông báo thay vì lỗi, và kiểm tra từ phía ứng dụng chứ không chỉ từ client của mình.
Ba điều nên mang theo:
- Đo trước, đừng đoán — version, lịch hỗ trợ, thời gian downtime, execution plan đều đo được bằng lệnh.
- Mọi bước thủ công nên nằm trong script — maintenance mode, worker, cron, verify, analyze.
- Database "available" chưa có nghĩa là hệ thống đã ổn — chỉ khi ứng dụng kết nối và chạy đúng mới là xong.
Muốn đi sâu từng phần, bắt đầu từ bài đầu tiên của khoá Nâng Cấp Database Trên AWS RDS.
