Skip to content

MySQL - 主键

PRIMARY KEY(主键)是数据库设计中的一个基本约束。它唯一标识表中的每条记录,确保数据完整性,并作为与其他表建立关系的稳定参考点。

  • 唯一性: 主键列(或列组合)中的每个值都必须是唯一的。不能有两行具有相同的主键值。
  • 非空性: 主键列不能包含 NULL 值。每行都必须有一个主键值。
  • 一个表只能有一个主键,但该键可以由一个或多个列组成(称为复合主键 composite primary key)。

在选择主键时,您有两个主要选项:

  • 自然键(Natural Key): 由现实世界中已存在的属性组成的键。例如,用户的 email 或国家的 iso_code。它们有意义,但有时可能会发生变化、过长,或不能保证唯一性。
  • 代理键(Surrogate Key): 一种没有人为业务意义的人工键,专门用作主键。最常见的类型是自增整数(AUTO_INCREMENT)。它们稳定、高效,并保证在表内是唯一的。

最佳实践: 对于大多数应用程序而言,使用代理键(INT 或 BIGINT 结合 AUTO_INCREMENT)是推荐的方法。它简化了关系,并且不受业务数据变化的影响。

定义主键最常见的方式是在 CREATE TABLE 语句中。

示例(单列代理键):

CREATE TABLE users (
user_id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
email VARCHAR(100) NOT NULL UNIQUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

示例(复合主键): 当需要多列组合来唯一标识一行时,复合主键非常有用。

CREATE TABLE order_items (
order_id INT,
product_id INT,
quantity INT NOT NULL,
price_per_unit DECIMAL(10, 2),
PRIMARY KEY (order_id, product_id) -- 复合键
-- 假设会添加 FOREIGN KEY 来链接到 orders 和 products 表
);

您可以使用 ALTER TABLE 语句向现有表添加主键。这只有在表尚未有主键,并且您选择的列为 NOT NULL 且包含唯一值的情况下才可能实现。

示例:

-- 首先,创建一个没有主键的表
CREATE TABLE employees (
employee_code VARCHAR(10) NOT NULL,
first_name VARCHAR(50),
last_name VARCHAR(50)
);
-- 添加一些数据
INSERT INTO employees (employee_code, first_name, last_name) VALUES
('EMP001', 'Jane', 'Doe'),
('EMP002', 'John', 'Smith');
-- 现在,添加主键约束
ALTER TABLE employees
ADD PRIMARY KEY (employee_code);

您可以使用 DESCRIBE 或 SHOW CREATE TABLE 来验证键是否已创建。

DESCRIBE employees;
字段类型可空键默认值额外
employee_codevarchar(10)NOPRINULL
first_namevarchar(50)YESNULL
last_namevarchar(50)YESNULL

现在尝试插入重复的 employee_code 将导致错误。

INSERT INTO employees (employee_code, first_name, last_name) VALUES ('EMP001', 'Jim', 'Beam');
-- 错误 1062 (23000): key 'employees.PRIMARY' 的重复条目 'EMP001'

您可以删除主键约束,但请务必非常谨慎。这是一项重大的结构性更改,可能破坏外键关系并导致数据完整性问题。

ALTER TABLE table_name DROP PRIMARY KEY;
ALTER TABLE employees DROP PRIMARY KEY;

以下是使用各种编程语言创建带主键的表的示例,遵循了现代最佳实践。

Python
NodeJS
Java
PHP
import mysql.connector
from mysql.connector import errorcode
config = {
'user': 'your_user',
'password': 'your_password',
'host': '127.0.0.1',
'database': 'your_database'
}
create_table_query = """
CREATE TABLE products (
product_id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
sku VARCHAR(50) NOT NULL UNIQUE
) ENGINE=InnoDB;
"""
try:
with mysql.connector.connect(**config) as connection:
with connection.cursor() as cursor:
cursor.execute("DROP TABLE IF EXISTS products") # 运行前清理
cursor.execute(create_table_query)
print("Table 'products' created successfully with a primary key.")
except mysql.connector.Error as err:
print(f"Failed to create table: {err}")
const mysql = require('mysql2/promise');
const pool = mysql.createPool({
host: '127.0.0.1',
user: 'your_user',
password: 'your_password',
database: 'your_database'
});
const createTableWithPK = async () => {
const query = `
CREATE TABLE articles (
article_id CHAR(36) PRIMARY KEY, -- Using UUID for primary key
title VARCHAR(255) NOT NULL,
author_id INT NOT NULL,
published_date DATE
);
`;
try {
await pool.query('DROP TABLE IF EXISTS articles'); // 用于幂等性
await pool.query(query);
console.log('Table \"articles\" created successfully with a UUID primary key.');
} catch (error) {
console.error('Failed to create table:', error);
} finally {
await pool.end();
}
};
createTableWithPK();
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
public class CreatePrimaryKey {
private static final String DB_URL = "jdbc:mysql://localhost:3306/your_database";
private static final String USER = "your_user";
private static final String PASS = "your_password";
public static void main(String[] args) {
String query = """
CREATE TABLE categories (
category_id SMALLINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL
)
"""
try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
Statement stmt = conn.createStatement()) {
stmt.execute("DROP TABLE IF EXISTS categories"); // 为重复运行清理
stmt.execute(query);
System.out.println("Table 'categories' created successfully with a primary key.");
} catch (SQLException e) {
e.printStackTrace();
}
}
}
<?php
$host = '127.0.0.1';
$db = 'your_database';
$user = 'your_user';
$pass = 'your_password';
$charset = 'utf8mb4';
$dsn = "mysql:host=$host;dbname=$db;charset=$charset";
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
];
$query = "
CREATE TABLE logs (
log_id BIGINT AUTO_INCREMENT PRIMARY KEY,
level VARCHAR(10) NOT NULL,
message TEXT,
log_time DATETIME NOT NULL
);
";
try {
$pdo = new PDO($dsn, $user, $pass, $options);
$pdo->exec('DROP TABLE IF EXISTS logs'); // 确保环境干净
$pdo->exec($query);
echo "Table 'logs' created successfully with a primary key.\n";
} catch (\PDOException $e) {
throw new \PDOException($e->getMessage(), (int)$e->getCode());
}
?>