1. 建库建表:写 SQL 之前先把表结构设计想明白
1.1 库表命名与字符集选择:这些小坑能让你少加三小时班
刚开始接触 MySQL 的人,通常第一件事就是打开命令行敲 CREATE DATABASE,然后急着写 INSERT。但我在实际项目里见过太多因为表结构设计不合理导致的返工,所以先把建库建表这一步说透。
库名和表名的命名,我的建议是全部小写、用下划线分隔单词、不要用 MySQL 保留字。比如存储学生成绩的库,叫 school 或者 student_db 都比 myDb 这种随意命名要好。保留字问题更隐蔽,如果你把表命名为 order 或 group,写 SQL 时就必须用反引号包起来,每次查询都多一步,还会埋下隐患。宁可多打几个下划线,也不要去碰保留字。
字符集是我最想强调的一个点。MySQL 里的 utf8 其实是 utf8mb3,最多只能存 3 个字节的字符,这意味着 emoji 和一些生僻汉字根本存不了,插入时会直接报错或者变成问号。正确的做法是使用 utf8mb4,它是完整的 4 字节 UTF-8 编码,是 MySQL 8.0 的默认字符集。排序规则方面,我习惯用 utf8mb4_unicode_ci,它在做比较和排序时更符合 Unicode 规范;如果你用的 MySQL 8.0,默认的 utf8mb4_0900_ai_ci 也可以,性能更好。创建库的时候就把字符集定死,避免表建完以后再去改,这一步能省掉后面大量的乱码排查时间。
1.2 学生-课程-成绩三张表的完整建表语句
为了让后面的增删改查有实际的业务场景,我用一个经典的学生选课成绩模型来做演示。这个模型包含三张表:学生表、课程表、成绩表。为什么是三张而不是一张大宽表?因为选课关系是多对多的——一个学生可以选多门课,一门课可以被多个学生选,成绩属于某个学生和某门课的关联。如果全部塞进一张表,会产生大量重复数据,改一个学生姓名就要改几十行,这种设计在工程上是行不通的。
下面是建表语句,每个字段都加了注释,你可以直接在自己本机的 MySQL 里执行。
sql复制CREATE DATABASE IF NOT EXISTS school
DEFAULT CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
USE school;
CREATE TABLE student (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
student_no VARCHAR(20) NOT NULL UNIQUE COMMENT '学号',
name VARCHAR(50) NOT NULL COMMENT '姓名',
gender TINYINT NOT NULL DEFAULT 0 COMMENT '性别:0未知 1男 2女',
birthday DATE DEFAULT NULL COMMENT '出生日期',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='学生表';
CREATE TABLE course (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
course_name VARCHAR(100) NOT NULL COMMENT '课程名称',
credit DECIMAL(3,1) NOT NULL DEFAULT 0.0 COMMENT '学分'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='课程表';
CREATE TABLE score (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
student_id BIGINT UNSIGNED NOT NULL COMMENT '学生ID',
course_id BIGINT UNSIGNED NOT NULL COMMENT '课程ID',
score DECIMAL(5,2) NOT NULL COMMENT '成绩',
exam_time DATETIME NOT NULL COMMENT '考试时间',
UNIQUE KEY uk_student_course (student_id, course_id),
KEY idx_course_score (course_id, score)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='成绩表';
注意 score 表最后两个索引。uk_student_course 是联合唯一键,保证同一个学生对同一门课只能有一条成绩记录,这是业务上防止重复录入的重要约束。idx_course_score 是给后面按课程查成绩、排序用的普通索引。索引不是越多越好,但这两个是高频查询路径,值得建。
1.3 字段类型如何取舍:长度、精度与业务含义
字段类型选错是初学者最容易犯的错误,而且往往要到数据量大了才暴露。我逐个说一下这张表里几个关键字段的选择逻辑。
主键用 BIGINT UNSIGNED AUTO_INCREMENT。INT 的最大值约 21 亿,对一张学生表可能够用,但如果是订单、日志这类增长极快的表,INT 很容易触顶。UNSIGNED 可以把上限翻一倍,对于用不到负数的主键来说是合理选择。BIGINT 在这个场景下略显保守,但在工程上多花 4 个字节换取未来的扩展空间,是划算的。
姓名和学号都用 VARCHAR 而不是 CHAR。CHAR 是定长存储,适合本身长度就固定的场景,比如手机号(虽然我更建议用 BIGINT 存,但这就涉及另一层取舍了);姓名长度不定,用 VARCHAR(50) 更合适。VARCHAR 的括号里不是字节数而是字符数,这是很多人会搞混的。
性别用 TINYINT 而不是 VARCHAR(10) 存"男""女"。字符串存储占用大、容易乱码、排序也不符合直觉。用 0/1/2 这样的数字编码,配合 COMMENT 说明含义,既省空间又清晰。
成绩用 DECIMAL(5,2) 而不
