Khóa chính thật sự làm gì
Khóa chính (primary key) là cột (hoặc tổ hợp cột) xác định duy nhất mỗi hàng trong bảng. Hai hàng không thể có cùng giá trị khóa chính. SQLite đảm bảo điều đó cho bạn, và dùng khóa để tìm hàng nhanh chóng.
Dạng đơn giản nhất được viết ngay trên một cột:
Bạn không cung cấp id và SQLite tự điền vào. Đó không phải phép màu mà là một trường hợp đặc biệt của INTEGER PRIMARY KEY, đáng để hiểu trước khi viết thêm bất cứ thứ gì.
INTEGER PRIMARY KEY là trường hợp đặc biệt
Trong hầu hết cơ sở dữ liệu, khóa chính chỉ là một chỉ mục unique. Trong SQLite, mọi bảng thông thường đã có sẵn một số nguyên 64 bit ẩn gọi là rowid để xác định hàng ở bên trong. Khi bạn khai báo một cột đúng là INTEGER PRIMARY KEY, cột đó trở thành rowid. Không có chỉ mục phụ, không tốn thêm dung lượng: id của bạn và vị trí vật lý của hàng là một.
id và rowid là cùng một cột với hai cái tên. Tra cứu theo id đi thẳng tới hàng, không có cây thứ hai phải duyệt. Đó là lý do lời khuyên chuẩn cho SQLite là: nếu muốn khóa chính dạng số, hãy viết đúng INTEGER PRIMARY KEY. Không phải INT, không phải BIGINT, không phải INTEGER NOT NULL PRIMARY KEY (thật ra cái đó cũng chạy được, nhưng kiểu phải là INTEGER).
Các kiểu khác vẫn dùng được, chỉ là chúng có một chỉ mục unique riêng. Điều đó không sao, chỉ là không gọn bằng.
AUTOINCREMENT thường là không cần thiết
Một phản xạ quen thuộc từ các cơ sở dữ liệu khác là viết id INTEGER PRIMARY KEY AUTOINCREMENT. Trong SQLite, từ khóa AUTOINCREMENT làm một việc hẹp hơn cái tên gợi ý, và phần lớn thời gian bạn không cần nó.
Không có AUTOINCREMENT, cột INTEGER PRIMARY KEY tự điền giá trị lớn hơn rowid lớn nhất hiện có một đơn vị. Nếu bạn xóa hàng cuối cùng, lần insert tiếp theo có thể dùng lại id đó.
Có AUTOINCREMENT, SQLite ghi lại id cao nhất từng được dùng trong một bảng phụ tên sqlite_sequence và không bao giờ dùng lại giá trị, kể cả sau khi xóa.
Bảng plain dùng lại id 3. Bảng có AUTOINCREMENT nhảy lên 4. Trừ khi bạn có lý do thật sự để cấm dùng lại id (kiểm toán, tham chiếu bên ngoài vẫn còn sau khi xóa), hãy bỏ qua AUTOINCREMENT. Nó tốn thêm một lần ghi cho mỗi lần insert và một bảng sổ sách riêng.
Khóa chính ghép
Đôi khi một cột là không đủ. Ví dụ, một bảng nối ánh xạ người dùng với vai trò được xác định duy nhất bởi cặp (user_id, role_id). Khi đó, hãy khai báo khóa ở cấp bảng:
Cặp giá trị phải duy nhất trong toàn bảng: (1, 10) chỉ được xuất hiện một lần. Mỗi cột riêng lẻ thì có thể lặp lại thoải mái. Đó chính là mục đích: mỗi người dùng có thể có nhiều vai trò, mỗi vai trò có thể có nhiều người dùng, nhưng một cặp người dùng và vai trò cụ thể chỉ tồn tại tối đa một lần.
Khóa chính ghép tạo một chỉ mục riêng bao gồm các cột được liệt kê. Nó không trở thành rowid; chỉ một INTEGER PRIMARY KEY trên một cột mới được đối xử như vậy.
Cái bẫy NULL trong khóa chính
Đây là một điểm kỳ quặc làm bất ngờ những người đến từ PostgreSQL hay MySQL: trong bảng SQLite thông thường, cột khóa chính không phải INTEGER PRIMARY KEY có thể chứa NULL. Đó là một bug lâu đời mà các tác giả SQLite giữ lại để tương thích ngược.
Hai hàng có NULL đã lọt qua khóa chính. Cách sửa là thêm NOT NULL một cách rõ ràng trên mọi cột khóa chính không phải số nguyên:
Hoặc dùng bảng STRICT, nơi bug NULL trong khóa chính đã được sửa. Thói quen viết NOT NULL trên mọi cột khóa chính là một khoản bảo hiểm rẻ.
Khóa chính và UNIQUE
Cả hai đều ngăn trùng lặp. Những khác biệt:
- Một bảng có tối đa một khóa chính, nhưng có thể có nhiều ràng buộc
UNIQUE. - Khóa chính là định danh "chính" của bảng; khóa ngoại mặc định trỏ tới nó.
INTEGER PRIMARY KEYtrở thành rowid; một cột số nguyênUNIQUEthì không.- Cột
UNIQUEvui vẻ chấp nhận nhiềuNULL(mỗi NULL được coi là khác nhau).
id là danh tính của hàng. email và username cũng duy nhất, nhưng chúng là thuộc tính nghiệp vụ: chúng có thể thay đổi, còn id thì không nên.
Thêm khóa chính sau này (phần lớn là: đừng)
ALTER TABLE của SQLite khá hạn chế. Bạn không thể chạy ALTER TABLE ... ADD PRIMARY KEY vì câu lệnh đó không tồn tại. Nếu bạn quên khóa chính và bảng đã có dữ liệu, cách làm là tạo lại bảng:
Đây là điệu nhảy migration chuẩn của SQLite. Trong code thật, hãy bọc nó trong một giao dịch, và tạm tắt khóa ngoại nếu có bảng khác tham chiếu tới bảng này. Bài học: hãy đặt khóa chính cho đúng ngay lúc CREATE TABLE.
Danh sách kiểm tra nhanh
Khi viết một bảng mới, hãy tự hỏi:
- Hàng này có id duy nhất tự nhiên không? Nếu đó là một số nguyên, dùng
INTEGER PRIMARY KEY. - Danh tính có thật ra là tổ hợp nhiều cột (một bảng nối) không? Dùng
PRIMARY KEY (col_a, col_b)ở cấp bảng. - Khóa là văn bản hay một kiểu không phải số nguyên? Thêm
NOT NULLmột cách rõ ràng. - Bạn có thật sự cần
AUTOINCREMENTkhông? Có lẽ là không. - Bảng nhỏ, chủ yếu để đọc, với khóa chính không phải số nguyên? Cân nhắc
WITHOUT ROWID(được trình bày trong trang về rowid).
Tiếp theo: rowid
INTEGER PRIMARY KEY đã xuất hiện thoáng qua như "bí danh của rowid", nhưng rowid là nền móng bên dưới mọi bảng SQLite thông thường và đáng được tìm hiểu trực tiếp. Đó là trang tiếp theo.
Câu hỏi thường gặp
Làm sao khai báo khóa chính trong SQLite?
Thêm PRIMARY KEY vào một cột trong câu lệnh CREATE TABLE, ví dụ id INTEGER PRIMARY KEY. Với khóa trải trên nhiều cột, dùng ràng buộc ở cấp bảng: PRIMARY KEY (col_a, col_b). Cột hoặc tổ hợp cột đó phải duy nhất giữa các hàng.
INTEGER PRIMARY KEY khác các khóa chính khác trong SQLite thế nào?
INTEGER PRIMARY KEY là trường hợp đặc biệt: nó trở thành bí danh của rowid có sẵn trong bảng, nên được lưu thẳng trong B-tree mà không cần chỉ mục phụ. Mọi kiểu khác, hoặc khóa ghép, đều có một chỉ mục unique riêng. Với id dạng số trên một cột, INTEGER PRIMARY KEY nhanh hơn và gọn hơn.
Khóa chính trong SQLite có cần AUTOINCREMENT không?
Thường là không. INTEGER PRIMARY KEY đã tự gán một rowid duy nhất khi bạn insert NULL. AUTOINCREMENT chỉ thêm đảm bảo rằng id không bao giờ bị dùng lại sau khi xóa, với cái giá là thêm một bảng sqlite_sequence. Hãy bỏ qua nó trừ khi bạn thật sự cần id tăng đơn điệu.
Vì sao khóa chính trong SQLite của tôi lại cho phép NULL?
Đây là một bug lịch sử được giữ lại để tương thích: trong bảng thông thường, cột khóa chính không phải INTEGER có thể nhận NULL trừ khi bạn thêm NOT NULL một cách rõ ràng. Cột INTEGER PRIMARY KEY là ngoại lệ, nó không bao giờ cho phép NULL. Để an toàn, hãy viết NOT NULL trên mọi cột khóa chính, hoặc dùng bảng STRICT, nơi quy tắc này được áp dụng đúng.