MySQL - 主键
MySQL - 主键(Primary Key)详解
Section titled “MySQL - 主键(Primary Key)详解”PRIMARY KEY(主键)是数据库设计中的一个基本约束。它唯一标识表中的每条记录,确保数据完整性,并作为与其他表建立关系的稳定参考点。
主键的核心特性
Section titled “主键的核心特性”- 唯一性: 主键列(或列组合)中的每个值都必须是唯一的。不能有两行具有相同的主键值。
- 非空性: 主键列不能包含
NULL值。每行都必须有一个主键值。 - 一个表只能有一个主键,但该键可以由一个或多个列组成(称为复合主键
composite primary key)。
选择主键:自然键与代理键
Section titled “选择主键:自然键与代理键”在选择主键时,您有两个主要选项:
- 自然键(Natural Key): 由现实世界中已存在的属性组成的键。例如,用户的
email或国家的iso_code。它们有意义,但有时可能会发生变化、过长,或不能保证唯一性。 - 代理键(Surrogate Key): 一种没有人为业务意义的人工键,专门用作主键。最常见的类型是自增整数(
AUTO_INCREMENT)。它们稳定、高效,并保证在表内是唯一的。
最佳实践: 对于大多数应用程序而言,使用代理键(INT 或 BIGINT 结合 AUTO_INCREMENT)是推荐的方法。它简化了关系,并且不受业务数据变化的影响。
1. 在表创建期间
Section titled “1. 在表创建期间”定义主键最常见的方式是在 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 表);2. 添加到现有表
Section titled “2. 添加到现有表”您可以使用 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 employeesADD PRIMARY KEY (employee_code);您可以使用 DESCRIBE 或 SHOW CREATE TABLE 来验证键是否已创建。
DESCRIBE employees;| 字段 | 类型 | 可空 | 键 | 默认值 | 额外 |
|---|---|---|---|---|---|
| employee_code | varchar(10) | NO | PRI | NULL | |
| first_name | varchar(50) | YES | NULL | ||
| last_name | varchar(50) | YES | NULL |
现在尝试插入重复的 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;使用客户端程序创建主键
Section titled “使用客户端程序创建主键”以下是使用各种编程语言创建带主键的表的示例,遵循了现代最佳实践。
PythonNodeJSJavaPHP
import mysql.connectorfrom 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());}
?>