Menu
ESC

Nhập từ khóa để tìm kiếm

↑↓ Di chuyển
Enter Mở
ESC Đóng

Đang tải...

Bài 11 — Physical Data Model — DBMS-Specific Implementation

Data Modeling and ERD Mastery Bài 11/60

Mở đầu — vì sao bài này quan trọng

Hãy hình dung bạn vừa hoàn thành một bản thiết kế kiến trúc nhà tuyệt đẹp: phòng khách thoáng, bếp tiện nghi, cầu thang đặt đúng chỗ. Nhưng bản vẽ kiến trúc đó không thể đưa trực tiếp cho thợ xây. Người thợ cần bản vẽ thi công — ghi rõ tường này dày bao nhiêu phân, dùng gạch loại gì, cốt thép phi mấy, móng sâu bao nhiêu mét. Đó chính là sự khác biệt giữa Logical Data Model (bài trước) và Physical Data Model (bài này).

Ở các bài trước, bạn đã đi từ Conceptual (góc nhìn nghiệp vụ) sang Logical (chuẩn hóa, chi tiết nhưng vẫn độc lập với công nghệ). Physical Data Model là chặng cuối: nơi mô hình "chạm đất". Nó là implementation-ready model — nghĩa là bạn có thể lấy nó và sinh ra ngay những câu lệnh DDL SQL để thực thi trên một hệ quản trị cơ sở dữ liệu (DBMS) cụ thể: MySQL, PostgreSQL, Oracle, SQL Server...

Với vai trò Business Analyst (BA), bạn có thể tự hỏi: "Tôi đâu phải DBA, sao phải học cái này?" Câu trả lời rất thực tế. Khi bạn hiểu Physical Data Model, bạn nói được cùng ngôn ngữ với đội kỹ thuật. Bạn biết tại sao một thay đổi nghiệp vụ tưởng nhỏ — ví dụ "cho phép số điện thoại dài hơn" — lại kéo theo việc sửa kiểu dữ liệu, sửa index, thậm chí migration cả triệu dòng. Bạn ước lượng được tác động, viết được tài liệu bàn giao chính xác, và không bị "qua mặt" trong các cuộc họp kỹ thuật. Đây là kỹ năng giúp BA chuyển từ "người ghi yêu cầu" thành "người thiết kế giải pháp".

Khái niệm cốt lõi

Physical Data Model là gì

Physical Data Model (PDM) là biểu diễn của cơ sở dữ liệu ở mức vật lý — tức là cách dữ liệu thực sự được lưu trữ và tổ chức trong một DBMS xác định. Khác với Logical Model vốn trung lập về công nghệ, PDM gắn chặt với một sản phẩm cụ thể và phản ánh mọi đặc thù của sản phẩm đó.

Khi chuyển từ Logical sang Physical, có ba thay đổi lớn về thuật ngữ và bản chất:

  • Entity trở thành Table (bảng).
  • Attribute trở thành Column (cột) — và mỗi cột phải có một data type cụ thể của DBMS đó.
  • Relationship trở thành Foreign Key constraint (ràng buộc khóa ngoại) thật sự, được DBMS thực thi.
Ngoài ra, PDM bổ sung những thứ mà Logical Model không quan tâm: index, partition, tablespace, storage engine, collation (quy tắc so sánh/sắp xếp ký tự), và các tinh chỉnh hiệu năng khác.

Tại sao "DBMS-specific" lại quan trọng đến vậy

Điểm cốt lõi cần khắc sâu: không có Physical Model chung chung. Mỗi DBMS có một "phương ngữ" SQL và một bộ kiểu dữ liệu riêng. Cùng một khái niệm nghiệp vụ "một chuỗi văn bản dài tùy ý", nhưng:

  • PostgreSQL dùng TEXT.
  • MySQL có VARCHAR, TEXT, MEDIUMTEXT, LONGTEXT tùy độ dài.
  • Oracle truyền thống dùng CLOB hoặc VARCHAR2 (giới hạn 4000 byte trước phiên bản mở rộng).
  • SQL Server dùng VARCHAR(MAX) hoặc NVARCHAR(MAX).
Cùng khái niệm "khóa chính tự tăng":

  • MySQL: INT AUTO_INCREMENT.
  • PostgreSQL: SERIAL hoặc GENERATED ALWAYS AS IDENTITY.
  • Oracle: SEQUENCE kết hợp trigger, hoặc GENERATED AS IDENTITY từ phiên bản 12c.
  • SQL Server: INT IDENTITY(1,1).
Nếu bạn viết một Physical Model rồi nói "nó chạy mọi nơi", bạn đã hiểu sai bản chất. Physical Model là nơi bạn cam kết với một nền tảng.

Những thành phần điển hình của một Physical Model

Một PDM hoàn chỉnh thường mô tả:

  • Tên bảng và tên cột theo đúng quy ước đặt tên của dự án (snake_case, số nhiều hay số ít, tiền tố...).
  • Kiểu dữ liệu vật lý cho từng cột, kèm độ dài/độ chính xác (precision/scale).
  • NULL / NOT NULLDEFAULT cho từng cột.
  • Primary Key và chiến lược sinh khóa.
  • Foreign Key với hành vi khi xóa/cập nhật (ON DELETE CASCADE, RESTRICT, SET NULL...).
  • Index — bao gồm unique index, composite index.
  • Check constraint thực thi quy tắc nghiệp vụ ở tầng DB.
  • Cấu hình đặc thù: storage engine (InnoDB của MySQL), charset/collation (utf8mb4_unicode_ci để lưu tiếng Việt có dấu chuẩn), partition.

Ánh xạ kiểu dữ liệu — phần dễ sai nhất

Đây là nơi BA và đội dev hay vấp. Một "số tiền" trong Logical Model nghe đơn giản, nhưng ở Physical Model phải quyết định rất rõ ràng. Tuyệt đối không dùng FLOAT/DOUBLE cho tiền vì sai số dấu phẩy động sẽ làm lệch sổ sách. Thay vào đó dùng DECIMAL(15,2) (MySQL/PostgreSQL) hay NUMBER(15,2) (Oracle). Một "ngày sinh" thì dùng DATE, còn "thời điểm tạo bản ghi" cần độ chính xác đến giây và có múi giờ thì dùng TIMESTAMP (PostgreSQL có TIMESTAMPTZ lưu kèm timezone). Những lựa chọn này sẽ được đào sâu ở các bài chuyên đề về Money và Dates, nhưng bạn cần biết rằng chúng được "chốt" tại tầng Physical.

Tình huống thực tế

Tình huống 1 — Ngân hàng số chuyển từ thiết kế chung sang MySQL

Một fintech tại TP.HCM, giả định tên TimoPay, xây ví điện tử cho khoảng 800.000 người dùng. Đội BA bàn giao Logical Model gồm các thực thể Customer, Wallet, Transaction. Khi đội kỹ thuật biến nó thành Physical Model trên MySQL 8 (InnoDB), nhiều quyết định mới xuất hiện.

Cột balance (số dư) ban đầu được dev nháp là FLOAT. Trong buổi review, BA chỉ ra rằng với hơn 50.000 giao dịch mỗi ngày, sai số dấu phẩy động có thể khiến tổng số dư lệch vài đồng mỗi tháng — điều không thể chấp nhận trong ngân hàng. Cột được đổi thành DECIMAL(18,2). Cột full_name được đặt charset utf8mb4 với collation utf8mb4_unicode_ci để lưu đúng tên tiếng Việt như "Nguyễn Thị Hồng Ánh" và sắp xếp đúng thứ tự. Cột phone dùng VARCHAR(15) chứ không phải INT, vì số điện thoại có số 0 đứng đầu và không bao giờ đem ra tính toán.

Bài học: Physical Model là nơi các quyết định "nhỏ" về kiểu dữ liệu tạo ra hậu quả "lớn" về tính đúng đắn và pháp lý. BA tham gia review tầng này giúp chặn lỗi từ trong trứng nước.

Tình huống 2 — Sàn thương mại điện tử chọn PostgreSQL vì JSON và full-text

Một startup bán lẻ tại Hà Nội, giả định ChợViệt, ban đầu định dùng MySQL nhưng cuối cùng chọn PostgreSQL 15. Lý do nằm hoàn toàn ở tầng Physical Model. Bảng product của họ có một thuộc tính "thông số kỹ thuật" rất linh hoạt — điện thoại có RAM/ROM, áo có size/màu, sách có số trang. Trên PostgreSQL, họ dùng kiểu JSONB cho cột attributes và đánh GIN index lên đó để tìm kiếm nhanh trong JSON. Trên MySQL phiên bản họ định dùng, khả năng index JSON kém linh hoạt hơn.

Khi viết Physical Model, cùng một Logical Model "Product có nhiều thuộc tính động", hai DBMS cho ra hai thiết kế khác hẳn nhau: PostgreSQL gói gọn trong một cột JSONB; nếu là MySQL cũ, có thể phải tách ra bảng product_attribute riêng theo mô hình EAV. Họ cũng dùng kiểu SERIAL cho khóa chính và TIMESTAMPTZ cho cột created_at để xử lý đơn hàng từ khách ở nhiều múi giờ.

Bài học: Việc chọn DBMS và việc thiết kế Physical Model là hai mặt của một quyết định. Năng lực đặc thù của nền tảng (như JSONB của PostgreSQL) có thể thay đổi cả cấu trúc bảng, không chỉ thay đổi tên kiểu dữ liệu.

Tình huống 3 — Doanh nghiệp viễn thông và bài toán phân vùng trên Oracle

Một nhà mạng lớn (giả định VietTelData) lưu bản ghi cước cuộc gọi (CDR) với khoảng 2 tỷ dòng mỗi tháng trên Oracle. Logical Model chỉ nói "có một thực thể CallRecord với ngày giờ cuộc gọi". Nhưng ở Physical Model, đội kỹ thuật bắt buộc phải dùng partition theo tháng (PARTITION BY RANGE trên cột call_date). Nhờ vậy, khi truy vấn cước của tháng 5, Oracle chỉ quét đúng phân vùng tháng 5 thay vì quét cả bảng khổng lồ.

Họ cũng dùng NUMBER cho định danh thay vì INT, dùng VARCHAR2 thay cho VARCHAR, và tạo SEQUENCE riêng để sinh khóa. Đây đều là đặc thù Oracle mà nếu bê nguyên cú pháp MySQL sang sẽ chạy lỗi ngay.

Bài học: Ở quy mô lớn, Physical Model không chỉ là "dịch kiểu dữ liệu" mà còn là chiến lược tổ chức lưu trữ vật lý (partition, tablespace). Đây là lúc mô hình dữ liệu trực tiếp quyết định hiệu năng hệ thống.

Hướng dẫn từng bước

Dưới đây là quy trình chuyển một Logical Model thành Physical Model, minh họa bằng MySQL và đối chiếu PostgreSQL.

Bước 1 — Chốt DBMS mục tiêu và phiên bản. Trước khi viết một dòng nào, xác nhận: MySQL 8? PostgreSQL 15? Oracle 19c? Phiên bản quan trọng vì tính năng (ví dụ IDENTITY của Oracle chỉ có từ 12c). Ghi rõ vào tài liệu.

Bước 2 — Áp quy ước đặt tên. Quyết định bảng dùng số ít hay số nhiều, snake_case hay camelCase, có tiền tố không. Ví dụ thống nhất: bảng snake_case số ít (customer, order_item), khóa chính tên id, khóa ngoại tên <bảng>_id.

Bước 3 — Ánh xạ từng attribute sang data type. Lập một bảng đối chiếu. Ví dụ:

  • "Mã khách hàng" → BIGINT (khóa chính tự tăng).
  • "Họ tên" → VARCHAR(150) charset utf8mb4.
  • "Số dư" → DECIMAL(18,2).
  • "Email" → VARCHAR(255).
  • "Ngày tạo" → TIMESTAMP DEFAULT CURRENT_TIMESTAMP.
Bước 4 — Xác định NULL, DEFAULT, và constraint. Cột nào bắt buộc thì NOT NULL. Cột nào có giá trị mặc định thì khai báo DEFAULT. Quy tắc nghiệp vụ đơn giản đưa vào CHECK (ví dụ CHECK (balance >= 0)).

Bước 5 — Khai báo khóa chính và chiến lược sinh khóa. MySQL: id BIGINT AUTO_INCREMENT PRIMARY KEY. PostgreSQL: id BIGSERIAL PRIMARY KEY hoặc GENERATED ALWAYS AS IDENTITY.

Bước 6 — Khai báo khóa ngoại và hành vi tham chiếu. Quyết định khi xóa cha thì con xử lý ra sao: ON DELETE RESTRICT (chặn xóa), CASCADE (xóa lan), hay SET NULL. Đây là quyết định nghiệp vụ, BA phải có ý kiến.

Bước 7 — Thiết kế index. Đánh index cho các cột thường dùng trong điều kiện tìm kiếm và join. Thêm UNIQUE index cho các cột không được trùng (như email).

Bước 8 — Sinh DDL và rà soát. Ví dụ một bảng hoàn chỉnh trên MySQL:

CREATE TABLE customer (
    id          BIGINT       NOT NULL AUTO_INCREMENT,
    full_name   VARCHAR(150) NOT NULL,
    email       VARCHAR(255) NOT NULL,
    phone       VARCHAR(15),
    balance     DECIMAL(18,2) NOT NULL DEFAULT 0.00,
    created_at  TIMESTAMP    NOT NULL DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (id),
    UNIQUE KEY uq_customer_email (email),
    CONSTRAINT chk_balance_nonneg CHECK (balance >= 0)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE INDEX idx_customer_phone ON customer (phone);

Cũng bảng đó trên PostgreSQL sẽ là:

CREATE TABLE customer (
    id          BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    full_name   VARCHAR(150) NOT NULL,
    email       VARCHAR(255) NOT NULL UNIQUE,
    phone       VARCHAR(15),
    balance     NUMERIC(18,2) NOT NULL DEFAULT 0.00 CHECK (balance >= 0),
    created_at  TIMESTAMPTZ  NOT NULL DEFAULT now()
);

CREATE INDEX idx_customer_phone ON customer (phone);

So sánh hai đoạn trên, bạn thấy ngay: cùng một ý nghĩa nghiệp vụ, nhưng cú pháp sinh khóa, kiểu thời gian, cách khai báo unique và charset đều khác nhau. Đó chính là tinh thần "DBMS-specific".

Lỗi thường gặp & mẹo

Lỗi 1 — Bê nguyên cú pháp giữa các DBMS. Viết AUTO_INCREMENT rồi đem chạy trên PostgreSQL sẽ lỗi cú pháp. Luôn xác nhận DBMS trước, và nếu dự án có thể đổi nền tảng, hãy giữ Logical Model sạch để tái ánh xạ.

Lỗi 2 — Dùng FLOAT/DOUBLE cho tiền. Đây là lỗi kinh điển gây sai sổ sách. Tiền luôn dùng DECIMAL/NUMERIC với scale cố định.

Lỗi 3 — Quên charset/collation cho tiếng Việt. Nếu để charset mặc định latin1, tên "Trần Quốc Tuấn" sẽ thành ký tự lỗi. Luôn dùng utf8mb4 trên MySQL. Đây là lỗi rất phổ biến với hệ thống Việt Nam.

Lỗi 4 — Lưu số điện thoại, CMND/CCCD bằng kiểu số. Số 0 đầu sẽ bị mất, và bạn chẳng bao giờ cộng trừ các giá trị này. Dùng VARCHAR.

Lỗi 5 — Đánh index tràn lan hoặc không đánh index nào. Quá nhiều index làm chậm thao tác ghi; quá ít làm chậm thao tác đọc. Đánh index dựa trên truy vấn thực tế.

Lỗi 6 — Bỏ qua hành vi ON DELETE của khóa ngoại. Để mặc định mà không cân nhắc có thể khiến xóa một khách hàng kéo theo xóa cả lịch sử giao dịch (CASCADE) — một thảm họa dữ liệu. BA phải xác nhận hành vi này theo nghiệp vụ.

Mẹo: Dùng công cụ như dbdiagram.io hoặc tính năng "generate DDL" của Lucidchart để sinh nhanh khung DDL, rồi tinh chỉnh tay theo đặc thù DBMS. Luôn lưu file DDL vào version control (Git) để theo dõi thay đổi schema theo thời gian.

Bài tập thực hành

Cho Logical Model đơn giản của một hệ thống đặt phòng khách sạn tại Đà Nẵng gồm ba thực thể:

  • Khách (mã khách, họ tên, số CCCD, số điện thoại, email).
  • Phòng (mã phòng, số phòng, loại phòng, giá mỗi đêm).
  • ĐặtPhòng (mã đặt, khách nào, phòng nào, ngày nhận, ngày trả, tổng tiền, trạng thái).
Yêu cầu:

  • Chọn một DBMS (MySQL hoặc PostgreSQL) và ghi rõ phiên bản.
  • Viết DDL CREATE TABLE đầy đủ cho cả ba bảng, chú ý: kiểu dữ liệu phù hợp cho từng cột (đặc biệt là tiền, số CCCD, ngày tháng), charset cho tiếng Việt, khóa chính, khóa ngoại nối ba bảng.
  • Thêm CHECK để đảm bảo ngày trả luôn sau ngày nhận, và tổng tiền >= 0.
  • Quyết định hành vi ON DELETE cho khóa ngoại từ ĐặtPhòng tới KháchPhòng, giải thích lựa chọn.
  • Thêm ít nhất hai index hợp lý (ví dụ tìm đặt phòng theo ngày nhận, tìm khách theo số điện thoại) và giải thích vì sao.
  • Nâng cao: Viết lại bảng ĐặtPhòng cho DBMS còn lại (cái bạn chưa chọn ở bước 1) và liệt kê mọi điểm cú pháp phải thay đổi.
Khi làm xong, hãy tự kiểm tra: nếu đưa file DDL này cho một dev, họ có chạy được ngay không? Nếu có, bạn đã đạt mục tiêu "implementation-ready".

Tóm tắt

Physical Data Model là chặng cuối của hành trình mô hình hóa: nơi thiết kế trừu tượng biến thành cấu trúc cụ thể, sẵn sàng thực thi dưới dạng DDL SQL trên một DBMS xác định. Ba điểm cốt lõi cần nhớ:

  • Physical Model luôn DBMS-specific. Entity thành table, attribute thành column có data type cụ thể, relationship thành foreign key thật. MySQL, PostgreSQL, Oracle có phương ngữ và kiểu dữ liệu khác nhau — không có thiết kế "chạy mọi nơi".
  • Các quyết định ở tầng này có hậu quả lớn: chọn sai kiểu cho tiền gây lệch sổ sách, quên charset làm hỏng tiếng Việt, thiếu index làm chậm hệ thống, sai hành vi ON DELETE có thể xóa nhầm dữ liệu lịch sử.
  • BA không cần viết DDL hằng ngày, nhưng cần hiểu nó để review thiết kế, ước lượng tác động thay đổi, và giao tiếp chính xác với đội kỹ thuật.
Khi bạn nắm vững Physical Model, bạn đã đi trọn con đường từ ý tưởng nghiệp vụ đến cơ sở dữ liệu vận hành — một năng lực giúp BA trở thành cầu nối thực sự giữa kinh doanh và công nghệ.