主键 = 非空 + 唯一,但在这个沙箱有两个例外: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 秒实验
id INTEGER PRIMARY KEY的表,插入时不写id会怎样?换成INT PRIMARY KEY呢?code TEXT PRIMARY KEY在这边能存 NULL 吗?为什么这是历史问题?- 为什么实际项目更常用「自增 id 主键 + 业务列复合唯一」,而不是复合主键?
常见误区
- 以为主键一定非空。 在这个沙箱,整型主键给 NULL 会自增,文本主键更是能直接存 NULL。
要非空就显式写
NOT NULL。 - 拿业务含义的列当主键。 手机号会换、身份证号会错录,主键一变,所有引用它的外键都得跟着改。
- 建表不给主键。 能跑,但改数据没法精确定位,而且 InnoDB 会自己造一个你看不见的主键。
自测三问(点开对答案)
1. 主键和唯一键的三条差别是什么? —— 主键一张表只能一个、不能为空;唯一键可以有多个、允许多个 NULL。两者都能被外键引用。 2. `INTEGER PRIMARY KEY` 给 NULL 为什么会自动填号? —— 它是 SQLite 的 rowid 别名,NULL 相当于「你帮我编一个」。`INT PRIMARY KEY` 没这个待遇。 3. 什么情况下该用复合主键? —— 业务上「两列一起才是身份证」时。但更常见的做法是自增 id 当主键、那两列做复合唯一。主键管自己这张表,下一篇管两张表之间的关系:9.8 外键。
- ☐ (本篇完,下一篇 9.8 外键)