aedev.net
  • Trang chủ
  • AI
  • Mobile
  • Flutter
  • React Native
  • Backend
  • DevOps
  • Hướng dẫn
No Result
View All Result
SAVED POSTS
aedev.net
  • Trang chủ
  • AI
  • Mobile
  • Flutter
  • React Native
  • Backend
  • DevOps
  • Hướng dẫn
No Result
View All Result
aedev.net
No Result
View All Result
Minh họa kiến trúc backend API gọn gàng cho sản phẩm nhỏ

SQLite: có FOREIGN KEY nhưng vẫn lọt dữ liệu mồ côi?

Suong Nguyen by Suong Nguyen
31/10/2026
in Backend
0
585
SHARES
3.2k
VIEWS
Summarize with ChatGPTShare to Facebook

Schema đã khai báo đơn hàng tham chiếu khách hàng, nhưng một script vẫn ghi được customer_id không tồn tại. Trước khi sửa toàn bộ lớp truy cập dữ liệu, hãy kiểm tra connection đó có thực sự bật kiểm tra khóa ngoại hay chưa.

Bài này tập trung SQLite, không suy rộng cách cấu hình sang PostgreSQL. Ví dụ đã chạy bằng Python 3.9.6 với SQLite runtime 3.54.0, đối chiếu tài liệu ngày 31/10/2026. Mỗi database thử nằm trong RAM, không mở database của ứng dụng.

1. Đọc lại trạng thái trên đúng connection

PRAGMA foreign_keys trả trạng thái thực thi constraint trên connection hiện tại. Không suy ra trạng thái của toàn bộ ứng dụng từ một tab console khác. Connection mới hoặc pool khác cần được khởi tạo theo chính sách của dự án.

SQLite cho phép cấu hình mặc định khi biên dịch, nên cách ổn định là chủ động đặt giá trị mong muốn và đọc lại. Nếu truy vấn không trả hàng 0 hoặc 1, kiểm tra runtime có hỗ trợ foreign key thay vì coi thiếu kết quả là đã bật.

PRAGMA foreign_keys = ON;
PRAGMA foreign_keys;

Thực hiện bước khởi tạo trước khi bắt đầu transaction. Theo tài liệu SQLite, đổi giá trị khi đang có transaction không bật hoặc tắt enforcement như bạn có thể kỳ vọng; thao tác đó không có hiệu lực trong transaction đang mở.

2. Một phép thử có đối chứng

Đoạn Python sau cố tình tạo hai connection với chế độ khác nhau. Hai database có cùng schema nhưng độc lập, để kết quả không phụ thuộc một file local đã có dữ liệu. isolation_level=None giữ ví dụ trong chế độ autocommit, tránh một transaction ngầm che mất thời điểm đặt PRAGMA.

import sqlite3

def demo(enabled):
    db = sqlite3.connect(":memory:", isolation_level=None)
    try:
        db.execute("PRAGMA foreign_keys = " + ("ON" if enabled else "OFF"))
        actual = db.execute("PRAGMA foreign_keys").fetchone()
        assert actual == (int(enabled),)
        db.executescript("""
            CREATE TABLE customers(id INTEGER PRIMARY KEY);
            CREATE TABLE orders(
                id INTEGER PRIMARY KEY,
                customer_id INTEGER NOT NULL REFERENCES customers(id)
            );
            INSERT INTO customers VALUES (1);
            INSERT INTO orders VALUES (10, 1);
        """)
        rejected = False
        try:
            db.execute("INSERT INTO orders VALUES (?, ?)", (11, 999))
        except sqlite3.IntegrityError:
            rejected = True
        count = db.execute("SELECT COUNT(*) FROM orders").fetchone()[0]
        violations = db.execute("PRAGMA foreign_key_check").fetchall()
        assert rejected == enabled
        assert count == (1 if enabled else 2)
        assert len(violations) == (0 if enabled else 1)
        print(enabled, rejected, count, len(violations))
    finally:
        db.close()

print("SQLite", sqlite3.sqlite_version)
demo(False)
demo(True)

Hai dòng kết quả của fixture là False False 2 1 và True True 1 0. Khi tắt kiểm tra, đơn 11 được ghi và phép kiểm tra tìm được một vi phạm. Khi bật, insert sai bị từ chối, đơn hợp lệ vẫn còn.

Đây là lỗi được tạo có chủ đích trong database tạm. Không tắt constraint trên dữ liệu thật để xem một insert sai có lọt hay không. Có thể đưa phép thử tương đương vào bộ kiểm thử tạo connection của ứng dụng.

3. Bật enforcement không sửa dữ liệu cũ

Giả sử dữ liệu mồ côi đã được ghi trong quá khứ. Đặt ON chỉ thay đổi cách kiểm tra các thao tác tiếp theo; đó không phải lệnh sửa các dòng đã sai. PRAGMA foreign_key_check giúp liệt kê vi phạm để bạn lập kế hoạch xử lý.

Không tự xóa tất cả các hàng được trả về. Một customer_id sai có thể cần khôi phục khách hàng, sửa liên kết hoặc xóa đơn theo quyết định nghiệp vụ. Ghi lại khóa và đối chiếu nguồn dữ liệu trước khi lựa chọn, rồi chạy lại kiểm tra sau thao tác đã được duyệt.

4. Đặt kiểm tra ở nơi tạo connection

Nếu dự án dùng pool, tìm hook khởi tạo connection của driver hoặc ORM đang dùng. Thực hiện PRAGMA và đọc lại ở đó, trước transaction nghiệp vụ. Không đặt một lần trong migration rồi cho rằng mọi worker hoặc script chạy sau sẽ kế thừa.

Trong ví dụ, cột tham chiếu có NOT NULL vì mỗi đơn phải có khách hàng. Foreign key tự nó không bắt buộc giá trị khác NULL. Quy tắc dữ liệu bắt buộc và quy tắc tồn tại ở bảng cha là hai điều cần khai báo rõ trong schema.

Kiểm tra và giới hạn

Thử đường đi hợp lệ, insert tham chiếu sai, cùng một script quản trị mở connection mới. Với ứng dụng có transaction, kiểm tra thời điểm PRAGMA thay vì chỉ đọc log khởi động. Các nhánh ON/OFF trong bài đã chạy thật, nhưng chưa kiểm thử pool hoặc ORM cụ thể.

Khi thấy constraint không hoạt động, kiểm tra theo thứ tự: đúng runtime, đúng connection, đúng thời điểm và đúng schema. Cách này giúp tìm nơi bỏ sót enforcement trước khi thêm các truy vấn kiểm tra thủ công ở mọi chỗ ghi dữ liệu.

Nguồn tham khảo

  • SQLite: bật foreign key support
  • SQLite: PRAGMA foreign_keys và foreign_key_check
  • Python 3.9: sqlite3 connection và transaction

Tags: DatabaseSQLiteToàn vẹn dữ liệu
SummarizeShare234
Suong Nguyen

Suong Nguyen

Related Stories

Minh họa kiến trúc backend API gọn gàng cho sản phẩm nhỏ

Lọc thời gian trong SQL: dùng khoảng nửa mở để không trùng biên

by Suong Nguyen
01/11/2026
0

So sánh BETWEEN với điều kiện >= đầu kỳ và < đầu kỳ kế tiếp bằng fixture ở đúng ranh giới tháng.

Minh họa kiến trúc backend API gọn gàng cho sản phẩm nhỏ

UNIQUE và NULL trong PostgreSQL: chốt quy tắc cho khóa tùy chọn

by Suong Nguyen
30/10/2026
0

Phân biệt cột tùy chọn có UNIQUE, UNIQUE NULLS NOT DISTINCT và NOT NULL UNIQUE trước khi viết migration.

Minh họa kiến trúc backend API gọn gàng cho sản phẩm nhỏ

SQL WHERE và HAVING: lọc đúng dữ liệu trước và sau khi gom nhóm

by Suong Nguyen
29/10/2026
0

Dùng ví dụ đơn hàng để phân biệt điều kiện trên từng dòng với điều kiện trên tổng của một nhóm.

Minh họa kiến trúc backend API gọn gàng cho sản phẩm nhỏ

SQL UNION và UNION ALL: đừng làm mất sự kiện khi hợp nhất dữ liệu

by Suong Nguyen
28/10/2026
0

Dùng fixture log web và mobile để thấy UNION loại trùng theo toàn bộ cột được chọn, còn UNION ALL giữ số lần xuất hiện.

Next Post
Minh họa kiến trúc backend API gọn gàng cho sản phẩm nhỏ

String.length và Buffer.byteLength: chọn phép đo cho UTF‑8

0 0 votes
Article Rating
Subscribe
Login
Notify of
guest
guest
0 Comments
Oldest
Newest Most Voted
AE Dev

AE Dev mang đến các bài viết về lập trình, mobile app, backend, frontend, WordPress và công nghệ, phù hợp cho developer muốn học hỏi và cập nhật kiến thức thực tế.

Recent Posts

  • React Native useColorScheme: đổi theme mà không cache màu từ module
  • Return trong finally có thể che cả return lẫn lỗi ban đầu
  • Android Intent extra: thiếu dữ liệu khác với giá trị 0

Categories

  • AI
  • Backend
  • Đánh giá
  • DevOps
  • Flutter
  • Hướng dẫn
  • Lập trình Mobile
  • Phỏng vấn
  • React Native
  • Uncategorized

Weekly Newsletter

  • Buy JNews
  • Support Forum
  • Pre-sale Question
  • Contact Us

© 2026 AE Dev - Blog công nghệ, lập trình và chia sẻ kiến thức IT.

Welcome Back!

Login to your account below

Forgotten Password?

Retrieve your password

Please enter your username or email address to reset your password.

Log In
No Result
View All Result
  • Trang chủ
  • AI
  • Mobile
  • React Native
  • Backend
  • DevOps
  • Hướng dẫn
  • Flutter

© 2026 AE Dev - Blog công nghệ, lập trình và chia sẻ kiến thức IT.

wpDiscuz
0
0
Would love your thoughts, please comment.x
()
x
| Reply