建表是数据库设计中最基础也是最重要的操作之一。作为关系型数据库的代表,MySQL中的表结构设计直接决定了数据存储的效率和查询性能。在实际项目中,合理的表结构设计往往能减少后期50%以上的性能优化工作。
我见过太多项目因为初期建表不规范,导致后期需要重构数据模型的情况。比如字段类型选择不当造成存储空间浪费、缺少必要索引导致查询缓慢、没有设置合适约束引发数据混乱等问题。这些问题往往在数据量小的时候不明显,但当数据增长到百万级后就会集中爆发。
MySQL创建表的标准语法如下:
sql复制CREATE TABLE [IF NOT EXISTS] table_name (
column1 datatype [constraints],
column2 datatype [constraints],
...
[table_constraints]
) [ENGINE=storage_engine];
这个语法看似简单,但每个部分都有讲究。IF NOT EXISTS是个很实用的选项,它可以避免表已存在时报错。在自动化部署脚本中特别有用,但要注意它不会检查现有表结构是否与你定义的相同。
选择合适的数据类型是建表的关键。常见的数据类型包括:
实际项目中,我建议:
约束是保证数据完整性的重要手段:
注意:外键约束虽然能保证数据完整性,但在高并发场景下可能影响性能,需要权衡使用。
sql复制CREATE TABLE IF NOT EXISTS users (
user_id INT AUTO_INCREMENT,
username VARCHAR(50) NOT NULL,
email VARCHAR(100) NOT NULL UNIQUE,
password_hash CHAR(60) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
is_active BOOLEAN DEFAULT TRUE,
PRIMARY KEY (user_id),
INDEX idx_username (username)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
这个用户表设计有几个要点:
sql复制CREATE TABLE orders (
order_id BIGINT AUTO_INCREMENT,
user_id INT NOT NULL,
order_amount DECIMAL(10,2) NOT NULL,
order_status ENUM('pending','paid','shipped','completed','cancelled') NOT NULL DEFAULT 'pending',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (order_id),
FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE,
INDEX idx_user_id (user_id),
INDEX idx_status_created (order_status, created_at)
) ENGINE=InnoDB;
这个订单表的特点是:
MySQL支持多种存储引擎,最常用的是InnoDB和MyISAM:
| 特性 | InnoDB | MyISAM |
|---|---|---|
| 事务支持 | 支持 | 不支持 |
| 外键支持 | 支持 | 不支持 |
| 锁级别 | 行锁 | 表锁 |
| 崩溃恢复 | 支持 | 不支持 |
| 全文索引 | 5.6+支持 | 支持 |
现在基本都推荐使用InnoDB,除非有特殊需求。
建议使用utf8mb4字符集,它支持完整的Unicode字符(包括emoji)。排序规则常用的是utf8mb4_unicode_ci(大小写不敏感)或utf8mb4_bin(二进制比较)。
对于大表,可以考虑使用分区:
sql复制CREATE TABLE logs (
log_id BIGINT AUTO_INCREMENT,
log_time DATETIME NOT NULL,
content TEXT,
PRIMARY KEY (log_id, log_time)
) ENGINE=InnoDB
PARTITION BY RANGE (YEAR(log_time)) (
PARTITION p2020 VALUES LESS THAN (2021),
PARTITION p2021 VALUES LESS THAN (2022),
PARTITION p2022 VALUES LESS THAN (2023),
PARTITION pmax VALUES LESS THAN MAXVALUE
);
INT最大是21亿左右,BIGINT足够大一般不会用完。如果真的担心,可以考虑使用UUID或雪花算法。
对于TEXT/BLOB等大字段,如果查询时不需要经常读取,可以考虑单独存到另一张表,主表只保存引用。
DATETIME和TIMESTAMP的区别:
修改表结构要谨慎,特别是大表:
sql复制-- 添加字段
ALTER TABLE users ADD COLUMN last_login TIMESTAMP NULL AFTER updated_at;
-- 修改字段
ALTER TABLE users MODIFY COLUMN username VARCHAR(100) NOT NULL;
-- 删除字段
ALTER TABLE users DROP COLUMN is_active;
对于大表的ALTER操作,可以考虑使用pt-online-schema-change工具,避免锁表。
ANALYZE TABLE更新统计信息SHOW TABLE STATUS查看表信息EXPLAIN分析查询执行计划通常不直接删除数据,而是标记为删除:
sql复制ALTER TABLE users ADD COLUMN deleted_at TIMESTAMP NULL DEFAULT NULL;
ALTER TABLE users ADD INDEX idx_deleted_at (deleted_at);
-- 删除操作变为更新
UPDATE users SET deleted_at = NOW() WHERE user_id = 123;
-- 查询时排除已删除的
SELECT * FROM users WHERE deleted_at IS NULL;
重要的表建议添加这些字段:
sql复制created_by INT COMMENT '创建人ID',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_by INT COMMENT '更新人ID',
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
version INT DEFAULT 0 COMMENT '乐观锁版本号'
常用的树形结构存储方案:
典型电商系统需要这些表:
社交网络常见表:
简单博客系统表:
sql复制CREATE TABLE articles (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255) NOT NULL,
slug VARCHAR(255) NOT NULL UNIQUE,
content LONGTEXT NOT NULL,
author_id INT NOT NULL,
status ENUM('draft','published','archived') NOT NULL DEFAULT 'draft',
published_at TIMESTAMP NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (author_id) REFERENCES users(id)
);
CREATE TABLE tags (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL UNIQUE
);
CREATE TABLE article_tag (
article_id INT NOT NULL,
tag_id INT NOT NULL,
PRIMARY KEY (article_id, tag_id),
FOREIGN KEY (article_id) REFERENCES articles(id) ON DELETE CASCADE,
FOREIGN KEY (tag_id) REFERENCES tags(id) ON DELETE CASCADE
);
建表是数据库应用的基石,好的表设计能让后续开发事半功倍。在实际项目中,我通常会先画出ER图,与团队讨论确认后,再编写具体的建表语句。对于核心表,可能还需要进行多种设计方案的性能测试比较。