讲解

CREATE TABLE 定义一张新表:表名、每列的名字和类型、以及各种约束。SQLite 的列类型比较宽松(叫类型亲和),常用 TEXT、INTEGER、REAL、BLOB 四类;MySQL/PostgreSQL 的类型系统更严格(VARCHAR(n)、DECIMAL、TIMESTAMP 等)。比类型更重要的是约束:PRIMARY KEY 主键、NOT NULL 非空、UNIQUE 唯一、DEFAULT 默认值、CHECK 检查表达式、REFERENCES 外键——约束是数据质量的第一道防线,能挡住大量脏数据。

索引用 CREATE INDEX 创建,作用是给查询加速:没有索引时 WHERE city = '北京' 要逐行扫描全表,有了索引就像书的目录一样直接定位。代价是每次写入都要维护索引,所以索引不是越多越好——给高频出现在 WHERE、JOIN ON、ORDER BY 里的列建索引,低频列不建。多列索引(复合索引)遵循「最左前缀」原则:索引 (a, b) 能加速只按 a 过滤的查询,但加速不了只按 b 过滤的。

两个 SQLite 特有的注意点:一是 INTEGER PRIMARY KEY 列就是 rowid 的别名,自带自增语义;二是 SQLite 默认不强制外键约束,需要每个连接执行 PRAGMA foreign_keys = ON 才会检查 REFERENCES——这也是很多教程数据「孤儿行」(比如本教程 student_id = 9 的订单)能在 SQLite 里存在的原因。其他数据库默认强制外键,这类脏数据根本插不进去。

示例

建一张带完整约束的商品表:主键、非空、唯一、默认值、检查约束:

CREATE TABLE products (
  id INTEGER PRIMARY KEY,
  sku TEXT NOT NULL UNIQUE,
  name TEXT NOT NULL,
  price REAL NOT NULL DEFAULT 0 CHECK (price >= 0),
  stock INTEGER NOT NULL DEFAULT 0,
  created_at TEXT NOT NULL DEFAULT (DATE('now'))
);

插入几条数据并给 name 建索引,违反约束(负价格)的行会被拒绝(这里只插合法的):

INSERT INTO products (sku, name, price, stock) VALUES
  ('KB-001', '机械键盘', 299.0, 50),
  ('MS-002', '无线鼠标', 99.0, 200),
  ('PD-003', '显示器支架', 159.0, 0);

CREATE INDEX idx_products_name ON products (name);

SELECT id, sku, name, price, created_at FROM products WHERE name = '无线鼠标';

约束定义保存在系统表 sqlite_master 里,可以随时查看;试图插入负价格或重复 sku 的行会被数据库直接报错拒绝(读者可自行验证,这里确认约束确实写进了表结构):

SELECT sql FROM sqlite_master WHERE name = 'products';

SELECT COUNT(*) AS product_count FROM products;

常见坑

  • 只靠程序校验、不加数据库约束:程序有 bug 或绕过程序写数据时,约束是最后一道防线。NOT NULL、UNIQUE、CHECK、外键,能在数据库层表达的就别留给应用层。
  • 盲目给所有列建索引:每个索引都拖慢写入、占用存储,低频查询列的索引纯属负担。从真实慢查询出发建索引,用 EXPLAIN QUERY PLAN 验证索引真的被用上。
  • 在 SQLite 里以为外键天然生效:不打开 PRAGMA foreign_keys = ON,REFERENCES 只是摆设,子表可以随意引用不存在的父行。需要外键保护时记得开启,或换默认强制的数据库。
  • 复合索引顺序随意:索引 (a, b) 对 WHERE b = ? 无能为力。把等值过滤最多、选择性最好的列放前面,顺序错了等于白建。

小结

CREATE TABLE 的核心是约束设计,CREATE INDEX 的核心是按需加速;SQLite 的类型亲和与默认关闭的外键是两个特殊点。下一节总结全教程的最佳实践。