MySQL数据库基础(二)
聚合函数
- 什么是聚合函数
聚合函数作用于一组数据,并对一组数据返回一个值。
聚合函数类型
- AVG()
- SUM()
- MAX()
- MIN()
- COUNT()
聚合函数不能嵌套调用。比如不能出现类似
AVG(SUM(字段名称))
形式的调用。
AVG和SUM函数
可以对数值型数据使用AVG
和SUM
函数。
1 | SELECT AVG(salary), MAX(salary),MIN(salary), SUM(salary) |
MIN和MAX函数
可以对任意数据类型的数据使用MIN
和MAX
函数。
1 | SELECT MIN(hire_date), MAX(hire_date) |
COUNT函数
COUNT(*)
返回表中记录总数,适用于任意数据类型。
1 | SELECT COUNT(*) |
COUNT(expr)
返回expr不为空的记录总数。
1 | SELECT COUNT(commission_pct) |
问题:用count(*),count(1),count(列名)谁好呢?
其实,对于MyISAM引擎的表是没有区别的。这种引擎内部有一计数器在维护着行数。
Innodb引擎的表用count(*),count(1)直接读行数,复杂度是O(n),因为innodb真的要去数一遍。但好于具体的count(列名)。
GROUP BY
GROUP BY
子句将查询结果按某一列或多列的值分组,值相等的为一组。对查询结果分组的目的是为了细化聚合函数的作用对象。如果未对查询结果分组,聚合函数将作用于整个查询结果。
在SELECT列表中所有未包含在组函数中的列都应该包含在 GROUP BY子句中
1 | SELECT department_id, AVG(salary) |
GROUP BY中使用WITH ROLLUP
使用WITH ROLLUP
关键字之后,在所有查询出的分组记录之后增加一条记录,该记录计算查询出的所有记录的总和,即统计记录数量。
1 | SELECT department_id,AVG(salary) |
HAVING
过滤分组:HAVING
子句
- 行已经被分组。
- 使用了聚合函数。
- 满足HAVING 子句中条件的分组将被显示。
HAVING
不能单独使用,必须要跟GROUP BY
一起使用。
1 | SELECT department_id, MAX(salary) |
- 非法使用聚合函数 : 不能在 WHERE 子句中使用聚合函数。如下:
1 | SELECT department_id, AVG(salary) |
ERROR at line 3: ORA-00934. group function is not allowed here
WHERE和HAVING的对比
区别1:WHERE 可以直接使用表中的字段作为筛选条件,但不能使用分组中的计算函数作为筛选条件;HAVING 必须要与 GROUP BY 配合使用,可以把分组计算的函数和分组字段作为筛选条件。
这决定了,在需要对数据进行分组统计的时候,HAVING
可以完成WHERE
不能完成的任务。这是因为,在查询语法结构中,WHERE
在GROUP BY
之前,所以无法对分组结果进行筛选。HAVING
在GROUP BY
之后,可以使用分组字段和分组中的计算函数,对分组的结果集进行筛选,这个功能是WHERE
无法完成的。另外,WHERE
排除的记录不再包括在分组中。
区别2:如果需要通过连接从关联表中获取需要的数据,WHERE 是先筛选后连接,而 HAVING 是先连接后筛选。 这一点,就决定了在关联查询中,WHERE
比HAVING
更高效。因为WHERE
可以先筛选,用一个筛选后的较小数据集和关联表进行连接,这样占用的资源比较少,执行效率也比较高。HAVING 则需要先把结果集准备好,也就是用未被筛选的数据集进行关联,然后对这个大的数据集进行筛选,这样占用的资源就比较多,执行效率也较低。
小结如下:
优点 | 缺点 | |
---|---|---|
WHERE | 先筛选数据再关联,执行效率高 | 不能使用分组中的计算函数进行筛选 |
HAVING | 可以使用分组中的计算函数 | 在最后的结果集中进行筛选,执行效率较低 |
开发中的选择:
WHERE
和HAVING
也不是互相排斥的,我们可以在一个查询里面同时使用WHERE
和HAVING
。包含分组统计函数的条件用HAVING
,普通条件用WHERE
。这样,我们就既利用了WHERE
条件的高效快速,又发挥了HAVING
可以使用包含分组统计函数的查询条件的优点。当数据量特别大的时候,运行效率会有很大的差别。
SELECT的执行过程
1 | SELECT [ALL | DISTINCT] < 目标列表达式 > [,< 目标列表达式 >]... |
SELECT执行顺序
你需要记住 SELECT 查询时的两个顺序:
1. 关键字的顺序是不能颠倒的:
1 | SELECT ... FROM ... WHERE ... GROUP BY ... HAVING ... ORDER BY ... LIMIT... |
2.SELECT 语句的执行顺序(在 MySQL 和 Oracle 中,SELECT 执行顺序基本相同):
1 | FROM -> WHERE -> GROUP BY -> HAVING -> SELECT 的字段 -> DISTINCT -> ORDER BY -> LIMIT |
比如你写了一个 SQL 语句,那么它的关键字顺序和执行顺序是下面这样的:
1 | SELECT DISTINCT player_id, player_name, count(*) as num # 顺序 5 |
在 SELECT 语句执行这些步骤的时候,每个步骤都会产生一个虚拟表
,然后将这个虚拟表传入下一个步骤中作为输入。需要注意的是,这些步骤隐含在 SQL 的执行过程中,对于我们来说是不可见的。
SQL 的执行原理
SELECT
是先执行FROM
这一步的。在这个阶段,如果是多张表联查,还会经历下面的几个步骤:
- 首先先通过
CROSS JOIN
求笛卡尔积,相当于得到虚拟表 vt(virtual table)1-1; - 通过
ON
进行筛选,在虚拟表 vt1-1 的基础上进行筛选,得到虚拟表 vt1-2; - 添加外部行。如果我们使用的是左连接、右链接或者全连接,就会涉及到外部行,也就是在虚拟表 vt1-2 的基础上增加外部行,得到虚拟表 vt1-3。
当然如果我们操作的是两张以上的表,还会重复上面的步骤,直到所有表都被处理完为止。这个过程得到是我们的原始数据。
当我们拿到了查询数据表的原始数据,也就是最终的虚拟表 vt1
,就可以在此基础上再进行 WHERE 阶段
。在这个阶段中,会根据 vt1 表的结果进行筛选过滤,得到虚拟表 vt2
。
然后进入第三步和第四步,也就是 GROUP
和HAVING
阶段。在这个阶段中,实际上是在虚拟表 vt2 的基础上进行分组和分组过滤,得到中间的虚拟表 vt3
和 vt4
。
当我们完成了条件筛选部分之后,就可以筛选表中提取的字段,也就是进入到 SELECT
和DISTINCT
阶段。
首先在SELECT
阶段会提取想要的字段,然后在DISTINCT
阶段过滤掉重复的行,分别得到中间的虚拟表 vt5-1
和 vt5-2
。
当我们提取了想要的字段数据之后,就可以按照指定的字段进行排序,也就是 ORDER BY
阶段,得到虚拟表 vt6
。
最后在 vt6 的基础上,取出指定行的记录,也就是LIMIT
阶段,得到最终的结果,对应的是虚拟表 vt7
。
当然我们在写 SELECT 语句的时候,不一定存在所有的关键字,相应的阶段就会省略。
同时因为 SQL 是一门类似英语的结构化查询语言,所以我们在写 SELECT 语句的时候,还要注意相应的关键字顺序,所谓底层运行的原理,就是我们刚才讲到的执行顺序。
子查询
子查询指一个查询语句嵌套在另一个查询语句内部的查询,这个特性从MySQL 4.1开始引入。
- 子查询(内查询)在主查询之前一次执行完成。
- 子查询的结果被主查询(外查询)使用 。
- 注意事项
- 子查询要包含在括号内
- 将子查询放在比较条件的右侧
- 单行操作符对应单行子查询,多行操作符对应多行子查询
单行子查询
单行比较操作符
操作符 | 含义 |
---|---|
= |
equal to |
> |
greater than |
>= |
greater than or equal to |
< |
less than |
<= |
less than or equal to |
<> |
not equal to |
题目:查询工资大于149号员工工资的员工的信息
1 | SELECT last_name |
多行子查询
- 也称为集合比较子查询
- 内查询返回多行
- 使用多行比较操作符
多行比较操作符
操作符 | 含义 |
---|---|
IN |
等于列表中的任意一个 |
ANY |
需要和单行比较操作符一起使用,和子查询返回的某一个值比较 |
ALL |
需要和单行比较操作符一起使用,和子查询返回的所有值比较 |
SOME |
实际上是ANY的别名,作用相同,一般常使用ANY |
题目:查询平均工资最低的部门id
1 | SELECT department_id |
相关子查询
相关子查询执行流程
如果子查询的执行依赖于外部查询,通常情况下都是因为子查询中的表用到了外部的表,并进行了条件关联,因此每执行一次外部查询,子查询都要重新计算一次,这样的子查询就称之为关联子查询
。
相关子查询按照一行接一行的顺序执行,主查询的每一行都执行一次子查询。
题目:找出每个学生超过他选修课程平均成绩的课程号
1 | SELECT Sno, Cno |
EXISTS 与 NOT EXISTS关键字
EXISTS
代表存在量词。带有EXISTS
谓词的子查询不返回任何数据,只产生逻辑真值true
或逻辑假值false
。
使用存在量词EXISTS
后,若内存查询结果非空,则外层的WHERE子
句返回真值,否则返回假值。
NOT EXISTS
关键字表示如果不存在某种条件,则返回TRUE
,否则返回FALSE
。
题目:查询departments表中,不存在于employees表中的部门的department_id和department_name
1 | SELECT department_id, department_name |
创建和管理表
标识符命名规则
- 数据库名、表名不得超过
30
个字符,变量名限制为29个 - 必须只能包含
A–Z
,a–z
,0–9
,_
共63个字符 - 数据库名、表名、字段名等对象名中间不要包含空格
- 同一个MySQL软件中,数据库不能同名;同一个库中,表不能重名;同一个表中,字段不能重名
- 必须保证你的字段没有和保留字、数据库系统或常用方法冲突。如果坚持使用,请在SQL语句中使用
`
(着重号)引起来 - 保持字段名和类型的一致性:在命名字段并为其指定数据类型的时候一定要保证一致性,假如数据类型在一个表里是整数,那在另一个表里可就别变成字符型了
MySQL中的数据类型
类型 | 类型举例 |
---|---|
整数类型 | TINYINT、SMALLINT、MEDIUMINT、**INT(或INTEGER)**、BIGINT |
浮点类型 | FLOAT、DOUBLE |
定点数类型 | DECIMAL |
位类型 | BIT |
日期时间类型 | YEAR、TIME、DATE、DATETIME、TIMESTAMP |
文本字符串类型 | CHAR、VARCHAR、TINYTEXT、TEXT、MEDIUMTEXT、LONGTEXT |
枚举类型 | ENUM |
集合类型 | SET |
二进制字符串类型 | BINARY、VARBINARY、TINYBLOB、BLOB、MEDIUMBLOB、LONGBLOB |
JSON类型 | JSON对象、JSON数组 |
空间数据类型 | 单值:GEOMETRY、POINT、LINESTRING、POLYGON; 集合:MULTIPOINT、MULTILINESTRING、MULTIPOLYGON、GEOMETRYCOLLECTION |
其中,常用的几类类型介绍如下:
数据类型 | 描述 |
---|---|
INT | 从-2^31到2^31-1的整型数据。存储大小为 4个字节 |
CHAR(size) | 定长字符数据。若未指定,默认为1个字符,最大长度255 |
VARCHAR(size) | 可变长字符数据,根据字符串实际长度保存,必须指定长度 |
FLOAT(M,D) | 单精度,占用4个字节,M=整数位+小数位,D=小数位。 D<=M<=255,0<=D<=30,默认M+D<=6 |
DOUBLE(M,D) | 双精度,占用8个字节,D<=M<=255,0<=D<=30,默认M+D<=15 |
DECIMAL(M,D) | 高精度小数,占用M+2个字节,D<=M<=65,0<=D<=30,最大取值范围与DOUBLE相同。 |
DATE | 日期型数据,格式’YYYY-MM-DD’ |
BLOB | 二进制形式的长文本数据,最大可达4G |
TEXT | 长文本数据,最大可达4G |
创建和管理数据库
创建数据库
- 方式1:创建数据库
1 | CREATE DATABASE 数据库名; |
- 方式2:创建数据库并指定字符集
1 | CREATE DATABASE 数据库名 CHARACTER SET 字符集; |
- 方式3:判断数据库是否已经存在,不存在则创建数据库(
推荐
)
1 | CREATE DATABASE IF NOT EXISTS 数据库名; |
如果MySQL中已经存在相关的数据库,则忽略创建语句,不再创建数据库。
修改数据库
- 更改数据库字符集
1 | ALTER DATABASE 数据库名 CHARACTER SET 字符集; #比如:gbk、utf8等 |
删除数据库
- 方式1:删除指定的数据库
1 | DROP DATABASE 数据库名; |
- 方式2:删除指定的数据库(
推荐
)
1 | DROP DATABASE IF EXISTS 数据库名; |
创建表
创建方式1
- 必须具备:
- CREATE TABLE权限
- 存储空间
- 语法格式:
1 | CREATE TABLE [IF NOT EXISTS] 表名( |
必须指定:
- 表名
- 列名(或字段名),数据类型,长度
可选指定:
- 约束条件
- 默认值
创建表举例:
1 | CREATE TABLE dept( |
创建方式2 基于查询创建
1 | CREATE TABLE dept80 |
查看数据表结构
在MySQL中创建好数据表之后,可以查看数据表的结构。MySQL支持使用DESCRIBE/DESC
语句查看数据表结构,也支持使用SHOW CREATE TABLE
语句查看数据表结构。
语法格式如下:
1 | SHOW CREATE TABLE 表名\G |
使用SHOW CREATE TABLE
语句不仅可以查看表创建时的详细语句,还可以查看存储引擎和字符编码。
修改表
修改表指的是修改数据库中已经存在的数据表的结构。
使用 ALTER TABLE 语句可以实现:
向已有的表中添加列
修改现有表中的列
删除现有表中的列
重命名现有表中的列
追加一个列
语法格式如下:
1 | ALTER TABLE 表名 ADD 【COLUMN】 字段名 字段类型 【FIRST|AFTER 字段名】; |
举例:
1 | ALTER TABLE dept80 |
修改一个列
可以修改列的数据类型,长度、默认值和位置
修改字段数据类型、长度、默认值、位置的语法格式如下:
1 | ALTER TABLE 表名 MODIFY 【COLUMN】 字段名1 字段类型 【DEFAULT 默认值】【FIRST|AFTER 字段名2】; |
- 举例:
1 | ALTER TABLE dept80 |
1 | ALTER TABLE dept80 |
- 对默认值的修改只影响今后对表的修改
重命名一个列
使用 CHANGE old_column new_column dataType
子句重命名列。语法格式如下:
1 | ALTER TABLE 表名 CHANGE 【column】 列名 新列名 新数据类型; |
举例:
1 | ALTER TABLE dept80 |
删除一个列
删除表中某个字段的语法格式如下:
1 | ALTER TABLE 表名 DROP 【COLUMN】字段名 |
举例:
1 | ALTER TABLE dept80 |
重命名表
- 方式一:使用
RENAME
1 | RENAME TABLE emp |
- 方式二:
1 | ALTER table dept |
- 必须是对象的拥有者
删除表
在MySQL中,当一张数据表
没有与其他任何数据表形成关联关系
时,可以将当前数据表直接删除。数据和结构都被删除
所有正在运行的相关事务被提交
所有相关索引被删除
语法格式:
1 | DROP TABLE [IF EXISTS] 数据表1 [, 数据表2, …, 数据表n]; |
IF EXISTS
的含义为:如果当前数据库中存在相应的数据表,则删除数据表;如果当前数据库中不存在相应的数据表,则忽略删除语句,不再执行删除数据表的操作。
- 举例:
1 | DROP TABLE dept80; |
DROP TABLE
语句不能回滚
清空表
TRUNCATE TABLE
语句:- 删除表中所有的数据
- 释放表的存储空间
举例:
1 | TRUNCATE TABLE detail_dept; |
TRUNCATE
语句不能回滚,而使用DELETE
语句删除数据,可以回滚对比:
1 | SET autocommit = FALSE; |
阿里巴巴《Java开发手册》之MySQL字段命名
【强制】表名、字段名必须使用小写字母或数字,禁止出现数字开头,禁止两个下划线中间只出现数字。数据库字段名的修改代价很大,因为无法进行预发布,所以字段名称需要慎重考虑。
- 正例:aliyun_admin,rdc_config,level3_name
- 反例:AliyunAdmin,rdcConfig,level_3_name
【强制】禁用保留字,如 desc、range、match、delayed 等,请参考 MySQL 官方保留字。
【强制】表必备三字段:id, gmt_create, gmt_modified。
- 说明:其中 id 必为主键,类型为BIGINT UNSIGNED、单表时自增、步长为 1。gmt_create, gmt_modified 的类型均为 DATETIME 类型,前者现在时表示主动式创建,后者过去分词表示被动式更新
【强制】表的命名最好是遵循 “业务名称_表的作用”。
- 正例:alipay_task 、 force_project、 trade_config
【强制】库名与应用名称尽量一致。
【参考】合适的字符存储长度,不但节约数据库表空间、节约索引存储,更重要的是提升检索速度。
- 正例:无符号值可以避免误存负数,且扩大了表示范围。
对象 | 年龄区间 | 类型 | 字节 | 表示范围 |
---|---|---|---|---|
人 | 150岁之内 | tinyint unsigned | 1 | 无符号值:0到255 |
龟 | 数百岁 | smallint unsigned | 2 | 无符号值:0到65535 |
恐龙化石 | 数千万年 | int unsigned | 4 | 无符号值:0到约43亿 |
太阳 | 约50亿年 | bigint unsigned | 8 | 无符号值:0到约10的19次方 |
拓展2:如何理解清空表、删除表等操作需谨慎?!
表删除
操作将把表的定义和表中的数据一起删除,并且MySQL在执行删除操作时,不会有任何的确认信息提示,因此执行删除操时应当慎重。在删除表前,最好对表中的数据进行备份
,这样当操作失误时可以对数据进行恢复,以免造成无法挽回的后果。
同样的,在使用ALTER TABLE进行表的基本修改操作时,在执行操作过程之前,也应该确保对数据进行完整的备份,因为数据库的改变是无法撤销
的,如果添加了一个不需要的字段,可以将其删除;相同的,如果删除了一个需要的列,该列下面的所有数据都将会丢失。
拓展3:MySQL8新特性—DDL的原子化
在MySQL 8.0版本中,InnoDB表的DDL支持事务完整性,即DDL操作要么成功要么回滚
。DDL操作回滚日志写入到data dictionary数据字典表mysql.innodb_ddl_log(该表是隐藏的表,通过show tables无法看到)中,用于回滚操作。通过设置参数,可将DDL操作日志打印输出到MySQL错误日志中。
分别在MySQL 5.7版本和MySQL 8.0版本中创建数据库和数据表,结果如下:
1 | CREATE DATABASE mytest; |
(1)在MySQL 5.7版本中,测试步骤如下:
删除数据表book1和数据表book2,结果如下:
1 | mysql> DROP TABLE book1,book2; |
再次查询数据库中的数据表名称,结果如下:
1 | mysql> SHOW TABLES; |
从结果可以看出,虽然删除操作时报错了,但是仍然删除了数据表book1。
(2)在MySQL 8.0版本中,测试步骤如下:
删除数据表book1和数据表book2,结果如下:
1 | mysql> DROP TABLE book1,book2; |
再次查询数据库中的数据表名称,结果如下:
1 | mysql> show tables; |
从结果可以看出,数据表book1并没有被删除。
数据处理之增删改
插入数据
方式1:VALUES的方式添加
使用这种语法一次只能向表中插入一条数据。
情况1:为表的所有字段按默认顺序插入数据
1 | INSERT INTO 表名 |
举例:
1 | INSERT INTO departments |
情况2:为表的指定字段插入数据
1 | INSERT INTO 表名(column1 [, column2, …, columnn]) |
为表的指定字段插入数据,就是在INSERT
语句中只向部分字段中插入值,而其他字段的值为表定义时的默认值。
举例:
1 | INSERT INTO departments(department_id, department_name) |
情况3:同时插入多条记录
INSERT
语句可以同时向数据表中插入多条记录,插入时指定多个值列表,每个值列表之间用逗号分隔开,基本语法格式如下:
1 | INSERT INTO table_name |
或者
1 | INSERT INTO table_name(column1 [, column2, …, columnn]) |
举例:
1 | mysql> INSERT INTO emp(emp_id,emp_name) |
使用INSERT同时插入多条记录时,MySQL会返回一些在执行单行插入时没有的额外信息,这些信息的含义如下:
● Records:表明插入的记录条数。
● Duplicates:表明插入时被忽略的记录,原因可能是这些记录包含了重复的主键值。
● Warnings:表明有问题的数据值,例如发生数据类型转换。
一个同时插入多行记录的INSERT语句等同于多个单行插入的INSERT语句,但是多行的INSERT语句在处理过程中
效率更高
。因为MySQL执行单条INSERT语句插入多行数据比使用多条INSERT语句快,所以在插入多条记录时最好选择使用单条INSERT语句的方式插入。
小结:
VALUES
也可以写成VALUE
,但是VALUES
是标准写法。
- 字符和日期型数据应包含在单引号中。
方式2:将查询结果插入到表中
INSERT还可以将SELECT语句查询的结果插入到表中,此时不需要把每一条记录的值一个一个输入,只需要使用一条INSERT语句和一条SELECT语句组成的组合语句即可快速地从一个或多个表中向一个表中插入多行。
基本语法格式如下:
1 | INSERT INTO 目标表名 |
- 在
INSERT
语句中加入子查询。 - 不必书写 VALUES 子句。
- 子查询中的值列表应与
INSERT
子句中的列名对应。
举例:
1 | INSERT INTO sales_reps(id, name, salary, commission_pct) |
更新数据
- 使用
UPDATE
语句更新数据。语法如下:
1 | UPDATE table_name |
可以一次更新多条数据。
如果需要回滚数据,需要保证在DML前,进行设置:SET AUTOCOMMIT = FALSE;
使用 WHERE 子句指定需要更新的数据。
1 | UPDATE employees |
- 如果省略
WHERE
子句,则表中的所有数据都将被更新。
1 | UPDATE copy_emp |
删除数据
- 使用
DELETE
语句从表中删除数据
1 | DELETE FROM table_name [WHERE <condition>]; |
table_name指定要执行删除操作的表;[WHERE <condition>]
为可选参数,指定删除条件,如果没有WHERE子句,DELETE语句将删除表中的所有记录。
- 使用 WHERE 子句删除指定的记录。
1 | DELETE FROM departments |
- 如果省略 WHERE 子句,则表中的全部数据将被删除
1 | DELETE FROM copy_emp; |
MySQL中的数据类型
类型 | 类型举例 |
---|---|
整数类型 | TINYINT、SMALLINT、MEDIUMINT、INT(或INTEGER)、BIGINT |
浮点类型 | FLOAT、DOUBLE |
定点数类型 | DECIMAL |
位类型 | BIT |
日期时间类型 | YEAR、TIME、DATE、DATETIME、TIMESTAMP |
文本字符串类型 | CHAR、VARCHAR、TINYTEXT、TEXT、MEDIUMTEXT、LONGTEXT |
枚举类型 | ENUM |
集合类型 | SET |
二进制字符串类型 | BINARY、VARBINARY、TINYBLOB、BLOB、MEDIUMBLOB、LONGBLOB |
JSON类型 | JSON对象、JSON数组 |
空间数据类型 | 单值类型:GEOMETRY、POINT、LINESTRING、POLYGON; 集合类型:MULTIPOINT、MULTILINESTRING、MULTIPOLYGON、GEOMETRYCOLLECTION |
常见数据类型的属性,如下:
MySQL关键字 | 含义 |
---|---|
NULL | 数据列可包含NULL值 |
NOT NULL | 数据列不允许包含NULL值 |
DEFAULT | 默认值 |
PRIMARY KEY | 主键 |
AUTO_INCREMENT | 自动递增,适用于整数类型 |
UNSIGNED | 无符号 |
CHARACTER SET name | 指定一个字符集 |
整数类型
类型介绍
整数类型一共有 5 种,包括 TINYINT、SMALLINT、MEDIUMINT、INT(INTEGER)和 BIGINT。
它们的区别如下表所示:
整数类型 | 字节 | 有符号数取值范围 | 无符号数取值范围 |
---|---|---|---|
TINYINT | 1 | -128~127 | 0~255 |
SMALLINT | 2 | -32768~32767 | 0~65535 |
MEDIUMINT | 3 | -8388608~8388607 | 0~16777215 |
INT、INTEGER | 4 | -2147483648~2147483647 | 0~4294967295 |
BIGINT | 8 | -9223372036854775808~9223372036854775807 | 0~18446744073709551615 |
适用场景
TINYINT
:一般用于枚举数据,比如系统设定取值范围很小且固定的场景。
SMALLINT
:可以用于较小范围的统计数据,比如统计工厂的固定资产库存数量等。
MEDIUMINT
:用于较大整数的计算,比如车站每日的客流量等。
INT
、INTEGER
:取值范围足够大,一般情况下不用考虑超限问题,用得最多。比如商品编号。
BIGINT
:只有当你处理特别巨大的整数时才会用到。比如双十一的交易量、大型门户网站点击量、证券公司衍生产品持仓等。
如何选择?
在评估用哪种整数类型的时候,你需要考虑存储空间
和可靠性
的平衡问题:一方 面,用占用字节数少的整数类型可以节省存储空间;另一方面,要是为了节省存储空间, 使用的整数类型取值范围太小,一旦遇到超出取值范围的情况,就可能引起系统错误
,影响可靠性。
举个例子,商品编号采用的数据类型是INT
。原因就在于,客户门店中流通的商品种类较多,而且,每天都有旧商品下架,新商品上架,这样不断迭代,日积月累。
如果使用SMALLINT
类型,虽然占用字节数比INT
类型的整数少,但是却不能保证数据不会超出范围 65535。相反,使用 INT,就能确保有足够大的取值范围,不用担心数据超出范围影响可靠性的问题。
你要注意的是,在实际工作中,系统故障产生的成本远远超过增加几个字段存储空间所产生的成本。因此,我建议你首先确保数据不会超过取值范围,在这个前提之下,再去考虑如何节省存储空间。
浮点类型
类型介绍
浮点数和定点数类型的特点是可以处理小数
,你可以把整数看成小数的一个特例。因此,浮点数和定点数的使用场景,比整数大多了。 MySQL支持的浮点数类型,分别是FLOAT
、DOUBLE
、REAL
。
FLOAT
表示单精度浮点数;DOUBLE
表示双精度浮点数;REAL
默认就是DOUBLE
。如果你把 SQL 模式设定为启用“REAL_AS_FLOAT
”,那 么,MySQL 就认为 REAL 是 FLOAT。如果要启用“REAL_AS_FLOAT”,可以通过以下 SQL 语句实现:1
SET sql_mode = “REAL_AS_FLOAT”;
问题1:FLOAT 和 DOUBLE 这两种数据类型的区别是啥呢?
FLOAT
占用字节数少,取值范围小;DOUBLE
占用字节数多,取值范围也大。
问题2:为什么浮点数类型的无符号数取值范围,只相当于有符号数取值范围的一半,也就是只相当于有符号数取值范围大于等于零的部分呢?
MySQL 存储浮点数的格式为:符号(S)
、尾数(M)
和 阶码(E)
。因此,无论有没有符号,MySQL 的浮点数都会存储表示符号的部分。因此, 所谓的无符号数取值范围,其实就是有符号数取值范围大于等于零的部分。
精度误差说明
浮点数类型有个缺陷,就是不精准。下面我来重点解释一下为什么 MySQL 的浮点数不够精准。比如,我们设计一个表,有f1这个字段,插入值分别为0.47,0.44,0.19,我们期待的运行结果是:0.47 + 0.44 + 0.19 = 1.1。而使用sum之后查询:
1 | CREATE TABLE test_double2( |
1 | mysql> SELECT SUM(f1) |
1 | mysql> SELECT SUM(f1) = 1.1,1.1 = 1.1 |
查询结果是 1.0999999999999999。看到了吗?虽然误差很小,但确实有误差。 你也可以尝试把数据类型改成 FLOAT,然后运行求和查询,得到的是, 1.0999999940395355。显然,误差更大了。
那么,为什么会存在这样的误差呢?问题还是出在 MySQL 对浮点类型数据的存储方式上。
MySQL 用 4 个字节存储 FLOAT 类型数据,用 8 个字节来存储 DOUBLE 类型数据。无论哪个,都是采用二进制的方式来进行存储的。比如 9.625,用二进制来表达,就是 1001.101,或者表达成 1.001101×2^3。如果尾数不是 0 或 5(比如 9.624),你就无法用一个二进制数来精确表达。进而,就只好在取值允许的范围内进行四舍五入。
在编程中,如果用到浮点数,要特别注意误差问题,因为浮点数是不准确的,所以我们要避免使用“=”来判断两个数是否相等。同时,在一些对精确度要求较高的项目中,千万不要使用浮点数,不然会导致结果错误,甚至是造成不可挽回的损失。那么,MySQL 有没有精准的数据类型呢?当然有,这就是定点数类型:DECIMAL
。
定点数类型
类型介绍
MySQL中的定点数类型只有 DECIMAL 一种类型。
数据类型 字节数 含义 DECIMAL(M,D),DEC,NUMERIC M+2字节 有效范围由M和D决定 使用
DECIMAL(M,D)
的方式表示高精度小数。其中,M被称为精度,D被称为标度。0<=M<=65,0<=D<=30,D<M。例如,定义DECIMAL(5, 2)
的类型,表示该列取值范围是-999.99~999.99。DECIMAL(M,D)的最大取值范围与DOUBLE类型一样,但是有效的数据范围是由M和D决定的。DECIMAL 的存储空间并不是固定的,由精度值M决定,总共占用的存储空间为M+2个字节。也就是说,在一些对精度要求不高的场景下,比起占用同样字节长度的定点数,浮点数表达的数值范围可以更大一些。
定点数在MySQL内部是以
字符串
的形式进行存储,这就决定了它一定是精准的。当DECIMAL类型不指定精度和标度时,其默认为
DECIMAL(10,0)
。当数据的精度超出了定点数类型的精度范围时,则MySQL同样会进行四舍五入处理。浮点数 vs 定点数
- 浮点数相对于定点数的优点是在长度一定的情况下,浮点类型取值范围大,但是不精准,适用于需要取值范围大,又可以容忍微小误差的科学计算场景(比如计算化学、分子建模、流体动力学等)
- 定点数类型取值范围相对小,但是精准,没有误差,适合于对精度要求极高的场景 (比如涉及金额计算的场景)
举例
1
2
3
4
5
6
7
8
9
10
11
12
13CREATE TABLE test_decimal1(
f1 DECIMAL,
f2 DECIMAL(5,2)
);
DESC test_decimal1;
INSERT INTO test_decimal1(f1,f2)
VALUES(123.123,123.456);
#Out of range value for column 'f2' at row 1
INSERT INTO test_decimal1(f2)
VALUES(1234.34);1
2
3
4
5
6
7mysql> SELECT * FROM test_decimal1;
+------+--------+
| f1 | f2 |
+------+--------+
| 123 | 123.46 |
+------+--------+
1 row in set (0.00 sec)举例
我们运行下面的语句,把test_double2表中字段“f1”的数据类型修改为 DECIMAL(5,2):
1
2ALTER TABLE test_double2
MODIFY f1 DECIMAL(5,2);然后,我们再一次运行求和语句:
1
2
3
4
5
6
7
8mysql> SELECT SUM(f1)
-> FROM test_double2;
+---------+
| SUM(f1) |
+---------+
| 1.10 |
+---------+
1 row in set (0.00 sec)1
2
3
4
5
6
7
8mysql> SELECT SUM(f1) = 1.1
-> FROM test_double2;
+---------------+
| SUM(f1) = 1.1 |
+---------------+
| 1 |
+---------------+
1 row in set (0.00 sec)
开发中经验
BIT
BIT类型中存储的是二进制值,类似010110。
二进制字符串类型 | 长度 | 长度范围 | 占用空间 |
---|---|---|---|
BIT(M) | M | 1 <= M <= 64 | 约为(M + 7)/8个字节 |
BIT类型,如果没有指定(M),默认是1位。这个1位,表示只能存1位的二进制值。这里(M)是表示二进制的位数,位数最小值为1,最大值为64。
日期与时间类型
日期与时间是重要的信息,在我们的系统中,几乎所有的数据表都用得到。原因是客户需要知道数据的时间标签,从而进行数据查询、统计和处理。
MySQL有多种表示日期和时间的数据类型,不同的版本可能有所差异,MySQL8.0版本支持的日期和时间类型主要有:YEAR
类型、TIME
类型、DATE
类型、DATETIME
类型和TIMESTAMP
类型。
YEAR
类型通常用来表示年DATE
类型通常用来表示年、月、日TIME
类型通常用来表示时、分、秒DATETIME
类型通常用来表示年、月、日、时、分、秒TIMESTAMP
类型通常用来表示带时区的年、月、日、时、分、秒
类型 | 名称 | 字节 | 日期格式 | 最小值 | 最大值 |
---|---|---|---|---|---|
YEAR | 年 | 1 | YYYY或YY | 1901 | 2155 |
TIME | 时间 | 3 | HH:MM:SS | -838:59:59 | 838:59:59 |
DATE | 日期 | 3 | YYYY-MM-DD | 1000-01-01 | 9999-12-03 |
DATETIME | 日期时间 | 8 | YYYY-MM-DD HH:MM:SS | 1000-01-01 00:00:00 | 9999-12-31 23:59:59 |
TIMESTAMP | 日期时间 | 4 | YYYY-MM-DD HH:MM:SS | 1970-01-01 00:00:00 UTC | 2038-01-19 03:14:07UTC |
可以看到,不同数据类型表示的时间内容不同、取值范围不同,而且占用的字节数也不一样,你要根据实际需要灵活选取。
为什么时间类型 TIME 的取值范围不是 -23:59:59~23:59:59 呢?原因是 MySQL 设计的 TIME 类型,不光表示一天之内的时间,而且可以用来表示一个时间间隔,这个时间间隔可以超过 24 小时。
开发中经验
用得最多的日期时间类型,就是DATETIME
。虽然 MySQL 也支持 YEAR(年)、 TIME(时间)、DATE(日期),以及 TIMESTAMP 类型,但是在实际项目中,尽量用DATETIME
类型。因为这个数据类型包括了完整的日期和时间信息,取值范围也最大,使用起来比较方便。毕竟,如果日期时间信息分散在好几个字段,很不容易记,而且查询的时候,SQL 语句也会更加复杂。
此外,一般存注册时间、商品发布时间等,不建议使用DATETIME存储,而是使用时间戳
,因为DATETIME虽然直观,但不便于计算。
1 | mysql> SELECT UNIX_TIMESTAMP(); |
文本字符串类型
在实际的项目中,我们还经常遇到一种数据,就是字符串数据。
MySQL中,文本字符串总体上分为CHAR
、VARCHAR
、TINYTEXT
、TEXT
、MEDIUMTEXT
、LONGTEXT
、ENUM
、SET
等类型。
文本字符串类型 | 值的长度 | 长度范围 | 占用的存储空间 |
---|---|---|---|
CHAR(M) | M | 0 <= M <= 255 | M个子节 |
VARCHAR(M) | M | 0 <= M <= 65535 | M+l个字节 |
TINYTEXT | L | 0 <= L <= 255 | L+2个字节 |
TEXT | L | 0 <= L <= 65535 | L+2个字节 |
MEDIUMTEXT | L | 0 <= L <= 16777215 | L+3个字节 |
LONGTEXT | L | 0 <= L <= 4294967295 | L+4个字节 |
ENUM | L | 1 <= L <= 65535 | 1或2个字节 |
SET | L | 0 <= L <= 64 | 1,2,3,4或8个字节 |
CHAR与VARCHAR类型
CHAR
和VARCHAR
类型都可以存储比较短的字符串。
字符串(文本)类型 | 特点 | 长度 | 长度范围 | 占用的存储空间 |
---|---|---|---|---|
CHAR(M) | 固定长度 | M | 0 <= M <= 255 | M个字节 |
VARCHAR(M) | 可变长度 | M | 0 <= M <= 65535 | (实际长度 + 1) 个字节 |
CHAR类型:
CHAR(M)
类型一般需要预先定义字符串长度。如果不指定(M),则表示长度默认是1个字符
。- 如果保存时,数据的实际长度比CHAR类型声明的长度小,则会在
右侧填充
空格以达到指定的长度。当MySQL检索CHAR类型的数据时,CHAR类型的字段会去除尾部的空格。 - 定义CHAR类型字段时,声明的字段长度即为CHAR类型字段所占的存储空间的字节数。
VARCHAR类型:
VARCHAR(M)
定义时,必须指定
长度M,否则报错。- MySQL4.0版本以下,varchar(20):指的是20字节,如果存放UTF8汉字时,只能存6个(每个汉字3字节) ;MySQL5.0版本以上,varchar(20):指的是20字符。
- 检索VARCHAR类型的字段数据时,会保留数据尾部的空格。VARCHAR类型的字段所占用的存储空间为字符串实际长度加1个字节。
哪些情况使用 CHAR 或 VARCHAR 更好
类型 | 特点 | 空间上 | 时间上 | 适用场景 |
---|---|---|---|---|
CHAR(M) | 固定长度 | 浪费存储空间 | 效率高 | 存储不大,速度要求高 |
VARCHAR(M) | 可变长度 | 节省存储空间 | 效率低 | 非CHAR的情况 |
情况1:存储很短的信息。比如门牌号码101,201……这样很短的信息应该用char,因为varchar还要占个byte用于存储信息长度,本来打算节约存储的,结果得不偿失。
情况2:固定长度的。比如使用uuid作为主键,那用char应该更合适。因为他固定长度,varchar动态根据长度的特性就消失了,而且还要占个长度信息。
情况3:十分频繁改变的column。因为varchar每次存储都要有额外的计算,得到长度等工作,如果一个非常频繁改变的,那就要有很多的精力用于计算,而这些对于char来说是不需要的。
情况4:具体存储引擎中的情况:
MyISAM
数据存储引擎和数据列:MyISAM数据表,最好使用固定长度(CHAR)的数据列代替可变长度(VARCHAR)的数据列。这样使得整个表静态化,从而使数据检索更快
,用空间换时间。MEMORY
存储引擎和数据列:MEMORY数据表目前都使用固定长度的数据行存储,因此无论使用CHAR或VARCHAR列都没有关系,两者都是作为CHAR类型处理的。InnoDB
存储引擎,建议使用VARCHAR类型。因为对于InnoDB数据表,内部的行存储格式并没有区分固定长度和可变长度列(所有数据行都使用指向数据列值的头指针),而且主要影响性能的因素是数据行使用的存储总量,由于char平均占用的空间多于varchar,所以除了简短并且固定长度的,其他考虑varchar。这样节省空间,对磁盘I/O和数据存储总量比较好。
TEXT类型
在MySQL中,TEXT
用来保存文本类型的字符串,总共包含4种类型,分别为TINYTEXT
、TEXT
、MEDIUMTEXT
和LONGTEXT
类型。
在向TEXT类型的字段保存和查询数据时,系统自动按照实际长度存储,不需要预先定义长度。这一点和 VARCHAR类型相同。
每种TEXT类型保存的数据长度和所占用的存储空间不同,如下:
文本字符串类型 | 特点 | 长度 | 长度范围 | 占用的存储空间 |
---|---|---|---|---|
TINYTEXT | 小文本、可变长度 | L | 0 <= L <= 255 | L + 2 个字节 |
TEXT | 文本、可变长度 | L | 0 <= L <= 65535 | L + 2 个字节 |
MEDIUMTEXT | 中等文本、可变长度 | L | 0 <= L <= 16777215 | L + 3 个字节 |
LONGTEXT | 大文本、可变长度 | L | 0 <= L<= 4294967295(相当于4GB) | L + 4 个字节 |
由于实际存储的长度不确定,MySQL 不允许 TEXT 类型的字段做主键。遇到这种情况,你只能采用 CHAR(M),或者 VARCHAR(M)。
开发中经验:
TEXT文本类型,可以存比较大的文本段,搜索速度稍慢,因此如果不是特别大的内容,建议使用CHAR,VARCHAR来代替。还有TEXT类型不用加默认值,加了也没用。而且text和blob类型的数据删除后容易导致“空洞”,使得文件碎片比较多,所以频繁使用的表不建议包含TEXT类型字段,建议单独分出去,单独用一个表。
ENUM类型
ENUM
类型也叫作枚举类型,ENUM类型的取值范围需要在定义字段时进行指定。设置字段值时,ENUM类型只允许从成员中选取单个值,不能一次选取多个值。
其所需要的存储空间由定义ENUM类型时指定的成员个数决定。
文本字符串类型 | 长度 | 长度范围 | 占用的存储空间 |
---|---|---|---|
ENUM | L | 1 <= L <= 65535 | 1或2个字节 |
当ENUM类型包含1~255个成员时,需要1个字节的存储空间;
当ENUM类型包含256~65535个成员时,需要2个字节的存储空间。
ENUM类型的成员个数的上限为65535个。
举例:
创建表如下:
1 | CREATE TABLE test_enum( |
SET类型
SET
表示一个字符串对象,可以包含0个或多个成员,但成员个数的上限为64
。设置字段值时,可以取取值范围内的 0 个或多个值。
当SET类型包含的成员个数不同时,其所占用的存储空间也是不同的,具体如下:
成员个数范围(L表示实际成员个数) | 占用的存储空间 |
---|---|
1 <= L <= 8 | 1个字节 |
9 <= L <= 16 | 2个字节 |
17 <= L <= 24 | 3个字节 |
25 <= L <= 32 | 4个字节 |
33 <= L <= 64 | 8个字节 |
SET类型在存储数据时成员个数越多,其占用的存储空间越大。注意:SET类型在选取成员时,可以一次选择多个成员,这一点与ENUM类型不同。
举例:
创建表:
1 | CREATE TABLE test_set( |
向表中插入数据:
1 | INSERT INTO test_set (s) VALUES ('A'), ('A,B'); |
二进制字符串类型
MySQL中的二进制字符串类型主要存储一些二进制数据,比如可以存储图片、音频和视频等二进制数据。
MySQL中支持的二进制字符串类型主要包括BINARY
、VARBINARY
、TINYBLOB
、BLOB
、MEDIUMBLOB
和LONGBLOB
类型。
BINARY与VARBINARY类型
BINARY
和VARBINARY
类似于CHAR和VARCHAR,只是它们存储的是二进制字符串。
BINARY (M)
为固定长度的二进制字符串,M表示最多能存储的字节数,取值范围是0~255个字符。如果未指定(M),表示只能存储1个字节
。例如BINARY (8),表示最多能存储8个字节,如果字段值不足(M)个字节,将在右边填充’\0’以补齐指定长度。
VARBINARY (M)
为可变长度的二进制字符串,M表示最多能存储的字节数,总字节数不能超过行的字节长度限制65535,另外还要考虑额外字节开销,VARBINARY类型的数据除了存储数据本身外,还需要1或2个字节来存储数据的字节数。VARBINARY类型必须指定(M),否则报错。
二进制字符串类型 | 特点 | 值的长度 | 占用空间 |
---|---|---|---|
BINARY(M) | 固定长度 | M (0 <= M <= 255) | M个字节 |
VARBINARY(M) | 可变长度 | M(0 <= M <= 65535) | M+1个字节 |
BLOB类型
BLOB
是一个二进制大对象,可以容纳可变数量的数据。
MySQL中的BLOB类型包括TINYBLOB、BLOB、MEDIUMBLOB和LONGBLOB 4种类型,它们可容纳值的最大长度不同。可以存储一个二进制的大对象,比如图片
、音频
和视频
等。
需要注意的是,在实际工作中,往往不会在MySQL数据库中使用BLOB类型存储大对象数据,通常会将图片、音频和视频文件存储到服务器的磁盘上
,并将图片、音频和视频的访问路径存储到MySQL中。
二进制字符串类型 | 值的长度 | 长度范围 | 占用空间 |
---|---|---|---|
TINYBLOB | L | 0 <= L <= 255 | L + 1 个字节 |
BLOB | L | 0 <= L <= 65535(相当于64KB) | L + 2 个字节 |
MEDIUMBLOB | L | 0 <= L <= 16777215 (相当于16MB) | L + 3 个字节 |
LONGBLOB | L | 0 <= L <= 4294967295(相当于4GB) | L + 4 个字节 |
举例:
1 | CREATE TABLE test_blob1( |
TEXT和BLOB的使用注意事项:
在使用text
和blob
字段类型时要注意以下几点,以便更好的发挥数据库的性能。
① BLOB和TEXT值也会引起自己的一些问题,特别是执行了大量的删除或更新操作的时候。删除这种值会在数据表中留下很大的”空洞
“,以后填入这些”空洞”的记录可能长度不同。为了提高性能,建议定期使用 OPTIMIZE TABLE 功能对这类表进行碎片整理
。
② 如果需要对大文本字段进行模糊查询,MySQL 提供了前缀索引。但是仍然要在不必要的时候避免检索大型的BLOB或TEXT值。例如,SELECT * 查询就不是很好的想法,除非你能够确定作为约束条件的WHERE子句只会找到所需要的数据行。否则,你可能毫无目的地在网络上传输大量的值。
③ 把BLOB或TEXT列分离到单独的表中。在某些环境中,如果把这些数据列移动到第二张数据表中,可以让你把原数据表中的数据列转换为固定长度的数据行格式,那么它就是有意义的。这会减少主表中的碎片
,使你得到固定长度数据行的性能优势。它还使你在主数据表上运行 SELECT * 查询的时候不会通过网络传输大量的BLOB或TEXT值。
JSON 类型
JSON(JavaScript Object Notation)
是一种轻量级的数据交换格式。简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。它易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率。JSON 可以将 JavaScript 对象中表示的一组数据转换为字符串,然后就可以在网络或者程序之间轻松地传递这个字符串,并在需要的时候将它还原为各编程语言所支持的数据格式。
在MySQL 5.7中,就已经支持JSON数据类型。在MySQL 8.x版本中,JSON类型提供了可以进行自动验证的JSON文档和优化的存储结构,使得在MySQL中存储和读取JSON类型的数据更加方便和高效。
创建数据表,表中包含一个JSON类型的字段 js 。
1 | CREATE TABLE test_json( |
向表中插入JSON数据。
1 | INSERT INTO test_json (js) |
当需要检索JSON类型的字段中数据的某个具体值时,可以使用“->”和“->>”符号。
1 | mysql> SELECT js -> '$.name' AS NAME,js -> '$.age' AS age ,js -> '$.address.province' AS province, js -> '$.address.city' AS city |
通过“->”和“->>”符号,从JSON字段中正确查询出了指定的JSON数据的值。
数据类型选择建议
在定义数据类型时,如果确定是整数
,就用INT
; 如果是小数
,一定用定点数类型 DECIMAL(M,D)
; 如果是日期与时间
,就用DATETIME
。
这样做的好处是,首先确保你的系统不会因为数据类型定义出错。不过,凡事都是有两面的,可靠性好,并不意味着高效。比如,TEXT 虽然使用方便,但是效率不如 CHAR(M) 和 VARCHAR(M)。
关于字符串的选择,建议参考如下阿里巴巴的《Java开发手册》规范:
阿里巴巴《Java开发手册》之MySQL数据库:
- 任何字段如果为
非负数
,必须是UNSIGNED
- 【
强制
】小数类型为DECIMAL
,禁止使用FLOAT
和DOUBLE
。- 说明:在存储的时候,
FLOAT
和DOUBLE
都存在精度损失的问题,很可能在比较值的时候,得到不正确的结果。如果存储的数据范围超过DECIMAL
的范围,建议将数据拆成整数和小数并分开存储。
- 说明:在存储的时候,
- 【
强制
】如果存储的字符串长度几乎相等,使用CHAR
定长字符串类型。 - 【
强制
】VARCHAR
是可变长字符串,不预先分配存储空间,长度不要超过 5000。如果存储长度大于此值,定义字段类型为TEXT
,独立出来一张表,用主键来对应,避免影响其它字段索引效率。
约束
为什么需要约束
数据完整性(Data Integrity)是指数据的精确性(Accuracy)和可靠性(Reliability)。它是防止数据库中存在不符合语义规定的数据和防止因错误信息的输入输出造成无效操作或错误信息而提出的。
为了保证数据的完整性,SQL规范以约束的方式对表数据进行额外的条件限制。从以下四个方面考虑:
- 实体完整性(Entity Integrity):例如,同一个表中,不能存在两条完全相同无法区分的记录
- 域完整性(Domain Integrity):例如:年龄范围0-120,性别范围“男/女”
- 引用完整性(Referential Integrity):例如:员工所在部门,在部门表中要能找到这个部门
- 用户自定义完整性(User-defined Integrity):例如:用户名唯一、密码不能为空等,本部门经理的工资不得高于本部门职工的平均工资的5倍。
什么是约束
约束是表级的强制规定。
可以在创建表时规定约束(通过 CREATE TABLE 语句),或者在表创建之后通过 ALTER TABLE 语句规定约束。
约束的分类
- 根据约束数据列的限制,约束可分为:
- 单列约束:每个约束只约束一列
- 多列约束:每个约束可约束多列数据
- 根据约束的作用范围,约束可分为:
- 列级约束:只能作用在一个列上,跟在列的定义后面
- 表级约束:可以作用在多个列上,不与列一起,而是单独定义
位置 | 支持的约束类型 | 是否可以起约束名 |
---|---|---|
列级约束: 列的后面 | 语法都支持,但外键没有效果 | 不可以 |
表级约束: 所有列的下面 | 默认和非空不支持,其他支持 | 可以(主键没有效果) |
- 根据约束起的作用,约束可分为:
- NOT NULL 非空约束,规定某个字段不能为空
- UNIQUE 唯一约束,规定某个字段在整个表中是唯一的
- PRIMARY KEY 主键(非空且唯一)约束
- FOREIGN KEY 外键约束
- CHECK 检查约束
- DEFAULT 默认值约束
- 查看某个表已有的约束
1 | #information_schema数据库名(系统库) |
非空约束
限定某个字段/某列的值不允许为空
特点
默认,所有的类型的值都可以是
NULL
,包括INT
、FLOAT
等数据类型非空约束只能出现在表对象的列上,只能某个列单独限定非空,不能组合非空
一个表可以有很多列都分别限定了非空
空字符串
''
不等于NULL
,0
也不等于NULL
添加非空约束
(1)建表时
1 | CREATE TABLE 表名称( |
举例:
1 | CREATE TABLE emp( |
(2)建表后
1 | alter table 表名称 modify 字段名 数据类型 not null; |
举例:
1 | ALTER TABLE emp |
1 | alter table student modify sname varchar(20) not null; |
删除非空约束
1 | alter table 表名称 modify 字段名 数据类型 NULL;#去掉not null,相当于修改某个非注解字段,该字段允许为空 |
举例:
1 | ALTER TABLE emp |
1 | ALTER TABLE emp |
唯一性约束
用来限制某个字段/某列的值不能重复。
特点
- 同一个表可以有多个唯一约束。
- 唯一约束可以是某一个列的值唯一,也可以多个列组合的值唯一。
- 唯一性约束允许列值为空。
- 在创建唯一约束的时候,如果不给唯一约束命名,就默认和列名相同。
- MySQL会给唯一约束的列上默认创建一个唯一索引。
添加唯一约束
(1)建表时
1 | create table 表名称( |
举例:
1 | create table student( |
1 | CREATE TABLE USER( |
(2)建表后指定唯一键约束
1 | #字段列表中如果是一个字段,表示该列的值唯一。如果是两个或更多个字段,那么复合唯一,即多个字段的组合是唯一的 |
1 | #方式2: |
举例:
1 | ALTER TABLE USER |
1 | ALTER TABLE USER |
1 | ALTER TABLE USER |
举例:
1 | create table student( |
1 | alter table student add unique key(tel); |
关于复合唯一约束
1 | create table 表名称( |
删除唯一约束
- 添加唯一性约束的列上也会自动创建唯一索引。
- 删除唯一约束只能通过删除唯一索引的方式删除。
- 删除时需要指定唯一索引名,唯一索引名就和唯一约束名一样。
- 如果创建唯一约束时未指定名称,如果是单列,就默认和列名相同;如果是组合列,那么默认和()中排在第一个的列名相同。也可以自定义唯一性约束名。
1 | SELECT * FROM information_schema.table_constraints WHERE table_name = '表名'; #查看都有哪些约束 |
1 | ALTER TABLE USER |
PRIMARY KEY 约束
用来唯一标识表中的一行记录。
特点
主键约束相当于唯一约束+非空约束的组合,主键约束列不允许重复,也不允许出现空值。
一个表最多只能有一个主键约束,建立主键约束可以在列级别创建,也可以在表级别上创建。
主键约束对应着表中的一列或者多列(复合主键)
如果是多列组合的复合主键约束,那么这些列都不允许为空值,并且组合的值不允许重复。
MySQL的主键名总是PRIMARY,就算自己命名了主键约束名也没用。
当创建主键约束时,系统默认会在所在的列或列组合上建立对应的主键索引(能够根据主键查询的,就根据主键查询,效率更高)。如果删除主键约束了,主键约束对应的索引就自动删除了。
- 需要注意的一点是,不要修改主键字段的值。因为主键是数据记录的唯一标识,如果修改了主键的值,就有可能会破坏数据的完整性。
添加主键约束
1)建表时指定主键约束
1 | create table 表名称( |
举例:
1 | create table temp( |
再举例:
- 列级约束
1 | CREATE TABLE emp4( |
- 表级约束
1 | CREATE TABLE emp5( |
(2)建表后增加主键约束
1 | ALTER TABLE 表名称 ADD PRIMARY KEY(字段列表); #字段列表可以是一个字段,也可以是多个字段,如果是多个字段的话,是复合主键 |
1 | ALTER TABLE student ADD PRIMARY KEY (sid); |
1 | ALTER TABLE emp5 ADD PRIMARY KEY(NAME,pwd); |
删除主键约束
1 | alter table 表名称 drop primary key; |
举例:
1 | ALTER TABLE student DROP PRIMARY KEY; |
1 | ALTER TABLE emp5 DROP PRIMARY KEY; |
自增列:AUTO_INCREMENT
某个字段的值自增
特点和要求
(1)一个表最多只能有一个自增长列
(2)当需要产生唯一标识符或顺序值时,可设置自增长
(3)自增长列约束的列必须是键列(主键列,唯一键列)
(4)自增约束的列的数据类型必须是整数类型
(5)如果自增列指定了 0 和 null,会在当前最大值的基础上自增;如果自增列手动指定了具体值,直接赋值为具体值。
如何指定自增约束
(1)建表时
1 | create table 表名称( |
1 | create table employee( |
1 | mysql> desc employee; |
(2)建表后
1 | alter table 表名称 modify 字段名 数据类型 auto_increment; |
例如:
1 | create table employee( |
1 | alter table employee modify eid int auto_increment; |
1 | mysql> desc employee; |
如何删除自增约束
1 | #alter table 表名称 modify 字段名 数据类型 auto_increment;#给这个字段增加自增约束 |
1 | alter table employee modify eid int; |
1 | mysql> desc employee; |
FOREIGN KEY 约束
限定某个表的某个字段的引用完整性。
比如:员工表的员工所在部门的选择,必须在部门表能找到对应的部分。
主表和从表/父表和子表
主表(父表):被引用的表,被参考的表
从表(子表):引用别人的表,参考别人的表
例如:员工表的员工所在部门这个字段的值要参考部门表:部门表是主表,员工表是从表。
例如:学生表、课程表、选课表:选课表的学生和课程要分别参考学生表和课程表,学生表和课程表是主表,选课表是从表。
特点
(1)从表的外键列,必须引用/参考主表的主键或唯一约束的列
为什么?因为被依赖/被参考的值必须是唯一的
(2)在创建外键约束时,如果不给外键约束命名,默认名不是列名,而是自动产生一个外键名(例如 student_ibfk_1;),也可以指定外键约束名。
(3)创建(CREATE)表时就指定外键约束的话,先创建主表,再创建从表
(4)删表时,先删从表(或先删除外键约束),再删除主表
(5)当主表的记录被从表参照时,主表的记录将不允许删除,如果要删除数据,需要先删除从表中依赖该记录的数据,然后才可以删除主表的数据
(6)在“从表”中指定外键约束,并且一个表可以建立多个外键约束
(7)从表的外键列与主表被参照的列名字可以不相同,但是数据类型必须一样,逻辑意义一致。如果类型不一样,创建子表时,就会出现错误“ERROR 1005 (HY000): Can’t create table’database.tablename’(errno: 150)”。
例如:都是表示部门编号,都是int类型。
(8)当创建外键约束时,系统默认会在所在的列上建立对应的普通索引。但是索引名是外键的约束名。(根据外键查询效率很高)
(9)删除外键约束后,必须手动
删除对应的索引
添加外键约束
(1)建表时
1 | create table 主表名称( |
1 | create table dept( #主表 |
(2)建表后
一般情况下,表与表的关联都是提前设计好了的,因此,会在创建表的时候就把外键约束定义好。不过,如果需要修改表的设计(比如添加新的字段,增加新的关联关系),但没有预先定义外键约束,那么,就要用修改表的方式来补充定义。
格式:
1 | ALTER TABLE 从表名 ADD [CONSTRAINT 约束名] FOREIGN KEY (从表的字段) REFERENCES 主表名(被引用字段) [on update xx][on delete xx]; |
举例:
1 | ALTER TABLE emp1 |
举例:
1 | create table dept( |
1 | alter table emp add foreign key (deptid) references dept(did); |
总结:约束关系是针对双方的
添加了外键约束后,主表的修改和删除数据受约束
添加了外键约束后,从表的添加和修改数据受约束
在从表上建立外键,要求主表必须存在
删除主表时,要求从表从表先删除,或将从表中外键引用该主表的关系先删除
约束等级
Cascade
方式:在父表上update/delete记录时,同步update/delete掉子表的匹配记录Set null
方式:在父表上update/delete记录时,将子表上匹配记录的列设为null,但是要注意子表的外键列不能为not nullNo action
方式:如果子表中有匹配的记录,则不允许对父表对应候选键进行update/delete操作Restrict
方式:同no action, 都是立即检查外键约束Set default
方式(在可视化工具SQLyog中可能显示空白):父表有变更时,子表将外键列设置成一个默认的值,但Innodb不能识别
如果没有指定等级,就相当于Restrict
方式。
对于外键约束,最好是采用: ON UPDATE CASCADE ON DELETE RESTRICT
的方式。
1 | create table emp( |
建和不建外键约束有什么区别?
答:建外键约束,你的操作(创建表、删除表、添加、修改、删除)会受到限制,从语法层面受到限制。例如:在员工表中不可能添加一个员工信息,它的部门的值在部门表中找不到。
不建外键约束,你的操作(创建表、删除表、添加、修改、删除)不受限制,要保证数据的引用完整性
,只能依靠程序员的自觉
,或者是在Java程序中进行限定
。例如:在员工表中,可以添加一个员工的信息,它的部门指定为一个完全不存在的部门。
那么建和不建外键约束和查询有没有关系?
阿里开发规范
【强制】不得使用外键与级联,一切外键概念必须在应用层解决。
说明:(概念解释)学生表中的 student_id 是主键,那么成绩表中的 student_id 则为外键。如果更新学生表中的 student_id,同时触发成绩表中的 student_id 更新,即为级联更新。外键与级联更新适用于单机低并发
,不适合分布式
、高并发集群
;级联更新是强阻塞,存在数据库更新风暴
的风险;外键影响数据库的插入速度
。
CHECK 约束
检查某个字段的值是否符号xx要求,一般指的是值的范围
说明:MySQL 5.7 不支持
MySQL5.7 可以使用check约束,但check约束对数据验证没有任何作用。添加数据时,没有任何错误或警告
但是MySQL 8.0中可以使用check约束了。
DEFAULT约束
给某个字段/某列指定默认值,一旦设置默认值,在插入数据时,如果此字段没有显式赋值,则赋值为默认值。
给字段加默认值
1)建表时
1 | create table 表名称( |
1 | create table employee( |
(2)建表后
1 | alter table 表名称 modify 字段名 数据类型 default 默认值; |
1 | alter table employee modify gender char default '男'; #给gender字段增加默认值约束 |
1 | alter table employee modify tel char(11) default '' not null;#给tel字段增加默认值约束,并保留非空约束 |
如何删除默认值约束
1 | alter table 表名称 modify 字段名 数据类型 ;#删除默认值约束,也不保留非空约束 |
1 | alter table employee modify gender char; #删除gender字段默认值约束,如果有非空约束,也一并删除 |