🦏 第9章 列的属性
📄 一句话概括

主键 = 非空 + 唯一,但在这个沙箱有两个例外:INTEGER PRIMARY KEY 给 NULL 会自动填号; TEXT PRIMARY KEY 竟然也能存 NULL。 一张表只能有一个主键,报 has more than one primary key。 想「两列一起当身份证」得写复合主键 PRIMARY KEY (a, b)。 主键和唯一键的区别: 主键是「这一行是谁」,唯一键是「这一列不许重」。 主键只有一个、不能空;唯一键可以有多个、能空。

📝 本章和前面几章的关系

上一篇讲唯一,这一篇讲它的「加强版」——主键。 主键是整张表的身份基础: 别的表靠它指过来、程序靠它定位某一行、更新删除靠它保证只动一行。 没有主键的表,改数据时就是在赌。

一、整型主键:给 NULL 会自己填号

id INTEGER PRIMARY KEY,插入时不写 id,它自己填了 1。 INTEGER PRIMARY KEY 在 SQLite 里是 rowid 的别名, 所以给 NULL 等于「你帮我编一个」。 这一条是 SQLite 独有的—— MySQL 的主键给 NULL 直接报错,要么显式给值、要么靠 AUTO_INCREMENT。 注意「INTEGER 才行」:写 INT 就没有这个待遇, 它会老老实实报非空错误。 一个字母的差别,行为完全不同。

二、重复就拦

同一个 id 插不进去,换个 id 但名字一样完全没问题。 主键管的是「哪一行」,不是「哪一行的内容」。 报错是 UNIQUE constraint failed: demo_pk.id——注意它说的是 UNIQUE 而不是 PRIMARY KEY, 因为主键在底层就是一个唯一索引。 看报错时别被这个词骗了,它说的是同一件事。

三、文本主键:这里能空,MySQL 不能

code TEXT PRIMARY KEY 竟然接受了 NULL。 这是 SQLite 为兼容老代码保留的历史问题,官方文档里明确承认了。 MySQL 的文本主键写 NULL 直接报错。 所以别指望「主键一定非空」在所有数据库上都成立—— 要保证非空,就老老实实再写一个 NOT NULL。 多打四个字,省掉一个隐蔽的坑。

四、一张表只能有一个主键

(a INT PRIMARY KEY, b INT PRIMARY KEY) 报 table "demo_pk3" has more than one primary key。 想「两个字段一起当身份证」,得写复合主键:PRIMARY KEY (a, b) 写在表级。 单看 a 能重复(两个 1),单看 b 也能重复,两个一起才唯一。 复合主键用得比想象中少:一是引用起来麻烦(别的表指过来要带两列), 二是业务上「两列一起才是身份证」的情况其实不多。 更常见的是加一个自增 id 当主键,再把那两列做成复合唯一。 这样既有稳定的单列引用,又保证了业务上的不重复。

五、主键和唯一键到底差在哪

对比项 主键 唯一键
能不能有多个 只能一个 可以有多个
能不能为空 MySQL 不能 可以,多个空也行
能不能被外键引用 可以 可以
典型用途 标识「哪一行」 保证「某列不重样」

一句话记: 主键是「这一行是谁」,唯一键是「这一列不许重」。 一张表的主键是它的身份,唯一键只是某一列上的规矩。 两边都能被外键引用(MySQL 要求被引用的列有索引,主键和唯一键都满足), 所以「用主键当外键目标」不是必须的,只是习惯上更稳——主键不会变,唯一键还有可能被调整。

六、没有主键的表会怎样

能建、能插、能查,看起来很自由。 但改数据时没法精确定位:update 表 set x = 1 where name = '甲', 如果一共有三个「甲」,三条一起改。 做关联时也难受:别的表想指过来,没有稳定的东西可指。 所以实际项目里几乎每张表都有主键,哪怕业务上根本用不到那个 id。 它还有个隐形好处:InnoDB 的主键就是数据的物理组织方式(聚簇索引), 没有主键它会自己造一个隐藏的,你既看不见也用不上。 第 12 章讲索引时会回来看这一句。

七、动手 Demo:自动填号、重复被拦、文本主键能空、复合主键

Demo 六段:整型主键自动填号、重复的 id 撞一次、文本主键插 NULL、两个主键报错、复合主键演示, 最后对照主键与唯一键。 第三段和第五段是这一篇的重点——它们分别是「主键不一定非空」和「主键只能有一个」的实证。

import _q46 as Q
c = Q.blank()
Q.run(c, "CREATE TABLE demo_pk (id INTEGER PRIMARY KEY, name TEXT)")
Q.run(c, "INSERT INTO demo_pk (name) VALUES ('甲')")     # id 自动填 1
c.execute("INSERT INTO demo_pk (id, name) VALUES (1, '乙')")   # 重复

# 文本主键能空(SQLite 的历史问题)
Q.run(c, "CREATE TABLE demo_pk2 (code TEXT PRIMARY KEY, name TEXT)")
Q.run(c, "INSERT INTO demo_pk2 (code, name) VALUES (NULL, '甲')")

# 一张表只能一个主键
c.execute("CREATE TABLE demo_pk3 (a INT PRIMARY KEY, b INT PRIMARY KEY)")

# 复合主键:两列的组合唯一
Q.run(c, "CREATE TABLE demo_pk4 (a INT, b INT, memo TEXT, PRIMARY KEY (a, b))")
Q.run(c, "INSERT INTO demo_pk4 VALUES (1, 1, '甲')")
c.execute("INSERT INTO demo_pk4 VALUES (1, 1, '乙')")

实跑输出:

--------------------------------------------------------------------
第四十八课:主键——一行的身份证
--------------------------------------------------------------------

  主键 = 非空 + 唯一,但在这个沙箱有个例外
  SQL> CREATE TABLE demo_pk (id INTEGER PRIMARY KEY, name TEXT)
  完成
  SQL> INSERT INTO demo_pk (name) VALUES ('甲')
  影响 1 行
  SQL> SELECT id, name FROM demo_pk
  id     name
  ------------
  1      甲
  (1 行)
  **没给 `id`,它自己填了 1。**
  **`INTEGER PRIMARY KEY` 在 SQLite 里是 rowid 的别名,所以给 NULL 等于「你帮我编一个」。**
  **这一条是 SQLite 独有的**:**MySQL 的主键给 NULL 会直接报错**,
  **要么显式给值,要么靠 `AUTO_INCREMENT`。**

  重复就拦
  SQL> INSERT INTO demo_pk (id, name) VALUES (1, '乙')
  报错:UNIQUE constraint failed: demo_pk.id
  SQL> INSERT INTO demo_pk (id, name) VALUES (2, '乙')
  影响 1 行
  SQL> SELECT id, name FROM demo_pk ORDER BY id
  id     name
  ------------
  1      甲
  2      乙
  (2 行)
  **同一个 `id` 插不进去,换个 `id` 但名字一样完全没问题。**
  **主键管的是「哪一行」,不是「哪一行的内容」。**

  文本主键:这里能空,MySQL 不能
  SQL> CREATE TABLE demo_pk2 (code TEXT PRIMARY KEY, name TEXT)
  完成
  SQL> SELECT code, name FROM demo_pk2
  code     name
  --------------
  NULL     甲
  (1 行)
  **`TEXT PRIMARY KEY` 竟然接受了 NULL。**
  **这是 SQLite 为兼容老代码保留的历史问题,官方文档里明确承认了。**
  **MySQL 的文本主键写 NULL 直接报错。**
  **所以别指望「主键一定非空」这件事在所有数据库上都成立**——
  **要保证非空,就老老实实再写一个 `NOT NULL`。** **多打四个字,省掉一个隐蔽的坑。**

  一张表只能有一个主键
  SQL> CREATE TABLE demo_pk3 (a INT PRIMARY KEY, b INT PRIMARY KEY)
  报错:table "demo_pk3" has more than one primary key
  **报 `table "demo_pk3" has more than one primary key`。** 想「两个字段一起当身份证」,
  得写成复合主键。
  SQL> CREATE TABLE demo_pk4 (a INT, b INT, memo TEXT, PRIMARY KEY (a, b))
  完成
  SQL> INSERT INTO demo_pk4 VALUES (1, 1, '甲')
  影响 1 行
  SQL> INSERT INTO demo_pk4 VALUES (1, 1, '乙')
  报错:UNIQUE constraint failed: demo_pk4.a, demo_pk4.b
  SQL> INSERT INTO demo_pk4 VALUES (1, 2, '乙')
  影响 1 行
  SQL> SELECT a, b, memo FROM demo_pk4 ORDER BY a, b
  a     b     memo
  -----------------
  1     1     甲
  1     2     乙
  (2 行)
  **复合主键管的是「两列的组合唯一」**,**单看 `a` 能重复(两个 1),单看 `b` 也能重复。**
  **复合主键用得比想象中少**:**一是引用起来麻烦**(别的表指过来要带两列),
  **二是业务上「两列一起才是身份证」的情况其实不多。**
  **更常见的是加一个自增 `id` 当主键,再把那两列做成复合唯一。**

  主键和唯一键到底差在哪
  对比项                主键                      唯一键
  ----------------------------------------------------------------------------
  能不能有多个          只能一个                  可以有多个
  能不能为空            MySQL 不能                可以,多个空也行
  能不能被外键引用      可以                      可以
  典型用途              标识「哪一行」            保证「某列不重样」

  **一句话记**:
  **主键是「这一行是谁」,唯一键是「这一列不许重」。**
  **一张表的主键是它的身份,唯一键只是某一列上的规矩。**

  没有主键的表会怎样
  **能建、能插、能查**,看起来很自由。
  **但改数据时没法精确定位**——`update 表 set x = 1 where name = '甲'`,
  **如果一共有三个「甲」,三条一起改。**
  **做关联时也难受**:别的表想指过来,没有稳定的东西可指。
  **所以实际项目里几乎每张表都有主键**,**哪怕业务上根本用不到那个 `id`。**

第一段不写 id,它自己填了 1。 第二段重复 id 被拦,报的是 UNIQUE constraint failed。 第三段文本主键插 NULL 竟然成功,表里 code 是 NULL。 第四段两个主键报错,has more than one primary key。 第五段复合主键:(1, 1) 重复被拦、(1, 2) 放行。 第六段是一张对照表,讲主键与唯一键的四条差别。

30 秒实验

  1. id INTEGER PRIMARY KEY 的表,插入时不写 id 会怎样?换成 INT PRIMARY KEY 呢?
  2. code TEXT PRIMARY KEY 在这边能存 NULL 吗?为什么这是历史问题?
  3. 为什么实际项目更常用「自增 id 主键 + 业务列复合唯一」,而不是复合主键?

常见误区

  1. 以为主键一定非空。 在这个沙箱,整型主键给 NULL 会自增,文本主键更是能直接存 NULL。 要非空就显式写 NOT NULL。
  2. 拿业务含义的列当主键。 手机号会换、身份证号会错录,主键一变,所有引用它的外键都得跟着改。
  3. 建表不给主键。 能跑,但改数据没法精确定位,而且 InnoDB 会自己造一个你看不见的主键。
自测三问(点开对答案) 1. 主键和唯一键的三条差别是什么? —— 主键一张表只能一个、不能为空;唯一键可以有多个、允许多个 NULL。两者都能被外键引用。 2. `INTEGER PRIMARY KEY` 给 NULL 为什么会自动填号? —— 它是 SQLite 的 rowid 别名,NULL 相当于「你帮我编一个」。`INT PRIMARY KEY` 没这个待遇。 3. 什么情况下该用复合主键? —— 业务上「两列一起才是身份证」时。但更常见的做法是自增 id 当主键、那两列做复合唯一。

主键管自己这张表,下一篇管两张表之间的关系:9.8 外键。

相关笔记