目录
[4.1 数据类型分类](#4.1 数据类型分类)
[4.2 数值类型](#4.2 数值类型)
[4.2.1 tinyint类型](#4.2.1 tinyint类型)
[4.2.2 bit类型](#4.2.2 bit类型)
[4.2.3 小数类型](#4.2.3 小数类型)
[4.2.3.1 float](#4.2.3.1 float)
[4.2.3.2 decimal](#4.2.3.2 decimal)
[4.3 字符串类型](#4.3 字符串类型)
[4.3.1 char](#4.3.1 char)
[4.3.2 varchar](#4.3.2 varchar)
[4.3.3 char和varchar比较](#4.3.3 char和varchar比较)
[4.4 日期和时间类型](#4.4 日期和时间类型)
[4.5 enum和set](#4.5 enum和set)
[5.2 默认值](#5.2 默认值)
[5.3 列描述](#5.3 列描述)
[5.4 zerofill](#5.4 zerofill)
[5.5 主键](#5.5 主键)
[5.6 自增长](#5.6 自增长)
[5.7 唯一键](#5.7 唯一键)
[5.8 外键](#5.8 外键)
[5.9 综合案例](#5.9 综合案例)

四、数据类型
MySQL 数据类型的选择核心原则:够用就小,精准匹配。选择合适的数据类型,既能大幅节省存储空间,又能显著提升索引和查询的效率。
4.1 数据类型分类
MySQL 常用数据类型可分为四大类,覆盖绝大多数业务场景:
- 数值类型:整数类型(tinyint、bit 等)、小数类型(float、decimal)
- 字符串类型:定长字符串 char、变长字符串 varchar
- 日期和时间类型:date、datetime、timestamp 等
- 枚举与集合类型:enum 单选枚举、set 多选集合
| 一级分类 | 二级分类 | 数据类型 | 核心说明 | 取值范围 & 存储空间 |
|---|---|---|---|---|
| 数值类型 | 整数类型 | BIT(M) | 位类型,按二进制位存储数据,M 为指定位数,默认值 1 | 位数范围 1-64;存储空间按位数动态分配(1-8 位占 1 字节,9-16 位占 2 字节,以此类推) |
| TINYINT [UNSIGNED] | 超小整数,MySQL 最小整数类型,默认有符号模式 | 有符号:-128 ~ 127;无符号:0 ~ 255;固定占用 1 字节 | ||
| BOOL/BOOLEAN | 布尔类型,本质是 TINYINT (1) 的别名 | 用 0 表示假,非 0 值表示真;固定占用 1 字节 | ||
| SMALLINT [UNSIGNED] | 小整数,默认有符号模式 | 有符号:-32768 ~ 32767(-2^15 ~ 2^15-1);无符号:0 ~ 65535(2^16-1);固定占用 2 字节 | ||
| INT/INTEGER [UNSIGNED] | 常规整数,业务最常用的整数类型,默认有符号模式 | 有符号:-2147483648 ~ 2147483647(-2^31 ~ 2^31-1);无符号:0 ~ 4294967295(2^32-1);固定占用 4 字节 | ||
| BIGINT [UNSIGNED] | 大整数,推荐用于主键 ID,默认有符号模式 | 有符号:-9223372036854775808 ~ 9223372036854775807(-2^63 ~ 2^63-1);无符号:0 ~ 18446744073709551615(2^64-1);固定占用 8 字节 | ||
| 小数类型 | FLOAT[(M,D)] [UNSIGNED] | 单精度浮点数,近似值存储,M 为总有效位数,D 为小数位数 | 固定占用 4 字节;存在精度丢失风险,适用于精度要求不高的非金额场景 | |
| DOUBLE[(M,D)] [UNSIGNED] | 双精度浮点数,近似值存储,精度高于 FLOAT,M 为总有效位数,D 为小数位数 | 固定占用 8 字节;仍有计算误差,不可用于金额类高精度场景 | ||
| DECIMAL(M,D) [UNSIGNED] | 定点数,高精度精确存储,无精度丢失,M 为总有效位数,D 为小数位数 | M 最大支持 65,D 最大支持 30;存储空间随有效位数动态分配;金额、财务数据强制使用该类型 | ||
| 字符串 & 二进制类型 | 字符串类型 | CHAR(size) | 定长字符串,size 为最大字符数,不足长度自动填充空格,查询时自动去除末尾空格 | 最大支持 255 个字符;存储空间固定为 size 字符对应的字节数,查询效率高于 VARCHAR |
| VARCHAR(size) | 变长字符串,size 为最大字符数,按实际内容占用空间,保留末尾空格 | 最大存储上限 65535 字节(受字符集影响,如 utf8mb4 下最大约 16383 个字符);需额外 1-2 字节记录字符串长度 | ||
| TINYTEXT | 短文本类型,不支持默认值 | 最大存储 255 字节 | ||
| TEXT | 大文本类型,支持全文索引,不支持默认值 | 最大存储 64KB(65535 字节) | ||
| MEDIUMTEXT | 中等长度文本类型,不支持默认值 | 最大存储 16MB | ||
| LONGTEXT | 超长文本类型,不支持默认值 | 最大存储 4GB | ||
| 枚举 & 集合 | ENUM (' 值 1',' 值 2',...) | 枚举类型,单选字符串,只能从预设的枚举列表中选择 1 个值 | 最多支持 65535 个枚举项;内部用数字索引存储,空间占用小,强制限制取值范围 | |
| SET (' 值 1',' 值 2',...) | 集合类型,多选字符串,可从预设列表中选择 0 个或多个值,多个值用英文逗号分隔 | 最多支持 64 个选项;内部用二进制位存储,空间占用极小,适用于多选标签场景 | ||
| 二进制类型 | BLOB | 二进制大对象,无字符集概念,用于存储二进制数据(图片、文件、加密串等) | 最大存储 64KB,对应 TEXT 的二进制格式,还有 TINYBLOB、MEDIUMBLOB、LONGBLOB 对应不同容量 | |
| 日期时间类型 | - | DATE | 纯日期类型,无时间部分 | 格式:YYYY-MM-DD;固定占用 3 字节;取值范围:1000-01-01 ~ 9999-12-31 |
| TIME | 纯时间 / 时长类型,无日期部分 | 格式:HH:MM:SS;固定占用 3 字节;取值范围:-838:59:59 ~ 838:59:59 | ||
| DATETIME | 日期时间类型,不受时区影响 | 格式:YYYY-MM-DD HH:MM:SS;固定占用 8 字节;取值范围:1000-01-01 00:00:00 ~ 9999-12-31 23:59:59 | ||
| TIMESTAMP | 时间戳类型,受数据库时区影响,支持自动更新 | 格式:YYYY-MM-DD HH:MM:SS;固定占用 4 字节;取值范围:1970-01-01 00:00:01 UTC ~ 2038-01-19 03:14:07 UTC | ||
| YEAR | 纯年份类型,占用空间最小 | 格式:YYYY;固定占用 1 字节;取值范围:1901 ~ 2155 |
4.2 数值类型
数值类型分为整数类型和小数类型,核心区别在于是否支持小数位,以及存储的精度和范围。

4.2.1 tinyint类型
数值越界测试:
mysql> insert into t1 values(1);
Query OK, 1 row affected (0.06 sec)
mysql> insert into t1 values(128);
ERROR 1264 (22003): Out of range value for column 'num' at row 1
mysql> select * from t1;
+------+
| num |
+------+
| 1 |
+------+
1 row in set (0.00 sec)
说明: 在MySQL中,整型可以指定是有符号的和无符号的,默认是有符号的。 可以通过UNSIGNED来说明某个字段是无符号的
无符号测试:
mysql> create table tt2(num tinyint unsigned);
Query OK, 0 rows affected (0.02 sec)
mysql> insert into tt2 values(1);
Query OK, 1 row affected (0.01 sec)
mysql> insert into tt2 values(129);
Query OK, 1 row affected (0.01 sec)
mysql> insert into tt2 values(-1);
ERROR 1264 (22003): Out of range value for column 'num' at row 1
mysql> select * from tt2;
+------+
| num |
+------+
| 1 |
| 129 |
+------+
2 rows in set (0.00 sec)
我们向mysql中插入了一个不合法的数据,在MySQL中一般是直接拦截我们,不让我们进行操作(这和c/c++等语言不同,语言一般会截断)
反过来,如果我们已经有数据被成功插入MySQL中了,一定是插入合法的。
所以,MySQL中数据类型本身也是一种约束!
注意:尽量不使用unsigned,对于int类型可能存放不下的数据,int unsigned同样可能存放不下,与其如此,还不如设计时,将int类型提升为bigint类型。
4.2.2 bit类型
M表示每个值的位数,范围从1到64。如果M被忽略,默认为1
bit[(M)] : 位字段类型
当我们执行插入语句时,MySQL 会自动把你输入的十进制数字,转换成对应长度的二进制值存入字段:
- 插入
10:十进制 10 → 8 位二进制00001010 - 插入
65:十进制 65 → 8 位二进制01000001
MySQL 原生命令行客户端,对BIT类型的字段,默认以十六进制(HEX)格式输出,而非十进制数字:
- 二进制
00001010→ 十六进制0A→ 显示为0x0A - 二进制
01000001→ 十六进制41→ 显示为0x41
数据本身是完全正确的,只是展示格式和我们预期的不一样。
mysql> insert into tt3 values(10,10);
Query OK, 1 row affected (0.01 sec)
mysql> select * from tt3;
+------+------------+
| id | a |
+------+------------+
| 10 | 0x0A |
+------+------------+
1 row in set (0.00 sec)
mysql> insert into tt3 values(65,65);
Query OK, 1 row affected (0.01 sec)
mysql> select * from tt3;
+------+------------+
| id | a |
+------+------------+
| 10 | 0x0A |
| 65 | 0x41 |
+------+------------+
2 rows in set (0.00 sec)
定义bit(1),只存放0或1,当插入2时,已经越界了
mysql> create table if not exists tt4(
-> gender bit(1)
-> );
Query OK, 0 rows affected (0.03 sec)
mysql> insert into tt4 values(0);
Query OK, 1 row affected (0.01 sec)
mysql> insert into tt4 values(1);
Query OK, 1 row affected (0.01 sec)
mysql> insert into tt4 values(2);
ERROR 1406 (22001): Data too long for column 'gender' at row 1
mysql> select * from tt4;
+----------------+
| gender |
+----------------+
| 0x00 |
| 0x01 |
+----------------+
2 rows in set (0.00 sec)
4.2.3 小数类型
4.2.3.1 float
M指定显示长度,d指定小数位数,占用空间4个字节
float[(m, d)] [unsigned]
样例:
小数:float(4,2)表示的范围是-99.99 ~ 99.99,MySQL在保存值时会进行四舍五入。
mysql> create table tt6(id int, salary float(4,2));
Query OK, 0 rows affected, 1 warning (0.03 sec)
mysql> insert into tt6 values(100, -99.99);
Query OK, 1 row affected (0.00 sec)
mysql> insert into tt6 values(101, -99.991);
Query OK, 1 row affected (0.01 sec)
mysql> select * from tt6;
+------+--------+
| id | salary |
+------+--------+
| 100 | -99.99 |
| 101 | -99.99 |
+------+--------+
2 rows in set (0.00 sec)


如果定义的是float(4,2) unsigned 这时,因为把它指定为无符号的数,范围是 0 ~ 99.99:
mysql> create table tt7(id int, salary float(4,2) unsigned);
Query OK, 0 rows affected, 2 warnings (0.04 sec)
mysql> insert into tt7 values(100, -0.1);
ERROR 1264 (22003): Out of range value for column 'salary' at row 1
mysql> insert into tt7 values(100, -0);
Query OK, 1 row affected (0.01 sec)
mysql> insert into tt7 values(100, 99.99);
Query OK, 1 row affected (0.01 sec)
mysql> select * from tt7;
+------+--------+
| id | salary |
+------+--------+
| 100 | 0.00 |
| 100 | 99.99 |
+------+--------+
2 rows in set (0.00 sec)

4.2.3.2 decimal
定点数m指定长度,d表示小数点的位数
decimal(m, d) [unsigned]
-
decimal(5,2) 表示的范围是 -999.99 ~ 999.99
-
decimal(5,2) unsigned 表示的范围 0 ~ 999.99
-
decimal和float很像,但是有区别: **float和decimal表示的精度不一样,float表示的精度大约是7位。**decimal整数最大位数m为65。支持小数最大位数d是30。如果d被省略,默认为0.如果m被省略, 默认是10。 如果希望小数的精度高,推荐使用decimal。
mysql> create table tt8 ( id int, salary1 float(10,8), salary2 decimal(10,8));
Query OK, 0 rows affected, 1 warning (0.04 sec)mysql> insert into tt8 values(100,23.12345612, 23.12345612);
Query OK, 1 row affected (0.00 sec)mysql> select * from tt8;
+------+-------------+-------------+
| id | salary1 | salary2 |
+------+-------------+-------------+
| 100 | 23.12345695 | 23.12345612 |
+------+-------------+-------------+
1 row in set (0.00 sec)

4.3 字符串类型
4.3.1 char
固定长度字符串,L是可以存储的长度,单位为字符,最大长度值可以为255
char(L)
样例:
mysql> insert into tt9 values(100, 'ab');
Query OK, 1 row affected (0.02 sec)
mysql> insert into tt9 values(101, '中国');
Query OK, 1 row affected (0.01 sec)
mysql> select * from tt9;
+------+--------+
| id | name |
+------+--------+
| 100 | ab |
| 101 | 中国 |
+------+--------+
2 rows in set (0.00 sec)
mysql> create table tt10(id int ,name char(256));
ERROR 1074 (42000): Column length too big for column 'name' (max = 255); use BLOB or TEXT instead
4.3.2 varchar
可变长度字符串,L表示字符长度,最大长度65535个字节
varchar(L)
样例:
mysql> create table tt10(id int ,name varchar(6));
Query OK, 0 rows affected (0.06 sec)
mysql> insert into tt10 values(100, 'hello');
Query OK, 1 row affected (0.01 sec)
mysql> insert into tt10 values(100,'cqw,中国');
Query OK, 1 row affected (0.02 sec)
mysql> select * from tt10;
+------+------------+
| id | name |
+------+------------+
| 100 | hello |
| 100 | cqw,中国 |
+------+------------+
2 rows in set (0.00 sec)
关于varchar(len),len到底是多大,这个len值,和表的编码密切相关:
- varchar长度可以指定为0到65535之间的值,但是有1 - 3 个字节用于记录数据大小,所以说有效字 节数是65532。
- 当我们的表的编码是utf8时,varchar(n)的参数n最大值是65532/3=21844[因为utf中,一个字符占 用3个字节],如果编码是gbk,varchar(n)的参数n最大是65532/2=32766(因为gbk中,一个字符 占用2字节)。
验证了utf8确实是不能超过21844:
mysql> create table tt11(name varchar(21845))charset=utf8;
ERROR 1118 (42000): Row size too large. The maximum row size for the used table type, not counting BLOBs, is 65535. This includes storage overhead, check the manual. You have to change some columns to TEXT or BLOBs
4.3.3 char和varchar比较

如何选择定长或变长字符串?
- 如果数据确定长度都一样,就使用定长(char),比如:身份证,手机号,md5
- 如果数据长度有变化,就使用变长(varchar), 比如:名字,地址,但是你要保证最长的能存的进去。
- 定长的磁盘空间比较浪费,但是效率高。
- 变长的磁盘空间比较节省,但是效率低。
- 定长的意义是,直接开辟好对应的空间
- 变长的意义是,在不超过自定义范围的情况下,用多少,开辟多少。
4.4 日期和时间类型
常用的日期有如下三个:
- date :日期 'yyyy-mm-dd' ,占用三字节
- datetime 时间日期格式 'yyyy-mm-dd HH:ii:ss' 表示范围从 1000 到 9999 ,占用八字节
- timestamp :时间戳,从1970年开始的 yyyy-mm-dd HH:ii:ss 格式和 datetime 完全一致,占用 四字节
样例:
我们插入两种时间,添加数据时,时间戳自动补上当前时间,更新数据,时间戳会更新成当前时间
其实timestamp 不会天生自动填充
- MySQL 5.6.6 之前(老版本) :该变量默认
OFF,表中第一个 timestamp 字段 会被隐式设置为DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,也就是插入时自动填当前时间,更新时自动刷新时间,无需手动写规则。- MySQL 5.6.6+、8.0+(新版本) :该变量默认
ON(8.0+ 强制开启,无法关闭),彻底取消了 timestamp 的隐式自动赋值特性。此时 timestamp 和 datetime 行为完全一致,你不手动设置默认值规则,它就不会自动填充。
我这里使用的就是新版本:
mysql> create table birthday (
-> t1 date,
-> t2 datetime,
-> t3 timestamp DEFAULT CURRENT_TIMESTAMP COMMENT '插入时自动填充当前时间'
-> );
Query OK, 0 rows affected (0.04 sec)
mysql> insert into birthday(t1,t2) values('2006-06-28','2024-09-01 12:1:1');
Query OK, 1 row affected (0.00 sec)
mysql> select * from birthday;
+------------+---------------------+---------------------+
| t1 | t2 | t3 |
+------------+---------------------+---------------------+
| 2006-06-28 | 2024-09-01 12:01:01 | 2026-05-04 14:17:31 |
+------------+---------------------+---------------------+
1 row in set (0.00 sec)
如果还要自动更新时间:
drop table if exists birthday;
create table birthday (
t1 date,
t2 datetime,
create_time timestamp DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间,插入时自动填充',
update_time timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间,插入/修改时自动刷新'
);
4.5 enum和set
语法:
- enum:枚举,"单选"类型;
- enum('选项1','选项2','选项3',...);
该设定只是提供了若干个选项的值,最终一个单元格中,实际只存储了其中一个值;而且出于效率考虑,这些值实际存储的是**"数字"**,因为这些选项的每个选项值依次对应如下数字:1,2,3,....最多65535 个;当我们添加枚举值时,也可以添加对应的数字编号。
- set:集合,"多选"类型;
- set('选项值1','选项值2','选项值3', ...);
该设定只是提供了若干个选项的值,最终一个单元格中,设计可存储了其中任意多个值;而且出于效率 考虑,这些值实际存储的是**"数字"**,因为这些选项的每个选项值依次对应如下数字:1,2,4,8,16,32,.... 最多64个。
样例:
cpp
mysql> create table vates(
-> name varchar(20),
-> hobby set('登山','游泳','篮球','武术'),
-> gender enum('男','女')
-> );
Query OK, 0 rows affected (0.03 sec)
mysql> insert into vates values('cqw','登山,游泳','女');\
Query OK, 1 row affected (0.01 sec)
mysql> insert into vates values('c','1','1');
Query OK, 1 row affected (0.02 sec)
mysql> insert into vates values('q','2,3','2');
ERROR 1265 (01000): Data truncated for column 'hobby' at row 1
mysql> select * from vates;
+------+---------------+--------+
| name | hobby | gender |
+------+---------------+--------+
| cqw | 登山,游泳 | 女 |
| c | 登山 | 男 |
+------+---------------+--------+
2 rows in set (0.00 sec)
mysql> insert into vates values('q','2','2');
Query OK, 1 row affected (0.01 sec)
mysql> insert into vates values('w','3','2');
Query OK, 1 row affected (0.02 sec)
mysql> insert into vates values('w','4','2');
Query OK, 1 row affected (0.01 sec)
mysql> select * from vates;
+------+---------------+--------+
| name | hobby | gender |
+------+---------------+--------+
| cqw | 登山,游泳 | 女 |
| c | 登山 | 男 |
| q | 游泳 | 女 |
| w | 登山,游泳 | 女 |
| w | 篮球 | 女 |
+------+---------------+--------+
5 rows in set (0.00 sec)
想查找所有喜欢登山的人:
cpp
mysql> select * from vates where hobby='登山';
+------+--------+--------+
| name | hobby | gender |
+------+--------+--------+
| c | 登山 | 男 |
+------+--------+--------+
1 row in set (0.00 sec)
集合查询使用find_ in_ set函数:
find_in_set(sub,str_list) :如果 sub 在 str_list 中,则返回下标;如果不在,返回0;
str_list是用逗号分隔的字符串。
cpp
mysql> select find_in_set('a', 'a,b,c');
+---------------------------+
| find_in_set('a', 'a,b,c') |
+---------------------------+
| 1 |
+---------------------------+
1 row in set (0.00 sec)
mysql> select find_in_set('d', 'a,b,c');
+---------------------------+
| find_in_set('d', 'a,b,c') |
+---------------------------+
| 0 |
+---------------------------+
1 row in set (0.01 sec)
查询爱好登山的人:
cpp
mysql> select * from vates where find_in_set('登山', hobby);
+------+---------------+--------+
| name | hobby | gender |
+------+---------------+--------+
| cqw | 登山,游泳 | 女 |
| c | 登山 | 男 |
| w | 登山,游泳 | 女 |
+------+---------------+--------+
3 rows in set (0.00 sec)
五、表的约束
我们之前提到过真正约束字段的是数据类型,但是数据类型约束很单一,需要有一些额外的约束,更好的保证数据的合法性,从业务逻辑角度保证数据的正确性。比如有一个字段是email,要求是唯一的。
表的约束很多,这里主要介绍如下几个: null/not null,default, comment, zerofill,primary key,auto_increment,unique key 。
5.1空属性
两个值:null(默认的)和not null(不为空)
数据库默认字段基本都是字段为空,但是实际开发时,尽可能保证字段不为空,因为数据为空没办 法参与运算。
样例:
创建一个班级表,包含班级名和班级所在的教室。
站在正常的业务逻辑中:
- 如果班级没有名字,你不知道你在哪个班级
- 如果教室名字可以为空,就不知道在哪上课
所以我们在设计数据库表的时候,一定要在表中进行限制,满足上面条件的数据就不能插入到表中。这就是**"约束"**。
cpp
mysql> create table myclass(
-> name varchar(20) not null,
-> room varchar(10) not null
-> );
Query OK, 0 rows affected (0.08 sec)
mysql> desc myclass;
+-------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| name | varchar(20) | NO | | NULL | |
| room | varchar(10) | NO | | NULL | |
+-------+-------------+------+-----+---------+-------+
2 rows in set (0.01 sec)
如果我们插入数据时没有给教室数据,则插入失败:
cpp
mysql> insert into myclass(name) values('class1');
ERROR 1364 (HY000): Field 'room' doesn't have a default value
5.2 默认值
默认值:某一种数据会经常性的出现某个具体的值,可以在一开始就指定好,在需要真实数据的时候, 用户可以选择性的使用默认值。
cpp
mysql> create table tt12(
-> name varchar(20) not null,
-> age tinyint unsigned default 0,
-> sex char(2) default '男'
-> );
Query OK, 0 rows affected (0.03 sec)
mysql> desc tt12;
+-------+------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+------------------+------+-----+---------+-------+
| name | varchar(20) | NO | | NULL | |
| age | tinyint unsigned | YES | | 0 | |
| sex | char(2) | YES | | 男 | |
+-------+------------------+------+-----+---------+-------+
3 rows in set (0.00 sec)
默认值的生效:数据在插入的时候不给该字段赋值,就使用默认值
注意:只有设置了default的列,才可以在插入值的时候,对列进行省略
cpp
mysql> insert into tt12(name) values('cqw');
Query OK, 1 row affected (0.01 sec)
mysql> select * from tt12;
+------+------+------+
| name | age | sex |
+------+------+------+
| cqw | 0 | 男 |
+------+------+------+
1 row in set (0.00 sec)
5.3 列描述
列描述:comment,没有实际含义,专门用来描述字段,会根据表创建语句保存,用来给程序员或DBA 来进行了解。
cpp
mysql> create table tt13 (
-> name varchar(20) not null comment '姓名',
-> age tinyint unsigned default 0 comment '年龄',
-> sex char(2) default '男' comment '性别'
-> );
Query OK, 0 rows affected (0.05 sec)
通过desc查看不到注释信息:
cpp
mysql> desc tt13;
+-------+------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+------------------+------+-----+---------+-------+
| name | varchar(20) | NO | | NULL | |
| age | tinyint unsigned | YES | | 0 | |
| sex | char(2) | YES | | 男 | |
+-------+------------------+------+-----+---------+-------+
3 rows in set (0.01 sec)
通过show可以看到:
cpp
mysql> show create table tt13\G
*************************** 1. row ***************************
Table: tt13
Create Table: CREATE TABLE `tt13` (
`name` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '姓名',
`age` tinyint unsigned DEFAULT '0' COMMENT '年龄',
`sex` char(2) COLLATE utf8mb4_unicode_ci DEFAULT '男' COMMENT '性别'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
1 row in set (0.00 sec)
5.4 zerofill
刚开始学习数据库时,很多人对数字类型后面的长度很迷茫。
通过show看看tt3表的建表语句:
cpp
mysql> show create table tt3\G
***************** 1. row *****************
Table: tt3
Create Table: CREATE TABLE `tt3` (
`a` int(10) unsigned DEFAULT NULL,
`b` int(10) unsigned DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=gbk
1 row in set (0.00 sec)
可以看到int(10),这个代表什么意思呢?整型不是4字节码?这个10又代表什么呢?其实没有zerofill这个 属性,括号内的数字是毫无意义的。a和b列就是前面插入的数据,如下:
cpp
mysql> insert into tt15 values(1,2);
Query OK, 1 row affected (0.01 sec)
mysql> select * from tt15;
+------+------+
| a | b |
+------+------+
| 1 | 2 |
+------+------+
1 row in set (0.00 sec)
但是对列添加了zerofill属性后,显示的结果就有所不同了。修改tt3表的属性:
cpp
mysql> alter table tt15 change a a int(5) unsigned zerofill;
Query OK, 1 row affected, 2 warnings (0.06 sec)
Records: 1 Duplicates: 0 Warnings: 2
mysql> show create table tt15\G
*************************** 1. row ***************************
Table: tt15
Create Table: CREATE TABLE `tt15` (
`a` int(5) unsigned zerofill DEFAULT NULL,
`b` int DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
1 row in set (0.00 sec)
mysql> select * from tt15;
+-------+------+
| a | b |
+-------+------+
| 00001 | 2 |
+-------+------+
1 row in set (0.00 sec)
这次可以看到a的值由原来的1变成00001,这就是zerofill属性的作用,如果宽度小于设定的宽度(这里设置的是5),自动填充0。要注意的是,这只是最后显示的结果,在MySQL中实际存储的还是1。
cpp
mysql> select a,hex(a) from tt15;
+-------+--------+
| a | hex(a) |
+-------+--------+
| 00001 | 1 |
+-------+--------+
1 row in set (0.01 sec)
5.5 主键
主键:primary key用来唯一的约束该字段里面的数据,**不能重复,不能为空,**一张表中最多只能有一个 主键;主键所在的列通常是整数类型。
样例:
- 创建表的时候直接在字段上指定主键:key 中 pri表示该字段是主键
cpp
mysql> create table tt16(
-> id int unsigned primary key comment '学号不能为空',
-> name varchar(20) not null
-> );
Query OK, 0 rows affected (0.03 sec)
mysql> desc tt16;
+-------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+--------------+------+-----+---------+-------+
| id | int unsigned | NO | PRI | NULL | |
| name | varchar(20) | NO | | NULL | |
+-------+--------------+------+-----+---------+-------+
2 rows in set (0.00 sec)
我们的创建信息上也有写:
cpp
mysql> show create table tt16\G
*************************** 1. row ***************************
Table: tt16
Create Table: CREATE TABLE `tt16` (
`id` int unsigned NOT NULL COMMENT '学号不能为空',
`name` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
1 row in set (0.00 sec)
- 主键约束:主键对应的字段中不能重复,一旦重复,操作失败。
cpp
mysql> insert into tt16 values(1,'cqw');
Query OK, 1 row affected (0.02 sec)
mysql> insert into tt16 values(1,'cqy');
ERROR 1062 (23000): Duplicate entry '1' for key 'tt16.PRIMARY'
- 当表创建好以后但是没有主键的时候,可以再次追加主键:追加前把重复的删掉
cpp
alter table 表名 add primary key(字段列表)
- 删除主键:
cpp
alter table 表名 drop primary key;
cpp
mysql> alter table tt16 drop primary key;
Query OK, 1 row affected (0.04 sec)
Records: 1 Duplicates: 0 Warnings: 0
mysql> desc tt16;
+-------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+--------------+------+-----+---------+-------+
| id | int unsigned | NO | | NULL | |
| name | varchar(20) | NO | | NULL | |
+-------+--------------+------+-----+---------+-------+
2 rows in set (0.01 sec)
- 复合主键
在创建表的时候,在所有字段之后,使用primary key(主键字段列表)来创建主键,如果有多个字段 作为主键,可以使用复合主键。
cpp
mysql> insert into tt17 (id,course)values(1, '001');
Query OK, 1 row affected (0.01 sec)
mysql> insert into tt17 (id,course)values(1, '002');
Query OK, 1 row affected (0.00 sec)
mysql> insert into tt17 (id,course)values(2, '002');
Query OK, 1 row affected (0.01 sec)
mysql> insert into tt17 (id,course)values(1, '001');
ERROR 1062 (23000): Duplicate entry '1-001' for key 'tt17.PRIMARY'
mysql> select * from tt17;
+----+--------+-------+
| id | course | score |
+----+--------+-------+
| 1 | 001 | 60 |
| 1 | 002 | 60 |
| 2 | 002 | 60 |
+----+--------+-------+
3 rows in set (0.00 sec)
5.6 自增长
auto_increment:当对应的字段,不给值,会自动的被系统触发,系统会从当前字段中已经有的最大值 +1操作,得到一个新的不同的值。通常和主键搭配使用,作为逻辑主键。
自增长的特点:
- 任何一个字段要做自增长,前提是本身是一个索引(key一栏有值)
- 自增长字段必须是整数
- 一张表最多只能有一个自增长
样例:
cpp
mysql> create table tt18(
-> id int unsigned primary key auto_increment,
-> name varchar(10) not null default ''
-> );
Query OK, 0 rows affected (0.02 sec)
mysql> insert into tt18(name) values('a');
Query OK, 1 row affected (0.01 sec)
mysql> insert into tt18(name) values('b');
Query OK, 1 row affected (0.02 sec)
mysql> select * from tt18;
+----+------+
| id | name |
+----+------+
| 1 | a |
| 2 | b |
+----+------+
2 rows in set (0.00 sec)
在插入后获取上次插入的 AUTO_INCREMENT 的值(批量插入获取的是第一个值):
cpp
mysql> select last_insert_id();
+------------------+
| last_insert_id() |
+------------------+
| 2 |
+------------------+
1 row in set (0.00 sec)
索引: 在关系数据库中,索引是一种单独的、物理的对数据库表中一列或多列的值进行排序的一种存储结构,它是某个表中一列或若干列值的集合和相应的指向表中物理标识这些值的数据页的逻辑指针清单。 索引的作用相当于图书的目录,可以根据目录中的页码快速找到所需的内容。
索引提供指向存储在表的指定列中的数据值的指针,然后根据您指定的排序顺序对这些指针排序。 数据库使用索引以找到特定值,然后顺指针找到包含该值的行。这样可以使对应于表的SQL语句执行得 更快,可快速访问数据库表中的特定信息。
5.7 唯一键
一张表中有往往有很多字段需要唯一性,数据不能重复,但是一张表中只能有一个主键:唯一键就可以 解决表中有多个字段需要唯一性约束的问题。
唯一键的本质和主键差不多,唯一键允许为空,而且可以多个为空,空字段不做唯一性比较。
关于唯一键和主键的区别: 我们可以简单理解成,主键更多的是标识唯一性的。而唯一键更多的是保证在业务上,不要和别的信息出现重复。
样例:
cpp
mysql> create table student (
-> id char(10) unique comment '学号,不能重复,但可以为空',
-> name varchar(10)
-> );
Query OK, 0 rows affected (0.04 sec)
mysql> insert into student(id, name) values('01', 'aaa');
Query OK, 1 row affected (0.00 sec)
mysql> insert into student(id, name) values('01', 'bbb');
ERROR 1062 (23000): Duplicate entry '01' for key 'student.id'
mysql> insert into student(id, name) values(null, 'bbb');
Query OK, 1 row affected (0.01 sec)
mysql> insert into student(name) values('bbb');
Query OK, 1 row affected (0.01 sec)
mysql> select * from student;
+------+------+
| id | name |
+------+------+
| 01 | aaa |
| NULL | bbb |
| NULL | bbb |
+------+------+
3 rows in set (0.00 sec)
5.8 外键
外键用于定义主表和从表之间的关系:外键约束主要定义在从表上,主表则必须是有主键约束或unique 约束。当定义外键后,要求外键列数据必须在主表的主键列存在或为null。
cpp
foreign key (字段名) references 主表(列)

样例:
- 先创建主键表:
cpp
create table myclass (
id int primary key,
name varchar(30) not null comment'班级名'
);
- 再创建从表:
cpp
create table stu (
id int primary key,
name varchar(30) not null comment '学生名',
class_id int,
foreign key (class_id) references myclass(id)
);
- 正常插入数据:
cpp
mysql> insert into myclass values(10, 'C++大牛班');
Query OK, 1 row affected (0.00 sec)
mysql> insert into myclass values(20, 'java大神班');
Query OK, 1 row affected (0.01 sec)
mysql> insert into stu values(100, '张三', 10);
Query OK, 1 row affected (0.01 sec)
mysql> insert into stu values(101, '李四', 20);
Query OK, 1 row affected (0.01 sec)
- 插入一个班级号为30的学生,因为没有这个班级,所以插入不成功:
cpp
mysql> insert into stu values(102, 'cqw',30);
ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint fails (`test`.`stu`, CONSTRAINT `stu_ibfk_1` FOREIGN KEY (`class_id`) REFERENCES `myclass` (`id`))
插入班级id为null,比如来了一个学生,目前还没有分配班级:
cpp
mysql> insert into stu values(102, 'cqw', null);
Query OK, 1 row affected (0.01 sec)
在我们显示生活中,很多数据很多都是相关性的。就上述的样例来说,虽然我们可以正常建立一个学生表一个班级表,该有的字段我们都有,但在实际使用的时候,可能插入的学生信息中有具体的班级,但是该班级却没有在班级表中。比如学校只开了100班,101班,但是在上课的学生里面竟然有102班的学生(这个班目前并 不存在),这很明显是有问题的。
因为此时两张表在业务上是有相关性的,但是在业务上没有建立约束关系,那么就可能出现问题。
解决方案就是通过外键完成的。建立外键的本质其实就是把相关性交给mysql去审核了,提前告诉mysql 表之间的约束关系,那么当用户插入不符合业务逻辑的数据的时候,mysql不允许你插入。
5.9 综合案例
有一个商店的数据,记录客户及购物情况,有以下三个表组成:
- 商品goods(商品编号goods_id,商品名goods_name, 单价unitprice, 商品类别category, 供应商 provider)
- 客户customer(客户号customer_id,姓名name,住址address,邮箱email,性别sex,身份证card_id)
- 购买purchase(购买订单号order_id,客户号customer_id,商品号goods_id,购买数量nums)
要求:
- 每个表的主外键
- 客户的姓名不能为空值
- 邮箱不能重复
- 客户的性别(男,女)
cpp
-- 创建数据库
create database if not exists mall
default character set utf8 ;
-- 选择数据库
use mall;
-- 创建数据库表
-- 商品
create table if not exists goods
(
goods_id int primary key auto_increment comment '商品编号',
goods_name varchar(32) not null comment '商品名称',
unitprice int not null default 0 comment '单价,单位分',
category varchar(12) comment '商品分类',
provider varchar(64) not null comment '供应商名称'
);
-- 客户
create table if not exists customer
(
customer_id int primary key auto_increment comment '客户编号',
name varchar(32) not null comment '客户姓名',
address varchar(256) comment '客户地址',
email varchar(64) unique key comment '电子邮箱',
sex enum('男','女') not null comment '性别',
card_id char(18) unique key comment '身份证'
);
-- 购买
create table if not exists purchase
(
order_id int primary key auto_increment comment '订单号',
customer_id int comment '客户编号',
goods_id int comment '商品编号',
nums int default 0 comment '购买数量',
foreign key (customer_id) references customer(customer_id),
foreign key (goods_id) references goods(goods_id)
);