Site logo
Tác giả
  • avatar Nguyễn Đức Xinh
    Name
    Nguyễn Đức Xinh
    Twitter
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:

  • ValidUpgradeTarget là 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_dump ghi 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:

  1. Ư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ó).
  2. 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.
  3. 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.
  4. Đồ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:

  1. Mang theo các tham số đã tự chỉnh ở group cũ (lọc --source user).
  2. Ghim charset/collation, timezone theo chuẩn của ứng dụng để version mới không tự đổi.
  3. 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ỉ ghi CHARSET utf8mb4 mà không ghi collation. Không ghim, MariaDB sẽ chọn utf8mb4_uca1400_ai_ci và dễ gây lỗi 1267 Illegal mix of collations khi JOIN với cột cũ. Với MySQL 8.x, tương tự là collation_server (mặc định utf8mb4_0900_ai_ci).
  • require_secure_transport = 0 chỉ 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ải time_zone), rds.force_ssl (thay cho require_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-up và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 header Retry-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 cron schedule:run.
  • [ ] Không còn query dài (processlist / pg_stat_activity).
  • [ ] Chạy modify-db-instance, theo dõi describe-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ới php artisan up, khôi phục cron, start worker.
  • [ ] ANALYZE TABLE hoặc vacuumdb --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:

  1. Đo trước, đừng đoán — version, lịch hỗ trợ, thời gian downtime, execution plan đều đo được bằng lệnh.
  2. Mọi bước thủ công nên nằm trong script — maintenance mode, worker, cron, verify, analyze.
  3. 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.