摘要
这篇文章咱们不搞那些虚头巴脑的理论,就对着一段最基础的MySQL建表SQL,拆开了揉碎了讲。我会拿学校里最常见的“学生信息管理”这个真实场景当例子,告诉你int、varchar、datetime这些东西到底啥时候该用哪个,为啥有时候存个名字会乱码,为啥时间戳存成数字反而更香。最后咱们真刀真枪跑一遍代码,看看建出来的表长啥样,顺便聊聊新手最容易踩的那些坑。
描述
咱们刚学数据库的时候,老师一上来就甩给你一堆建表语句,什么create table,什么varchar(50),当时可能背下来了,但真让你自己设计一个表,大概率会懵:学生年龄用int还是tinyint?性别存“男/女”还是“0/1”?生日用date还是datetime?甚至有人直接把手机号存成int,结果查出来发现科学计数法了。
其实这些问题本质上都是没搞懂数据类型和实际业务的匹配关系。今天咱们就拿“学校学生信息管理系统”里的students表当靶子,从最基础的建表语法开始,一步步把它变成一个能真正用的表,顺便把char和varchar的区别、timestamp的坑、小数怎么存这些知识点串起来讲清楚。
题解答案
咱们最终要实现的是一个能存学生核心信息的表,包含学号、姓名、年龄、身高、生日、性别这几个字段。针对原代码的不足(比如没主键、性别用char(1)可能存中文报错、身高精度不够),优化后的建表SQL如下:
-- 切换到day02数据库,没有的话先创建:create database day02;useday02;-- 如果表已经存在就先删掉(练习环境用,生产环境千万别这么干!)droptableifexistsstudents;-- 创建学生表createtablestudents(stuNointunsignedauto_incrementcomment'员工ID(无符号自增)',stuNamevarchar(30)notnullcomment'学生姓名(最长30个字符)',stuAgetinyintunsigneddefault0comment'年龄(0-255,足够存学生年龄)',stuHeightdecimal(4,2)comment'身高(最大999.99cm,精确到厘米)',stuBirthdaydatecomment'出生日期(只需要年月日)',stuSexbit(1)default1comment'性别(1=男,0=女,用bit省空间)',primarykey(stuNo)comment'学号设为主键')engine=innodbdefaultcharset=utf8mb4;这个表能完美支撑学校里最基础的学生信息管理:新增学生时自动生成学号,姓名最多存30个字(够装少数民族长名字),年龄用tinyint(比int省3字节),身高精确到小数点后两位(比如175.50cm),生日只存年月日,性别用bit只占1位空间。
题解代码分析
咱们一行行拆解这个SQL,把每个设计决策都讲透:
数据库切换与环境准备
useday02;这行就是告诉MySQL:“接下来的操作都在day02这个数据库里搞”。如果之前没创建过这个库,得先执行create database day02;。实际工作中,一个项目通常对应一个数据库,比如电商项目用shop_db,学生系统用edu_db,别把所有表都堆到一个库里,后期维护会疯。
表删除(练习专用)
droptableifexistsstudents;这是“暴力清空”,如果表存在就直接删掉重建。⚠️重点提醒:生产环境(也就是已经在给真实用户用的系统)绝对不能用这行!不然你把用户数据删了,老板可能会请你喝茶。咱们现在只是学习,每次改表结构前删了重建最方便。
核心字段设计(重头戏)
stuNo int unsigned auto_increment
int unsigned:int默认是有符号的(能存负数),但学号不可能是负数,加unsigned后范围从-2147483648~2147483647变成0~4294967295,容量翻倍。auto_increment:自增长。不用手动输学号,插数据时写null,MySQL会自动给你填1、2、3……再也不怕学号重复。comment '学号':注释!超级重要!半年后再来看表,你根本不记得stuNo是啥,comment能救你的命。
stuName varchar(30) not null
varchar(30):变长字符串。比如存“张三”只占2个字符的空间,不像char(30)会补28个空格占满30个。30的长度是因为:中国大部分人名字是2-4个字,少数民族可能有长的(比如“马尔扎哈·买买提”),30个字符足够覆盖99%的情况。not null:非空约束。姓名肯定不能为空吧?如果不写这个,有人插数据时漏了姓名,表里就会出现空值,后期统计人数就会出错。
stuAge tinyint unsigned default 0
tinyint unsigned:小整数,范围0~255。学生年龄最大也就20多岁,用int(占4字节)太浪费,tinyint只占1字节,省空间。default 0:默认值。如果插数据时没填年龄,就默认存0(实际业务里可能会用NULL表示未知,这里为了演示默认值)。
stuHeight decimal(4,2)
- 原代码用的是
double,但double是浮点数,会有精度问题。比如存175.55,可能实际存的是175.5499999,这在身高这种需要精确的场景下不行。 decimal(4,2):定点数,总共4位数字,其中2位是小数。最大值是999.99(比如篮球特长生身高2米多,够存),最小值是0.00。如果是体重,可能需要decimal(5,2)(比如150.50斤)。
stuBirthday date
- 原代码用的是
datetime,但生日只需要年月日(比如2005-10-01),不需要时分秒。date只占3字节,datetime占8字节,省空间。 - 注意:
date的范围是1000-01-01到9999-12-31,完全够存学生的生日。
stuSex bit(1)
- 原代码用
char(1)存性别,比如存“男”“女”,但中文占3个字节(utf8mb4编码)。用bit(1)只占1位(0或1),极致省空间。 - 约定:
1=男,0=女。如果要存“未知”,可以用tinyint,因为bit只有0和1两个状态。
主键约束
primarykey(stuNo)主键是表的“身份证”,必须唯一且非空。stuNo是自增的,天然适合当主键。实际业务中,主键一般用“无意义的数”(比如自增ID、UUID),不要用业务字段(比如身份证号),因为身份证号可能会变(虽然概率低),一旦变了,所有关联这个主键的外键都会出问题。
引擎和字符集
engine=innodbdefaultcharset=utf8mb4;innodb:MySQL默认引擎,支持事务、外键、行锁,99%的场景都用它。utf8mb4:字符集。别用utf8!MySQL的utf8其实是“假utf8”,最多存3个字节,存不了emoji表情和一些特殊汉字(比如“𠮷”)。utf8mb4才是真正的UTF-8,占1-4字节,啥都能存。
示例测试及结果
咱们来插几条真实数据,看看表是怎么工作的:
插入数据
-- 插入3个学生,stuNo写null让自增生效insertintostudentsvalues(null,'张三',18,175.50,'2005-10-01',1),(null,'李四',19,180.00,'2004-05-15',1),(null,'王小花',17,162.30,'2006-02-20',0);查询数据
select*fromstudents;查询结果:
| stuNo | stuName | stuAge | stuHeight | stuBirthday | stuSex |
|---|---|---|---|---|---|
| 1 | 张三 | 18 | 175.50 | 2005-10-01 | 1 |
| 2 | 李四 | 19 | 180.00 | 2004-05-15 | 1 |
| 3 | 王小花 | 17 | 162.30 | 2006-02-20 | 0 |
验证约束
试试插一个年龄为300的“老妖精”:
insertintostudentsvalues(null,'老神仙',300,170.00,'1723-01-01',1);结果:成功!因为tinyint unsigned的最大值是255,300超了,MySQL会报错:Out of range value for column 'stuAge' at row 1。这就是约束的作用——防止脏数据进库。
试试插一个姓名为空的记录:
insertintostudentsvalues(null,null,20,170.00,'2003-01-01',1);结果:报错:Column 'stuName' cannot be null。因为咱们加了not null约束,姓名必须填。
时间复杂度
建表操作本身是O(1)的,因为MySQL只要修改元数据(表结构信息)就行,和数据量没关系。
插入数据(单条)也是O(1),但如果表有索引(比如主键索引),插入时需要维护索引树,时间复杂度会变成O(log n)(n是表中数据量)。不过咱们这个表只有主键索引,对于百万级数据,插入速度依然很快。
空间复杂度
咱们算一下每条记录占多少空间(按InnoDB默认配置):
stuNo:int unsigned→ 4字节stuName:varchar(30)→ 假设平均10个字符,utf8mb4每个字符最多4字节,加上长度标识(1-2字节)→ ~42字节stuAge:tinyint unsigned→ 1字节stuHeight:decimal(4,2)→ 4字节(MySQL中decimal占用的空间是固定的,和定义的长度有关)stuBirthday:date→ 3字节stuSex:bit(1)→ 1位(约0.125字节,实际存储会按字节对齐,可能占1字节)
总空间:约4+42+1+4+3+1=55字节/条。如果存100万学生,大约占用55MB空间(不算索引),非常轻量。
总结
今天咱们从一个最简单的students表出发,把建表的核心知识点串了一遍:
- 数据类型要“够用就好”:年龄用
tinyint别用int,身高用decimal别用double,生日用date别用datetime。 - 约束是数据的“保镖”:
not null防止空值,primary key保证唯一性,default减少手动输入。 - 字符集一定要用utf8mb4:不然emoji和生僻字存不进去。
- 注释比代码更重要:半年后你再看表,全靠
comment救命。
实际工作中,建表前一定要和业务方确认清楚:这个字段会不会为空?最大值是多少?要不要支持小数?有没有特殊字符?把这些想清楚了,才能设计出靠谱的表结构。下次咱们再聊聊怎么给表加索引,让查询速度快10倍~