文章目錄
- MYSQL:數據庫約束:為你的數據上把“安全鎖”
- 1. 約束的類型概覽
- 2. `NOT NULL` 非空約束
- 3. `DEFAULT` 默認值約束
- 4. `UNIQUE` 唯一約束
- 5. `PRIMARY KEY` 主鍵約束
- 5.1 自增主鍵 `AUTO_INCREMENT`
- 5.3 復合主鍵
- 6. `FOREIGN KEY` 外鍵約束
- 7. `CHECK` 約束
- 總結
MYSQL:數據庫約束:為你的數據上把“安全鎖”
大家好!今天我們來聊一個數據庫中非常重要,但又常常被初學者忽視的概念——數據庫約束。
想象一下,我們正在錄入一個班級的學生信息。如果有人不小心把“姓名”這一欄漏填了,或者把兩個同學的學號填成了一樣的,那數據不就亂套了嗎?為了保證存入數據庫的數據是準確、可靠的,我們就需要給表中的數據定一些“規矩”。這些規矩,就是我們今天要聊的“數據庫約束”。
簡單來說,約束就是作用于表中列的規則,用于限制存儲在列中的數據。它就像一把把安全鎖,能從源頭上防止“臟數據”的產生,確保數據的完整性和準確性。
1. 約束的類型概覽
數據庫提供了多種約束類型,來滿足不同的“規則”需求。我們先來看一個總覽,對它們有個初步印象:
類型 | 說明 |
---|---|
NOT NULL (非空約束) | 規定這一列的值不能是 NULL ,必須得填點什么。 |
DEFAULT (默認約束) | 如果我們插入數據時沒有給這一列賦值,數據庫會自動使用一個預設的默認值。 |
UNIQUE (唯一約束) | 保證這一列中所有的值都是獨一無二的,不能有重復。 |
PRIMARY KEY (主鍵約束) | 它是 NOT NULL 和 UNIQUE 的結合體,是表中每一行數據的唯一身份標識。 |
FOREIGN KEY (外鍵約束) | 用于建立兩張表之間的關聯關系,確保引用的數據是真實存在的。 |
CHECK 約束 | 一個更靈活的“檢查員”,可以自定義各種復雜的規則來限制列中的值。 |
接下來,我們就一個個地把這些“安全鎖”研究明白。
2. NOT NULL
非空約束
NOT NULL
是最簡單也最常用的約束。它的作用就是強制某一列在插入或更新數據時,不能接受 NULL
值。
比如,我們創建一個學生表,但暫時不加任何約束:
DROP TABLE IF EXISTS student;
CREATE TABLE student(id BIGINT,name VARCHAR(20)
);-- 嘗試插入一條名字為 NULL 的記錄
INSERT INTO student VALUES (1, NULL);-- 查詢結果,可以看到 name 是 NULL
SELECT * FROM student;
一條沒有名字的學生記錄,這顯然是不合理的。所以,我們需要給 name
列加上非空約束,把它變成一個必填項。
DROP TABLE IF EXISTS student;
-- 在 name 列后面加上 NOT NULL 關鍵字
CREATE TABLE student (id BIGINT,name VARCHAR(20) NOT NULL
);-- 再次嘗試插入 NULL 值
INSERT INTO student VALUES (1, NULL);
-- 這次,數據庫會直接報錯,拒絕插入
-- ERROR 1048 (23000): Column 'name' cannot be null-- 插入正常值就可以成功
INSERT INTO student VALUES (1, '張三');
SELECT * FROM student;
數據庫會幫我們自動進行了校驗,擋住了不合規的數據。我們可以通過 DESC
命令查看表結構,Null
這一列顯示 NO
,就代表該列不允許為空。
DESC student;
3. DEFAULT
默認值約束
DEFAULT
約束也很好理解,它提供了一個“默認選項”。當我們在插入新記錄時,如果沒有明確指定某一列的值,數據庫就會自動使用這個默認值。
我們給學生表加上 age
列:
ALTER TABLE student ADD COLUMN age INT;
這時,如果我們只插入 id
和 name
,age
列就會是 NULL
。
INSERT INTO student(id, name) VALUES (1, '張三');
SELECT * FROM student;
假設大部分學生的年齡都是18歲,我們就可以把18設為默認值,簡化插入操作。
DROP TABLE IF EXISTS student;
-- 在 age 列后使用 DEFAULT 關鍵字設置默認值
CREATE TABLE student (id BIGINT,name VARCHAR(20) NOT NULL,age INT DEFAULT 18
);-- 插入時,不指定 age
INSERT INTO student(id, name) VALUES (2, '李四');
可以看到,李四的年齡被自動設為了18。
一個值得注意的點: 如果我們插入時明確地將 age
指定為 NULL
,那么默認值約束就不會生效。用戶的明確指定優先級更高。
INSERT INTO student(id, name, age) VALUES (3, '王五', NULL);
SELECT * FROM student;
4. UNIQUE
唯一約束
UNIQUE
約束確保了某列中的所有值都是獨一無二、不能重復的。比如學生的學號、用戶的身份證號,這些都應該是唯一的。
我們給學生表加上學號(sno
)列,并為其設置唯一約束。
DROP TABLE IF EXISTS student;
CREATE TABLE student (id BIGINT,name VARCHAR(20) NOT NULL,age INT DEFAULT 18,sno VARCHAR(10) UNIQUE -- 為 sno 列添加唯一約束
);-- 插入第一條記錄,成功
INSERT INTO student(id, name, sno) VALUES (1, '張三', '100001');-- 嘗試插入第二條記錄,使用相同的學號
INSERT INTO student(id, name, sno) VALUES (2, '李四', '100001');
-- 數據庫報錯,唯一約束生效
-- ERROR 1062 (23000): Duplicate entry '100001' for key 'student.sno'
一個有趣的特例: 在大多數數據庫中,UNIQUE
約束的列是可以包含多個 NULL
值的。因為從邏輯上講,NULL
并不等于 NULL
,它代表的是“未知”,所以多個“未知”并不算重復。
查看表結構,Key
列顯示 UNI
就表示該列有唯一約束。
DESC student;
5. PRIMARY KEY
主鍵約束
主鍵可以說是表中最重要的約束,它是每一行數據的唯一身份標識。我們可以把它想象成每個人的身份證號。
一個列如果被設置為主鍵,它將同時擁有兩個屬性:
NOT NULL
:不能為空。UNIQUE
:必須唯一。
每個表最多只能有一個主鍵。這個主鍵可以由單個列構成,也可以由多個列共同構成(稱為復合主鍵)。
DROP TABLE IF EXISTS student;
-- 直接使用 PRIMARY KEY 關鍵字定義主鍵
CREATE TABLE student (id BIGINT PRIMARY KEY,name VARCHAR(20) NOT NULL,age INT DEFAULT 18,sno VARCHAR(10) UNIQUE
);
當我們把 id
設置為主鍵后,它就自動具備了非空和唯一的特性。如果我們嘗試插入重復的 id
,就會觸發主鍵沖突。
INSERT INTO student(id, name, sno) VALUES (1, '張三', '100001');-- 嘗試插入 id 同樣為 1 的記錄
INSERT INTO student(id, name, sno) VALUES (1, '李四', '100002');
-- 報錯:主鍵沖突
-- ERROR 1062 (23000): Duplicate entry '1' for key 'student.PRIMARY'
5.1 自增主鍵 AUTO_INCREMENT
在實際開發中,我們很少會手動去為每一條記錄分配主鍵,這太麻煩了。通常,我們會把主鍵列設置為“自動增長”,讓數據庫來幫我們管理。
DROP TABLE IF EXISTS student;
CREATE TABLE student (id BIGINT PRIMARY KEY AUTO_INCREMENT, -- 設置 id 為自增主鍵name VARCHAR(20) NOT NULL,age INT DEFAULT 18,sno VARCHAR(10) UNIQUE
);
設置了 AUTO_INCREMENT
后,我們在插入數據時就可以完全不管 id
列,數據庫會自動為我們生成一個唯一的、遞增的值。
-- 插入時可以不寫 id 列,或者將其值設為 NULL
INSERT INTO student(name, sno) VALUES ('張三', '100001');
INSERT INTO student(id, name, sno) VALUES (NULL, '李四', '100002');SELECT * FROM student;
一個需要注意的現象: 如果某次插入因為其他原因失敗了(比如學號重復),那么這次自增分配的主鍵值就會被“浪費”掉,不會被下一次插入使用。
-- 這次插入會因為學號 '100002' 重復而失敗,但數據庫內部已經為它準備好了 id=3
INSERT INTO student(name, sno) VALUES ('王五', '100002');
-- ERROR 1062 (23000): Duplicate entry '100002' for key 'student.sno'-- 修正學號后,再次插入
INSERT INTO student(name, sno) VALUES ('王五', '100003');-- 查詢結果會發現,王五的 id 是 4,而不是 3
SELECT * FROM student;
另外,自增主鍵雖然是遞增的,但不保證一定是連續的。我們也可以手動插入一個更大的值,后續的自增會從這個新的最大值開始。
#手動指定一個值
insert into student(id,name,sno) values (100,'趙六','100004');
select * from student;
# 下一次自增從主鍵的最大值開始
insert into student(name,sno) values ('錢七','100005');
select * from student;
在分布式系統中,為了避免不同服務器生成相同的主鍵,常常會為每臺服務器預分配一個主鍵區間,這也是導致主鍵不連續的常見原因。
5.2 主鍵沖突時的處理策略
當我們插入的數據與現有的主鍵或唯一鍵沖突時,除了報錯,MySQL還提供了兩種優雅的處理方式:
ON DUPLICATE KEY UPDATE
(存在則更新)
這個語法的意思是:嘗試插入,如果發生主鍵或唯一鍵沖突,那就別報錯了,改成執行更新操作。
-- 嘗試插入 id=100 的記錄,如果已存在,則更新它的 name 和 sno
INSERT INTO student(id, name, sno) VALUES (100, '趙六', '100100')ON DUPLICATE KEY UPDATE name = '趙六', sno = '100100';
-- Query OK, 2 rows affected... 這表示執行了“刪除舊記錄,插入新記錄”的操作
REPLACE INTO
(存在則替換)
這個語法更“暴力”一些:如果記錄不存在,就插入;如果存在(根據主鍵或唯一鍵判斷),就先刪除舊的記錄,再插入新的記錄。
-- 如果 id=101 的記錄存在,就刪掉它,然后插入這條新的
REPLACE INTO student(id, name, sno) VALUES (101, '錢七', '100101');
-- Query OK, 2 rows affected...-- 如果 id=102 的記錄不存在,就直接插入
REPLACE INTO student(id, name, sno) VALUES (102, '吳八', '100102');
-- Query OK, 1 row affected...
5.3 復合主鍵
有時候,單個列不足以唯一標識一條記錄,我們就需要用多個列組合起來作為主鍵,這就是復合主鍵。
DROP TABLE IF EXISTS student;
CREATE TABLE student (id BIGINT,name VARCHAR(20),PRIMARY KEY (id, name) -- 指定 id 和 name 共同組成主鍵
);
對于復合主鍵,只有當所有組成主鍵的列的值都完全相同時,才會被認為是主鍵沖突。
-- 插入成功
INSERT INTO student(id, name) VALUES (1, '張三');-- 再次插入,因為 (1, '張三') 這個組合已經存在,所以沖突
INSERT INTO student(id, name) VALUES (1, '張三');
-- ERROR 1062 (23000): Duplicate entry '1-張三' for key 'student.PRIMARY'-- 只改變其中一個列的值,就不算沖突,插入成功
INSERT INTO student(id, name) VALUES (2, '張三');
6. FOREIGN KEY
外鍵約束
外鍵是體現數據庫“關系”的核心。它用于建立和加強兩張表數據之間的聯系,保證了數據的引用完整性。
我們用一個經典的“班級表”和“學生表”的例子來理解。一個班級可以有多個學生,一個學生只屬于一個班級。在這里,班級表是“主表”,學生表是“從表”。
首先,創建主表 class
:
DROP TABLE IF EXISTS class;
CREATE TABLE class (id BIGINT PRIMARY KEY AUTO_INCREMENT,name VARCHAR(20) NOT NULL
);
-- 初始化一些班級數據
INSERT INTO class (name) VALUES ('java01'), ('java02'), ('java03'), ('C++01'), ('C++02');
然后,創建從表 student
,并在其中定義一個外鍵,讓它的 class_id
列引用 class
表的 id
列。
DROP TABLE IF EXISTS student;
CREATE TABLE student(id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(20) NOT NULL,age INT DEFAULT 18,class_id BIGINT,-- 定義外鍵:本表的 class_id 列,引用 class 表的 id 列FOREIGN KEY (class_id) REFERENCES class(id)
);
這個外鍵約束建立后,會產生以下效果:
-
插入限制:你不能在
student
表中插入一個class_id
在class
表中不存在的值。比如,你不能給學生分配一個不存在的班級。-- 嘗試插入一個 class_id 為 100 的學生,因為 class 表中沒有 id=100 的班級,所以失敗 INSERT INTO student(name, class_id) VALUES ('王五', 100); -- ERROR 1452 (23000): Cannot add or update a child row...
-
刪除限制:你不能從主表
class
中刪除一個已經被從表student
引用的記錄。比如,如果java01
班(假設id=1)里還有學生,你就不能直接刪除這個班級。-- 嘗試刪除 java01 班,因為有學生記錄引用了它,所以失敗 DELETE FROM class WHERE name = 'java01'; -- ERROR 1451 (23000): Cannot delete or update a parent row...
這條規則保證了不會出現“學生所屬班級信息丟失”的情況。要想刪除主表記錄,必須先處理掉從表中所有依賴它的記錄。
-
刪表限制:不能直接刪除被外鍵引用的主表。必須先刪除從表,才能刪除主表。
-- 直接刪主表,失敗 DROP TABLE class; -- ERROR 3730 (HY000): Cannot drop table 'class' referenced by...-- 正確的順序 DROP TABLE student; -- 先刪從表 DROP TABLE class; -- 再刪主表
外鍵就像一條牢固的鎖鏈,將相關的表緊密地聯系在一起,確保了數據之間邏輯關系的一致性和正確性。
7. CHECK
約束
CHECK
約束是一個通用的“校驗器”,你可以用它來定義更復雜的、自定義的數據驗證規則。
比如,我們要求學生的年齡必須大于等于16歲,性別只能是’男’或’女’。
DROP TABLE IF EXISTS student;
CREATE TABLE student(id BIGINT PRIMARY KEY AUTO_INCREMENT,name VARCHAR(20) NOT NULL,age INT DEFAULT 18,gender CHAR(1),-- 定義 CHECK 約束CHECK (age >= 16),CHECK (gender = '男' OR gender = '女')
);-- 插入年齡小于16的記錄,失敗
INSERT INTO student(name, age, gender) VALUES ('張三', 15, '男');
-- ERROR 3819 (HY000): Check constraint 'student_chk_1' is violated.-- 插入性別不合規的記錄,失敗
INSERT INTO student(name, age, gender) VALUES ('張三', 17, '1');
-- ERROR 3819 (HY000): Check constraint 'student_chk_2' is violated.
CHECK
約束甚至可以用于比較同一行中不同列之間的值。
CREATE TABLE t_check (c1 INT CHECK(c1 <> 0),c2 INT CHECK(c2 > 0),c3 INT,-- c3 必須大于等于 c2CHECK(c3 >= c2)
);
一點建議:
CHECK
約束雖然強大,但在 MySQL 8.0.16 版本之前并不被真正支持(語法能通過但不起作用),這導致了它的兼容性問題。在實際的項目中,更傾向于將這類復雜的業務邏輯校驗放在應用程序層面(比如Java、Python代碼中)來完成,而不是過度依賴數據庫的 CHECK
約束。這樣做能讓業務規則更清晰,也更容易維護和遷移。
總結
好了,今天我們把數據庫的幾種核心約束都過了一遍。它們就像是數據庫的“衛兵”,時刻守護著數據的準確性和一致性。
NOT NULL
:保證數據不為空。DEFAULT
:提供省事的默認值。UNIQUE
:確保數據不重復。PRIMARY KEY
:每一行數據的唯一身份證。FOREIGN KEY
:連接不同表之間的關系紐帶。CHECK
:自定義的超級校驗器。
熟練掌握并合理使用這些約束,是每一個后端開發者的基本功。希望這篇筆記能幫助你更好地理解它們!