MySQL
MYSQL 基础篇
快速上手
Quick Start
数据库是有组织的数据容器。表是存储特定类型数据的结构化文件。模式说明库与表的布局与特性信息。列是表中的一个字段,所有表都由一个或多个列组成。行作为表中的一个记录。唯一标识表中每行的这个列或这组列,称为主键。没有主键则无法安全更新或删除表中的特定行。任何列都可以作为主键,但是需满足任意两行都不具有相同的主键值且每行都必须具有一个非 NULL
的主键值的条件。
默认数据库:
数据库 | 描述 |
---|---|
infomation_schema | 信息数据库,包括 MySQL 在维护的其他数据库、表、列访问权限 |
performance_schema | 性能数据库,记录 MySQL Server 运行过程中的一些资源消耗相关信息 |
mysql | 用于存储数据库管理者的用户信息、权限信息以及日志信息 |
sys | 简易版的 performance_schema,将性能数据库的数据汇总成易理解形式 |
SELECT 语句完整结构:
SELECT
:要返回的列或者表达式 -> 必须使用FROM
:检索数据的表 -> 仅在从表中选择数据时使用WHERE
:行级过滤 -> 非必须使用GROUP BY
:分组说明 -> 仅在按组计算聚集时使用HAVING
:组级过滤 -> 非必须使用ORDER BY
:输出排序顺序 -> 非必须使用LIMIT
:要检索的行数 -> 非必须使用
// sql92
SELECT ...(聚合函数) FROM ... WHERE 多表连接条件 AND 不包含聚合函数的过滤条件 GROUP BY ... HAVING 包含聚合函数的过滤条件 ORDER BY ...(ASC/DESC) LIMIT ...
// sql99
SELECT ...(聚合函数) FROM ... (LEFT/RIGHT) JOIN ... ON ... (多表的连接条件) (LEFT/RIGHT) JOIN ... ON ... (多表的连接条件) WHERE 不包含聚合函数的过滤条件 GROUP BY ... HAVING 包含聚合函数的过滤条件 ORDER BY ...(ASC/DESC) LIMIT ...
SQL 底层执行原理:产生一系列虚拟表
先执行 FROM
关键字,多表联查会通过 CROSS JOIN
求笛卡尔积,将得到的虚拟表进行 ON
筛选,此过程在 VT1-1 的基础上得到 VT1-2。若使用到左、右连接或者全连接,那么会在 VT1-2 的基础上增加外部行产生 VT1-3。
通过 VT1 获取原始数据后进行 WHERE
操作,根据结果筛选过滤得到 VT2。
GROUP BY
和 HAVING
对 VT2 进行分组和过滤得到 VT3 和 VT4。
SELECT
和 DISTINCT
提取所需字段并过滤掉重复的行得到 VT5-1 和 VT5-2。
ORDER BY
和 LIMIT
对指定的字段排序并通过分页取出指定行,得到最终的 VT6 与 VT7。
// FROM 首先加载、SELECT 视作索引首位处于第二加载、ORDER BY 与 LIMIT 看作索引末尾最后加载
FROM ... 多表联查会先 CROSS JOIN 求笛卡尔积 => ON 连接条件过滤 => 关注左右连接 => WHERE 过滤数据 => GROUP BY 按要求分组 => HAVING 聚合条件筛选 => SELECT => DISTINCT => ORDER BY => LIMIT
创建并管理数据库
仅通过 CREATE DATABASE
创建数据库时,字符集会使用数据库的默认设置。
显示指明字符集:CREATE DATABASE 数据库名 CHARACTER SET '字符集';
。
# 创建数据库若存在 => 不成功不报错
CREATE DATABASE IF NOT EXISTS 数据库名 CHARACTER SET '字符集';
更改现有数据库字符集:ALTER DATABASE 数据库名 CHARACTER SET 字符集;
。
区分 SHOW DATABASES;
& SELECT DATABASE();
:前者查看所有数据库,后者是通过一个全局函数查看当前正在使用的数据库。
查看指定库下的所有表:SHOW TABLES FROM 数据库名;
。
查看数据库的创建语句:SHOW CREATE DATABASE 数据库名\G
,\G
表示格式化输出信息。
删除数据库时最好能加以限制:DROP DATABASE IF EXISTS 数据库名;
。
建表时必须指定表名、字段名、类型和长度,约束条件和默认值的设定不必须。
CREATE TABLE IF NOT EXISTS myempl(id INT,emp_name VARCHAR(15),hire_date DATE);
表的结构描述:DESC ???;
;表的创建语句:SHOW CREATE TABLE ???;
。
在现有表的基础上创建新表(选择字段别名作为新表的字段名)常应用于表复制。
CREATE TABLE 新表名 AS SELECT FIELD01 FIELD01ALIAS, FIELD02, ... FROM 现有表;
# 创建新表 employees_copy 实现对 employees 表复制 => 包含表数据
CREATE TABLE employees_copy AS SELECT * FROM employees;
# 创建新表 employees_copy 实现对 employees 表复制 => 不包含表数据 => 不可能条件
CREATE TABLE employees_copy AS SELECT * FROM employees WHERE department_id > 10000;
CREATE TABLE employees_copy AS SELECT * FROM employees WHERE 1=2;
修改表 ALTER TABLE ???
后边可以指定具体执行的操作:
- 添加字段:
... ADD 字段名 字段类型 [FIRST|AFTER 字段名];
- 修改字段(通常不修改类型):
... MODIFY FIELD TYPE DEFAULT X;
- 重命名字段:
... CHANGE old_col_name new_col_name TYPE;
- 删除字段:
... DROP COLUMN col_name;
重命名表:
RENAME TABLE old_name TO new_name;
ALTER TABLE old_name RENAME TO new_name;
删除表数据:
DROP ???
:表的结构和数据都会被删除,且释放空间TRUNCATE ???
:仅清空表中的所有数据,表结构会被保留
执行 COMMIT
提交的数据会被永久保存,不可回滚,后续修改需提交事务。
ROLLBACK
可以使得数据回滚到最近一次 COMMIT
之后:
- 执行过
TRUNCATE
不能回滚 - 使用
DELETE
语句删除的数据可以回滚
DDL 的操作一旦执行就不可回滚,指令 SET autocommit = FALSE
对其失效。
DDL 操作会追加执行 COMMIT
。此提交不受 SET autocommit = FALSE
影响。
区分 TRUNCATE
和 DELETE
:
TRUNCATE
在功能上与不带WHERE
的DELETE
语句相同- 虽然
TRUNCATE
速度更快,且使用的系统和事务日志资源较少,但无事务和不触发TRIGGER
有可能会造成事故,不建议在开发环境中使用。
Data manipulation language
DML 是一系列描述增删改操作的 SQL 语句。SELECT
有时也会被包括其中。
INSERT INTO [TABLE_NAME] (字段1, 字段2, ...) SELECT 对应字段1, 对应字段2, ... FROM [TABLE_NAME_other] WHERE ...;
DML statements for an InnoDB table operate in the context of a transaction, so their effects can be committed or rolled back as a single unit. dev.mysql
DML 操作同样默认为执行后不能回滚。若在执行操作前指定 SET autocommit = FALSE
,那么执行的 DML 操作依然可回滚。
查询 & 函数
Multi-table Query Core Concepts
多表查询也称为关联查询,是由多个表一起完成的查询操作。多表查询的意义在于减少网络交互次数。通过单条语句完成需要多个简单 SQL 语句才能完成的目的。
多表查询出现笛卡尔乘积的原因可能是关联条件失效,或省略多个表的连接条件。
# 笛卡尔乘积的错误形式
SELECT employee_id,department_name FROM employees,departments;
SELECT * FROM employees,departments;
SELECT employee_id,department_name FROM employees CROSS JOIN departments;
从 SQL 优化角度来说,建议多表查询时对每个字段指明其所在的表。
# FROM 语句使用的别名也可以在 SELECT 语句里使用 => 起了别名就必须用别名
SELECT emp.employee_id,dept.department_name,emp.department_id FROM employees emp,departments dept WHERE emp.department_id = dept.department_id;
如果有 n 张表要实现多表查询,则至少需要 n-1 个连接条件。
SELECT emp.employee_id,emp.last_name,dept.department_name,emp.department_id,loc.city,loc.location_id FROM employees emp,departments dept,locations loc WHERE emp.department_id = dept.department_id AND dept.location_id = loc.location_id;
当查询请求涉及到多个表,且连接表的条件为相等时,即等值连接查询;由其他的运算符连接就是非等值查询。
SELECT e.last_name,e.salary,j.grade_level FROM employees e, job_grades j WHERE e.salary >= j.lowest_sal AND e.salary <= j.highest_sal;
不同表之间实现连接操作称为非自连接,表自行与自己的连接称为自连接。
# 查询员工id、姓名以及管理者的id、姓名
SELECT emp.employee_id,emp.last_name,mgr.employee_id,mgr.last_name FROM employees emp, employees mgr WHERE emp.manager_id = mgr.employee_id;
Inner Join & Outer Join
The different types of the JOINs in SQL:
(INNER) JOIN
: Returns records that have matching values in both tablesLEFT (OUTER) JOIN
: Returns all records from the left table, and the matched records from the right tableRIGHT (OUTER) JOIN
: Returns all records from the right table, and the matched records from the left tableFULL (OUTER) JOIN
: Returns all records when there is a match in either left or right table
# 右上图 => 右外连接
SELECT employee_id,department_name FROM employees e RIGHT JOIN departments d ON e.department_id = d.department_id;
# 左中图
SELECT employee_id,department_name FROM employees e LEFT JOIN departments d ON e.department_id = d.department_id WHERE d.department_id IS NULL;
# 右中图
SELECT employee_id,department_name FROM employees e RIGHT JOIN departments d ON e.department_id = d.department_id WHERE e.department_id IS NULL;
# 左下图 => 满外连接 => 左上图 UNION ALL 右中图
SELECT employee_id,department_name FROM employees e LEFT OUTER JOIN departments d ON e.department_id = d.department_id UNION ALL SELECT employee_id,department_name FROM employees e RIGHT JOIN departments d ON e.department_id = d.department_id WHERE e.department_id IS NULL;
# 右下图 => 左中图 UNION ALL 右中图
SELECT employee_id,department_name FROM employees e LEFT JOIN departments d ON e.department_id = d.department_id WHERE d.department_id IS NULL UNION ALL SELECT employee_id,department_name FROM employees e RIGHT JOIN departments d ON e.department_id = d.department_id WHERE e.department_id IS NULL;
MySQL 不支持 SQL92 中以 (+)
标记从表位置的外连接写法。
# SQL92 实现左外连接
SELECT employee_id,department_name FROM employees e,departments d WHERE e.`department_id`=d.`department_id`(+);
MySQL 不支持全外连接 FULL OUTER JOIN
写法,需要利用 UNION
实现全外连接操作。UNION
可以将多条 SELECT
语句的结果组成单个结果集。
SELECT column,... FROM table1 UNION [ALL] SELECT column,... FROM table2;
由于不去重,UNION ALL
执行语句时所需的资源较 UNION
更少。当明确知道合并后结果不存在重复的情况时,使用 UNION ALL
可以提高查询效率。
Subquery
A subquery in MySQL is a query, which is nested into another SQL query and embedded with SELECT, INSERT, UPDATE or DELETE statement along with the various operators.
子查询 subquery 是可嵌套在查询中的内部查询,从内向外进行处理。
子查询会在主查询执行前完成,其结果会被主查询使用。子查询使用括号包裹,使用时放在比较条件的右侧可读性更高。
# 查找出较 Abel 更高工资的同事并列出薪资
# 方式一
SELECT salary FROM employees WHERE last_name = 'Abel';
SELECT last_name,salary FROM employees WHERE salary > 11000;
# 方式二 => 自连接
SELECT e2.last_name,e2.salary FROM employees e1,employees e2 WHERE e1.last_name = 'Abel' AND e1.`salary` < e2.`salary`;
# 方式三 => 子查询
SELECT last_name,salary FROM employees WHERE salary > ( SELECT salary FROM employees WHERE last_name = 'Abel' );
按内查询结果所返回的记录条目,可将子查询分为单行子查询、多行子查询。按子查询是否被执行多次,可将子查询分为关联子查询和非关联子查询。
A row subquery is a subquery variant that returns a single row and can thus return more than one column value.
单行子查询是一种子查询的变体,返回单行,但可以包含多个列值。
# 返回公司工资最少的员工的 last_name、job_id 和 salary
SELECT last_name,job_id,salary FROM employees WHERE salary = ( SELECT MIN(salary) FROM employees );
# 查询与141号员工的manager_id和department_id相同的其他员工的employee_id,manager_id,department_id
SELECT employee_id, manager_id, department_id FROM employees WHERE manager_id = ( SELECT manager_id FROM employees WHERE employee_id = 141 ) AND department_id = ( SELECT department_id FROM employees WHERE employee_id = 141 ) AND employee_id <> 141;
# 查询与141号或174号员工的manager_id和department_id相同的其他员工的employee_id,manager_id,department_id
SELECT employee_id, manager_id, department_id FROM employees WHERE manager_id IN (SELECT manager_id FROM employees WHERE employee_id IN (174,141)) AND department_id IN (SELECT department_id FROM employees WHERE employee_id IN (174,141)) AND employee_id NOT IN(174,141);
# HAVING 中的子查询
# 查询最低工资大于50号部门最低工资的部门id和其最低工资
SELECT department_id, MIN(salary) AS dept_lowest_salary FROM employees WHERE department_id IS NOT NULL GROUP BY department_id HAVING dept_lowest_salary > ( SELECT MIN(salary) FROM employees WHERE department_id = 50);
# CASE中的子查询
# 显示员工的employee_id,last_name和location.其中,若员工department_id与location_id为1800的department_id相同,则location为'Canada',其余则为'USA'
SELECT employee_id, last_name, ( CASE department_id WHEN ( SELECT department_id FROM departments WHERE location_id = 1800 ) THEN 'Canada' ELSE 'USA' END ) "location" FROM employees;
# 子查询中的空值问题 => 内查询空值不报错,仅无结果
SELECT last_name, job_id FROM employees WHERE job_id = ( SELECT job_id FROM employees WHERE last_name = 'Haas' );
# 非法使用子查询 => ERROR 1242 (21000): Subquery returns more than 1 row
SELECT employee_id, last_name FROM employees WHERE salary = ( SELECT MIN( salary ) FROM employees GROUP BY department_id );
# 非法使用子查询 => 正解
# 查出哪些员工是等于下面各部门最低工资的人
SELECT employee_id, last_name FROM employees WHERE salary IN ( SELECT MIN( salary ) FROM employees GROUP BY department_id );
多行子查询可以返回多行的查询结果。因为需要返回多行,所以必须经过一系列的比较运算符(IN、ALL、ANY、SOME
)处理。
# 查询平均工资最低的部门 id
SELECT department_id, AVG(salary) FROM employees GROUP BY department_id HAVING AVG(salary) = ( SELECT MIN(avg_salary) FROM ( SELECT AVG(salary) avg_salary FROM employees GROUP BY department_id ) AS avg_salary_table);
SELECT department_id FROM employees GROUP BY department_id HAVING AVG(salary) <= ALL ( SELECT AVG(salary) FROM employees GROUP BY department_id );
# 返回其它 job_id 中比 job_id 为 'IT_PROG' 部门任一工资低的员工的员工号、姓名、job_id 以及 salary
SELECT employee_id,last_name,job_id,salary FROM employees WHERE job_id <> 'IT_PROG' AND salary < ANY (SELECT salary FROM employees WHERE job_id = 'IT_PROG') AND job_id <> 'IT_PROG';
// 返回其它 job_id 中比 job_id 为 'IT_PROG' 部门所有工资都低的员工的员工号、姓名、job_id 以及 salary
SELECT employee_id,last_name,job_id,salary FROM employees WHERE job_id <> 'IT_PROG' AND salary < ALL (SELECT salary FROM employees WHERE job_id = 'IT_PROG') AND job_id <> 'IT_PROG';
子查询的执行依赖于外部查询,通常是因为子查询的表用到了外部的表,并存在条件关联。这种情况下,每执行一次外部查询,子查询都需要重新计算,这种形式的子查询称为关联子查询。
/*
相关子查询 => 子查询中使用主查询中的列
在SELECT中除了GROUP BY 和 LIMIT之外,其他位置都可以声明子查询
*/
// 查询员工中工资大于公司平均工资的员工的 last_name,salary 和其 department_id
SELECT last_name,salary,department_id FROM employees WHERE salary > ( SELECT AVG(salary) FROM employees);
// 查询员工中工资大于本部门平均工资的员工的 last_name,salary 和其 department_id
// 方式一 => 相关查询
SELECT last_name,salary,department_id FROM employees e1 WHERE salary > ( SELECT AVG(salary) FROM employees e2 WHERE department_id = e1.department_id);
// 方式二 => FROM子查询
SELECT e.last_name,e.salary,e.department_id FROM employees e,(SELECT department_id,AVG(salary) avg_sal FROM employees GROUP BY department_id) t_debt_avg_sal WHERE e.department_id = t_debt_avg_sal.department_id AND e.salary > t_debt_avg_sal.avg_sal;
// 查询员工的id,salary,按照department_name 排序
// 方式一 => 相关子查询
SELECT employee_id, salary FROM employees emp ORDER BY ( SELECT department_name FROM departments dept WHERE emp.department_id = dept.department_id ) ASC;
// 方式二 => 表连接
SELECT employee_id, salary, department_name FROM employees emp LEFT JOIN departments dept ON emp.department_id = dept.department_id ORDER BY department_name ASC;
// 若employees表中employee_id与job_history表中employee_id相同的数目不小于2则输出这些相同id的员工的employee_id,last_name和其job_id
SELECT e.employee_id,last_name,e.job_id FROM employees e WHERE 2 <= (SELECT COUNT(*) FROM job_history j WHERE e.employee_id = j.employee_id);
关联子查询通常会搭配 EXISTS
操作符:
- 子查询中存在满足条件的行:返回
TRUE
,停止子查询,返回符合的记录 - 子查询中不存在满足条件的行:返回
FALSE
,继续查找
NOTEXISTS
关键字表示如果不存在某种条件,则返回 TRUE
,反之 FALSE
。
// 查询公司管理者的 employee_id, last_name, job_id, department_id 信息
// 方式一 => 自连接 => 将同一张表看做两张表进行等值连接
SELECT DISTINCT mgr.employee_id, mgr.last_name, mgr.job_id, mgr.department_id FROM employees emp JOIN employees mgr ON emp.manager_id = mgr.employee_id;
// 方式二 => 子查询的方式 => 先将所有的manager_id查出
SELECT employee_id, last_name, job_id, department_id FROM employees WHERE employee_id IN ( SELECT DISTINCT manager_id FROM employees );
// 方式三 => EXISTS
SELECT employee_id, last_name, job_id, department_id FROM employees e1 WHERE EXISTS ( SELECT 1 FROM employees e2 WHERE e1.employee_id = e2.manager_id);
# 查询departments表中不存在于employees表中的部门的department_id和department_name (employees表中部门id为NULL的情况)
# 方式一 => 右外连接
SELECT dept.department_id, dept.department_name, emp.department_id FROM departments dept LEFT JOIN employees emp USING (department_id) WHERE emp.department_id IS NULL;
# 方式二 => NOT EXISTS
SELECT department_id, department_name FROM departments d WHERE NOT EXISTS (SELECT * FROM employees emp WHERE emp.department_id = d.department_id);
在多数的数据库中,自连接的处理速度要比子查询快得多:子查询是先对未知表进行查询,再使用条件来判断;自连接是对已知的数据表进行条件判断。
Built-in Functions
SQL 在不同 DBMS 间的差异性很大,远大于 SQL 不同版本之间的差异。
拼接符:多数 DBMS 使用 ||
或 +
,MySQL 字符串拼接函数为 concat()
。
单行函数是指返回单行结果的函数。聚合函数是对一组数据进行汇总的函数。输入一组数据的集合,输出单个值。聚合函数不能嵌套调用。
常见聚合函数:
AVG
、SUM
、MAX/MIN
、COUNT
...GROUP BY
:通常与聚合函数一起使用,对结果集进行分组HAVING
:根据指定的条件过滤分组
区分 count(*)
、count(1)
和 count(列名)
的使用:
- 三者都是获取指定字段的出现个数
count(col_name)
检索字段时,会忽略NULL
SELECT
指定的非聚合函数字段必须在 GROUP BY
中出现。
GROUP BY
中出现的字段不一定要在 SELECT
中有声明。
// 查询各个department_id,job_id的平均工资
SELECT department_id,job_id,AVG(salary) FROM employees GROUP BY department_id,job_id;
SELECT job_id,department_id,AVG(salary) FROM employees GROUP BY job_id,department_id;
// 错误形式 job_id 未出现在 GROUP BY 中 => 一个部门不一定只有一个工种
SELECT department_id,job_id,AVG(salary) FROM employees GROUP BY department_id;
ROLLUP
是 GROUP BY
子句的扩展,该选项将在结果集中添加一个额外的行以显示总计。
HAVING
子句通常与 GROUP BY
一起使用,若省略 GROUP BY
,则 HAVING
子句的行为与 WHERE
子句类似。
HAVING
或者 WHERE
的选择:过滤条件中有聚合函数时须声明在 HAVING
内。
查询语法结构中,WHERE
在 GROUP BY
之前,所以无法对分组结果进行筛选。
此外,WHERE
排除的记录不再包括在分组中。
// 查询各部门最高工资较10000高的部门
// 错误写法 => ERROR 1111 (HY000): Invalid use of group function
SELECT department_id,MAX(salary) FROM employees WHERE MAX(salary) > 10000 GROUP BY department_id;
// 正确写法
SELECT department_id,MAX(salary) FROM employees GROUP BY department_id HAVING MAX(salary) > 10000;
// 查询部门 id 为 10-40 四个部门中最高工资高于 10000 的部门信息
// 方式一 => 推荐 => 执行效率高
SELECT department_id, MAX(salary) FROM employees WHERE department_id IN (10,20,30,40) GROUP BY department_id HAVING MAX(salary) > 10000;
// 方式二
SELECT department_id, MAX(salary) FROM employees GROUP BY department_id HAVING MAX(salary) > 10000 AND department_id IN (10,20,30,40);
关联表数据需要连接时,WHERE
是先筛选后连接,HAVING
是先连接后筛选:
在关联查询中,前者比后者更高效。因为前者可以先筛选,用一个筛选后的较小数据集和关联表进行连接,占用的资源会比较少;后者需要先将结果集准备好,也就是用未被筛选的数据集进行关联,然后再对这个数据集进行筛选,这样所占用的资源就会比较多,执行效率也就较低。
数据类型
整数类型
超出设置的整数类型范围:Out of range value for column '?' at ...
。
整数类型 | 字节 | 有符号数取值范围 | 无符号数取值范围 (UNSIGNED) |
---|---|---|---|
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 |
MySQL 8.0.17:整数数据类型不推荐使用显示宽度属性:
整型数据类型可以在定义表结构时指定所需显示宽度,如果不指定,则系统会为每一种类型指定默认的宽度值。
所有整数类型都有一个可选的 UNSIGNED
无符号属性:
UNSIGNED
是在 MySQL 中用來設定數值只能是正的屬性,比如tinyint
原本的範圍是 -128~128,加了UNSIGNED
的屬性後,範圍就會變成從 0~255 這樣,對於一些欄位如果確定不會有負數,可以設定這個屬性增加資料長度。
mysql> CREATE TABLE test_int3(f1 INT UNSIGNED);
mysql> desc test_int3;
mysql> INSERT INTO test_int3 VALUES(4294967295);
mysql> INSERT INTO test_int3 VALUES(4294967296);
mysql> SELECT * FROm test_int3;
+------------+
| f1 |
+------------+
| 4294967295 |
+------------+
TINYINT
:一般用于枚举数据(范围很小且固定)SMALLINT
:用于较小范围的统计数据(统计固定资产库存数量)MEDIUMINT
:用于较大整数的计算(机场每日的客流量)INT、INTEGER
:范围足够,一般情况不用考虑超限问题(商品编号)BIGINT
:处理巨大的整数(网站点击量、双十一的交易量、产品持仓)
浮点类型
浮点数类型的无符号数取值范围,只相当于有符号数取值范围的一半:不论有无符号,浮点数都会存储表示符号的部分。因此无符号数的取值范围,其实就是有符号数取值范围大于等于零的部分。
浮点类型单精度值使用 4 个字节,双精度值使用 8 个字节。
自 MySQL 8.0.17 开始,浮点类型(M,D)
的用法不再推荐使用:
FLOAT(M,D)
or DOUBLE(M,D)
:M
= 整数位 + 小数,D
= 小数位。D
<= M
<= 255,0 <= D
<= 30。FLOAT(5,2)
可以显示 -999.99-999.99。
定点数类型
定点数在数据库内部以字符串的形式进行存储。
定点数 DECIMAL(M,D)
中 M
表示精度,D
表示标度。
默认 DECIMAL(10,0)
,当精度超出时会进行四舍五入处理。
DECIMAL
的存储空间不固定,由 M
决定,总占用的存储空间是 M+2
个字节。
浮点数与定点数的区别:
- 长度一定时,浮点类型的可取值范围更大,但是不精准
- 定点数类型的取值范围虽然相对较小,但是精准
位类型
BIT
类型中存储二进制值。没有指定二进制的位数 M
时,默认是 1 位。
位数最小值为 1,最大值为 64。
mysql> CREATE TABLE test_bit1(f1 BIT, f2 BIT(5), f3 BIT(64));
mysql> INSERT INTO test_bit1(f1,f2) VALUES (1,23);
mysql> SELECT * FROM test_bit1;
+------------+------------+------------+
| f1 | f2 | f3 |
+------------+------------+------------+
| 0x01 | 0x17 | NULL |
+------------+------------+------------+
mysql> INSERT INTO test_bit1 (f1) VALUES (2);
ERROR 1406 (22001): Data too long for column 'f1' at row 1
日期时间类型
类型 | 名称 | 字节 | 日期格式 | 最小值 | 最大值 |
---|---|---|---|---|---|
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 |
YEAR
类型表示年份,存储空间 1 字节,是所有日期时间类型中占空间最小的。
4 位字符串或数字表示的 YEAR
类型,最小值为 1901,最大值为 2155。
2 位字符串格式表示的 YEAR
类型,最小值为 00,最大值为 99:
- 01-69 表示 2001 到 2069
- 70-99 表示 1970 到 1999
- 取值整数的 0 或 00 表示 0000 年
- 取值是日期/字符串的 '0' 表示 2000 年
YYYY-MM-DD
的 DATE
类型表示日期,无时间部分。需要 3 个字节的存储空间。
在向 DATE
类型的字段插入数据时,需要满足一定的格式条件:
YYYY-MM-DD/YYYYMMDD
最小取值 1000-01-01,最大取值 9999-12-03YY-MM-DD/YYMMDD
转化方式同两位的YEAR
CURRENT_DATE()
或NOW()
函数会插入当前系统的日期
TIME
类型表示不包含日期部分的时间,也是需要 3 个字节的存储空间。
在向 TIME
类型的字段插入数据时,需要满足一定的格式条件:
- 带有冒号的字符串:
D
表示天,会被转化为小时 - 不带有冒号的字符串或者数字:转化为
HH:MM:SS
进行存储
DATETIME
类型的格式为 DATE
和 TIME
结合,即 YYYY-MM-DD HH:MM:SS
。
DATETIME
类型需要 8 个字节的存储空间,在所有的日期时间类型中是最多的。
在向 DATETIME
类型的字段插入数据时,需要满足一定的格式条件:
YYYY-MM-DD HH:MM:SS
或YYYYMMDDHHMMSS
格式的字符串YYYYMMDDHHMMSS
格式的数字YY-MM-DD HH:MM:SS
或YYMMDDHHMMSS
字符串:年符合YEAR
规则CURRENT_TIMESTAMP()
、NOW()
、SYSDATE()
函数
TIMESTAMP
显示格式与 DATETIME
相同,但只需要 4 个字节的存储空间。
TIMESTAMP
has a range of '1970-01-01 00:00:01' UTC to '2038-01-19 03:14:07' UTC. UTC Coordinated Universal Time 世界协调时间,也称作世界标准时间。
TIMESTAMP
存储的值会进行时区转换,即「当前时间」和 UTC 时间互相转换。
TIMESTAMP
和 DATETIME
的区别:
TIMESTAMP
存储空间较小,表示的时间范围也较小;TIMESTAMP
底层选择的是毫秒值存储,表示距 '1970-1-1 0:0:0' 的时间- 在比较大小或日期计算时,
TIMESTAMP
更方便、更快; TIMESTAMP
和时区有关,会根据用户的时区不同,显示不同的结果DATETIME
只能反映出插入的时间,与时区无关
mysql> CREATE TABLE temp_time(d1 DATETIME,d2 TIMESTAMP);
mysql> INSERT INTO temp_time VALUES('2021-4-22 16:18:52','2021-4-22 16:18:52');
mysql> INSERT INTO temp_time VALUES(NOW(),NOW());
mysql> SELECT * FROM temp_time;
# 修改当前的时区
SET time_zone = '+9:00';
mysql> SELECT * FROM temp_time;
开发中用得最多的日期时间类型,就是 DATETIME
,因为这个数据类型包括了完整的日期和时间信息,取值范围也最大,使用起来比较方便。
但是对于注册时间、商品发布时间等,不建议使用 DATETIME
存储,而是使用时间戳,因为 DATETIME
不便于计算。
mysql> SELECT UNIX_TIMESTAMP();
文本字符串类型
CHAR(M)
类型在不指定 M
时,默认是 1 个字符长度。
CHAR
类型字段在定义时所声明的字段长度即为该字段占的存储空间字节数。
保存数据的实际长度比声明小:
- 存储数据时,右侧填充空格以达到指定的长度
- 检索数据时,其字段会去除尾部空格
VARCHAR(M)
在定义时必须指定长度 M
。
VARCHAR
在不同 MySQL 版本:
- 低于 4.0:
varchar(20)
指 20 个字节。只能存 6 个 UTF8 汉字 - 高于 5.0:
varchar(20)
指 20 个字符。
在检索 VARCHAR
类型的字段数据时,会保留数据尾部的空格。VARCHAR
类型字段所占用的存储空间为字符串实际长度加 1 个字节。
CHAR
和 VARCHAR
的选择:
CHAR
适合存储较短的固定长度数据:门派号码、uuidVARCHAR
具有动态长度的特性,适合频繁改动的信息- 根据存储引擎:
- MyISAM 最好使用固定长度的数据列代替可变长度的数据列
- MEMORY 目前都使用固定长度的数据行存储
- InnoDB 建议使用
varchar
类型。
对于 InnoDB 数据表,其内部的存储格式并没有区分固定长度和可变长度列,所有的数据行都使用头指针指向数据列,主要影响其性能是数据行使用的存储总量。
TEXT
用于保存文本类型的字符串,不需要预先定义长度,不允许做主键。
文本字符串类型 | 特点 | 长度 | 长度范围 | 占用的存储空间 | 最大值 |
---|---|---|---|---|---|
TINYTEXT | 小文本、可变长度 | L | 0 <= L <= 255 | L + 2 个字节 | 2155 |
TEXT | 文本、可变长度 | L | 0 <= L <= 65535 | L + 2 个字节 | 838:59:59 |
MEDIUMTEXT | 中等文本、可变长度 | L | 0 <= L <= 16777215 | L + 3 个字节 | 9999-12-03 |
LONGTEXT | 大文本、可变长度 | L | 0 <= L<= 4294967295(相当于4GB) | L + 4 个字节 | 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 |
TEXT
文本类型适合存储较大的文本段,不需要设置默认值。
TEXT
和 BLOB
类型的数据在删除后容易导致空洞(文件碎片多)。
对于频繁使用的表不建议包含 TEXT
类型字段,建议分表存储。
枚举类型 ENUM
的取值范围需要在定义字段时指定。
ENUM
类型只允许从已有的成员中选取单个值,且一次不能选取多个值。
其存储空间由定义 ENUM
类型时所指定的成员个数决定。
文本字符串类型 | 长度 | 长度范围 | 占用的存储空间 |
---|---|---|---|
ENUM | L | 1 <= L <= 65535 | 1或2个字节 |
ENUM
所需要的字节存储空间:
- 1~255 个成员需要 1 个字节的存储空间
- 256~65535 个成员需要 2 个字节的存储空间
- 上限最多为 65535 个字节的存储空间
SET
表示字符串对象,可以包含 0 个或多个成员,上限为 64。
SET
类型所占的存储空间取决于包含成员的个数。与 ENUM
不同,SET
在选取成员时,可以同时选择多个成员。对 SET
插入重复的成员时,重复的成员会被自动删除。
二进制字符串类型
BINARY
和 VARBINARY
类似于 CHAR
和 VARCHAR
,用于存储二进制字符串。
BINARY(M)
可以设置固定长度的二进制字符串,M
表示最多能存储的字节数。
可变长度的二进制字符串 VARBINARY(M)
中,M
必须设置(存储上限)。
二进制字符串类型 | 特点 | 值的长度 | 占用空间(字节) |
---|---|---|---|
BINARY(M) | 固定长度 | M (0 <= M <= 255) | M个字节 |
VARBINARY(M) | 可变长度 | M(0 <= M <= 65535) | M+1个字节 |
BLOB
类型适合存储一个二进制的大对象,常用于图片、音频和视频等。
实际开发中往往不会在数据库里直接存储较大的数据,而是将这些数据存储在服务器的磁盘目录,然后将这些数据的访问路径存储到数据库中。
二进制字符串类型 | 值的长度 | 长度范围 | 占用空间 |
---|---|---|---|
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 个字节 |
对 TEXT
和 BLOB
执行删除或更新操作后,可能会在表中留下空洞。建议定期使用 OPTIMIZE TABLE
命令进行碎片整理。
在开发中最好将这类数据分表管理,减少主表中的碎片,保持住固定长度数据行的性能优势,避免大量的值传输。
阿里巴巴《Java开发手册》之 MySQL 数据库:
- 任何字段如果为非负数,必须是
UNSIGNED
- 【强制】小数类型为
DECIMAL
,禁止使用FLOAT
和DOUBLE
。
- 说明:在存储的时候,
FLOAT
和DOUBLE
都存在精度损失的问题,很可能在比较值的时候,得到不正确的结果。如果存储的数据范围超过DECIMAL
的范围,建议将数据拆成整数和小数并分开存储。- 【强制】如果存储的字符串长度几乎相等,使用
CHAR
定长字符串类型。- 【强制】
VARCHAR
是可变长字符串,不预先分配存储空间,长度不要超过 5000。如果存储长度大于此值,定义字段类型为TEXT
,独立出来一张表,用主键来对应,避免影响其它字段索引效率。
约束
约束即对表中字段的限制,目的是保证数据的完整性。完整性又可以分为实体完整性、域完整性、引用完整性以及自定义完整性。
information_schema 系统库;table_constraints 专门存储各个表的约束的表
# 查看某个表已有的约束
SELECT * FROM information_schema.table_constraints WHERE table_name = '表名称';
NOT NULL Constraint
默认情况下,列可以包含 NULL
值。
空字符串 ' '
不等于 NULL
,0 也不等于 NULL
。
非空约束强制列不接受 NULL
值。
# 建表时添加非空约束
CREATE TABLE 表名称(...,字段名 数据类型 NOT NULL,...);
# 建表后添加非空约束
ALTER TABLE 表名称 MODIFY 字段名 数据类型 NOT NULL;
# 删除非空约束
ALTER TABLE 表名 MODIFY 字段名 数据类型 NULL;
ALTER TABLE 表名 MODIFY 字段名 数据类型;
UNIQUE Constraint
唯一约束可以确保列中的所有值都是不同的。唯一性约束允许列值为 NULL
。
每个表可以有多个 UNIQUE 约束,但每个表只能有一个 PRIMARY KEY 约束。
当创建唯一约束时未给一约束命名,那么默认和列名相同。
# 建表时添加唯一性约束
CREATE TABLE test2(id INT UNIQUE,last_name VARCHAR(15),email VARCHAR(25),salary DECIMAL(10,2),CONSTRAINT uni_test2_email UNIQUE(email));
DESC test2;
+-----------+---------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-----------+---------------+------+-----+---------+-------+
| id | int | YES | UNI | NULL | |
| last_name | varchar(15) | YES | | NULL | |
| email | varchar(25) | YES | UNI | NULL | |
| salary | decimal(10,2) | YES | | NULL | |
+-----------+---------------+------+-----+---------+-------+
SELECT * FROM information_schema.table_constraints WHERE table_name = 'test2';
+--------------------+-------------------+-----------------+--------------+------------+-----------------+----------+
| CONSTRAINT_CATALOG | CONSTRAINT_SCHEMA | CONSTRAINT_NAME | TABLE_SCHEMA | TABLE_NAME | CONSTRAINT_TYPE | ENFORCED |
+--------------------+-------------------+-----------------+--------------+------------+-----------------+----------+
| def | dbtest13 | id | dbtest13 | test2 | UNIQUE | YES |
| def | dbtest13 | uni_test2_email | dbtest13 | test2 | UNIQUE | YES |
+--------------------+-------------------+-----------------+--------------+------------+-----------------+----------+
# 可以向 unique 标识的字段添加 null 值
INSERT INTO test2 (id,last_name,email,salary) VALUES (2,'gz',NULL,99998);
INSERT INTO test2 (id,last_name,email,salary) VALUES (3,'hz',NULL,99997);
# 创建表后添加约束
ALTER TABLE 表名 ADD UNIQUE key(字段列表);
ALTER TABLE 表名 MODIFY 字段名 字段类型 UNIQUE;
# 若已存在列中数据相等的情况 => 需要修改后才可设置
mysql> ALTER TABLE test2 ADD CONSTRAINT uni_test2_sal UNIQUE (salary);
mysql> ALTER TABLE test2 MODIFY last_name VARCHAR(15) UNIQUE;
# 复合的唯一性约束 + 表级约束
mysql> CREATE TABLE USER(id INT,`name` VARCHAR(15), `password` VARCHAR(25), CONSTRAINT uni_user_name_pwd UNIQUE (`name`,`password`));
mysql> INSERT INTO USER VALUES (1, 'zs', 'abc');
# 复合约束存在一个字段有区别即可
mysql> INSERT INTO USER VALUES (1, 'zscopy', 'abc');
添加唯一约束的列上会自动创建唯一索引。删除唯一约束只能通过删除唯一索引的方式。删除时需要指定唯一索引名(唯一索引名和唯一约束名相同)。
创建唯一约束时未指定名称:
- 单列默认和列名相同
- 组合列默认和括号中的首个列名相同
- 可以自定义唯一约束名
mysql> ALTER TABLE test2 DROP INDEX last_name;
mysql> ALTER TABLE test2 DROP INDEX uni_test2_sal;
mysql> SELECT * FROM information_schema.table_constraints WHERE table_name = 'test2';
mysql> DESC test2;
PRIMARY KEY Constraint
Entity integrity is concerned with ensuring that each row of a table has a unique and non-null primary key value; this is the same as saying that each row in a table represents a single instance of the entity type modelled by the table. wiki
主键约束用来唯一标识表中的每条记录。主键约束相当于唯一约束与非空约束的组合,主键约束列不允许重复,也不允许出现空值。
一张表最多只能有一个主键约束,主键约束可以在列级创建,也可以在表级创建。
主键约束对应着表中的一列或者多列(复合主键)。如果是多列组合的复合主键约束,那么这些列都不允许为空值,并且组合的值不允许重复。
自行命名的主键约束名无效。当创建主键约束时,系统会默认在指定列或列组合上建立对应的主键索引。
主键约束删除时,主键约束所对应的索引也会自动删除。注意:修改主键字段的值有可能会破坏数据的完整性。
# 创建表时添加约束
# 列级约束
mysql> CREATE TABLE test3 (id INT PRIMARY KEY,last_name VARCHAR(15),salary DECIMAL(10,2),email VARCHAR(25));
# 表级约束 => 没有必要起名
mysql> CREATE TABLE test4 (id INT,last_name VARCHAR(15),salary DECIMAL(10,2),email VARCHAR(25),CONSTRAINT pk_test4_id PRIMARY KEY(id));
mysql> SELECT * FROM information_schema.table_constraints WHERE table_name = 'test4';
mysql> INSERT INTO test4 VALUES(1,'zs',99999,'zs@mail.com');
mysql> INSERT INTO test4 VALUES(1,'zscopy',99999,'zscopy@mail.com');
ERROR 1062 (23000): Duplicate entry '1' for key 'test4.PRIMARY'
mysql> INSERT INTO test4 VALUES(NULL,'zscopy',99999,'zscopy@mail.com');
ERROR 1048 (23000): Column 'id' cannot be null
# 复合主键约束
mysql> CREATE TABLE test5(id INT,`name` VARCHAR(15),`password` VARCHAR(25),PRIMARY KEY (`name`,`password`));
mysql> INSERT INTO test5 VALUES (1,'zs','abc');
mysql> INSERT INTO test5 VALUES (1,'zscopy','abc');
# 多列组合的复合主键约束都不可以为NULL
mysql> INSERT INTO test5 VALUES (1,NULL,'abc');
ERROR 1048 (23000): Column 'name' cannot be null
/* 创建表后添加约束 */
mysql> CREATE TABLE test6(id INT,`name` VARCHAR(15),`password` VARCHAR(25));
mysql> ALTER table test6 ADD PRIMARY KEY (id);
mysql> DESC test6;
+----------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+----------+-------------+------+-----+---------+-------+
| id | int | NO | PRI | NULL | |
| name | varchar(15) | YES | | NULL | |
| password | varchar(25) | YES | | NULL | |
+----------+-------------+------+-----+---------+-------+
# 删除主键约束 => 实际开发根本不会去做
ALTER TABLE 表名称 DROP PRIMARY KEY;
AUTO_INCREMENT
自增特性可以让某个字段的后续插入值自增。
若对主键设置自增,后续写 SQL 时就不需要再标记出主键列了。
一张表最多只能有一个自增列。当需要唯一标识或顺序时,可以设置自增。
自增约束的列必须是键:主键或者唯一键。
自增约束的列数据类型必须是整数类型:
- 若自增列指定了 0 和 null:在当前最大值的基础上自增
- 若自增列指定了具体值:直接赋值为具体值
# 创建表时自增
mysql> CREATE TABLE test7 (id INT PRIMARY KEY AUTO_INCREMENT, last_name VARCHAR(15));
mysql> INSERT INTO test7(last_name) VALUES ('zs');
mysql> SELECT * FROM test7;
+----+-----------+
| id | last_name |
+----+-----------+
| 1 | zs |
+----+-----------+
# 创建表后自增
alter table 表名称 modify 字段名 数据类型 auto_increment;
mysql> CREATE TABLE test8 (id INT PRIMARY KEY, last_name VARCHAR(15));
mysql> ALTER TABLE TEST8 MODIFY ID INT AUTO_INCREMENT;
# 删除自增
alter table 表名称 modify 字段名 数据类型;
mysql> ALTER TABLE test8 MODIFY id INT;
MySQL 8.0 新特性:自增变量的持久化
MySQL 8.0 之前,InnoDB 不能恢复重启前的自增列。
# MySQL 5.7
mysql> CREATE TABLE test9(id INT PRIMARY KEY AUTO_INCREMENT);
mysql> INSERT INTO test9 VALUES (0),(0),(0),(0);
mysql> SELECT * FROM test9;
mysql> DELETE FROM test9 WHERE id = 4;
mysql> INSERT INTO test9 VALUES(0);
mysql> SELECT * FROM test9;
mysql> DELETE FROM test9 where id=5;
# --- 此时重启数据库 ---
# --- 新插入一个空值 ---
mysql> INSERT INTO test1 values(0);
# 新插入的 0 值分配的自增主键值是 4 => 按重启前的操作逻辑应该分配 6
mysql> SELECT * FROM test1;
+----+
| id |
+----+
| 1 |
| 2 |
| 3 |
| 4 |
+----+
MySQL 5.7 中自增主键的分配取决于 InnoDB 数据字典内部的一个计数器:
该计数器只在内存中维护,并不会持久化到磁盘。当数据库重启时,该计数器会被初始化。
MySQL 8.0 后,自增主键的计数器会持久化到重做日志中:
每次计数器发生改变,都会将改变写入重做日志。数据库重启时,InnoDB 会根据重做日志所保存的信息来初始化计数器的内存值。
FOREIGN KEY Constraint
MySQL supports foreign keys, which permit cross-referencing related data across tables, and foreign key constraints, which help keep the related data consistent. dev.mysql
A foreign key relationship involves a parent table that holds the initial column values, and a child table with column values that reference the parent column values. A foreign key constraint is defined on the child table.
外键是一张表中的某个字段或某些字段集合,这些字段其实是其他表中的主键。
主表即父表,是被引用参考的表;从表即子表,是引用别人的表。
从表的外键列,必须引用主表的主键或者唯一约束的列,因为被依赖的值必须是唯一的。
在创建外键约束时,若不给外键约束命名,默认名不是列名,而是自动产生的一个外键名。
创建表时就指定外键约束的话,应该先创建主表,再创建从表。在删除表时,应该先删除从表或者先删除外键约束,再删除主表。
一个表可以建立多个外键约束。
从表的外键列与主表的被参照列名字可以不相同,但是数据类型必须一样。如果类型不一样,创建子表时,就会出现错误 "ERROR 1005 (HY000)"。
设置外键约束时,系统会默认建立对应的普通索引,索引名是外键的约束名。删除外键约束后,必须手动删除对应的索引。
/* 在创建表时添加 */
# 创建主表与从表 -- 表级约束
mysql> CREATE TABLE dept1(dept_id INT,dept_name VARCHAR(15));
mysql> CREATE TABLE emp1(emp_id INT PRIMARY KEY AUTO_INCREMENT,emp_name VARCHAR(15),department_id INT,CONSTRAINT fk_emp1_dept_id FOREIGN KEY (department_id) REFERENCES dept1(dept_id)); # ERROR 1822 (HY000): Failed to add the foreign key constraint. Missing index for constraint 'fk_emp1_dept_id' in the referenced table 'dept1'
# 报错原因是主表没有主键约束 => 完成主键添加后再进行从表创建
mysql> ALTER TABLE dept1 ADD PRIMARY KEY (dept_id);
# 不能添加主表中没有的字段值 => 先向主表添加再向从表添加
mysql> INSERT INTO emp1 VALUES (1001,'zs',10); # ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint fails (`dbtest13`.`emp1`, CONSTRAINT `fk_emp1_dept_id` FOREIGN KEY (`department_id`) REFERENCES `dept1` (`dept_id`))
mysql> INSERT INTO dept1 VALUES (10, 'IT');
# 删除、更新失败 => 外键约束起作用
mysql> DELETE FROM dept1 WHERE dept_id = 10; # ERROR 1451 (23000): Cannot delete or update a parent row: a foreign key constraint fails (`dbtest13`.`emp1`, CONSTRAINT `fk_emp1_dept_id` FOREIGN KEY (`department_id`) REFERENCES `dept1` (`dept_id`))
mysql> UPDATE dept1 SET dept_id = 20 WHERE dept_id = 10; # ERROR 1451 (23000): Cannot delete or update a parent row: a foreign key constraint fails (`dbtest13`.`emp1`, CONSTRAINT `fk_emp1_dept_id` FOREIGN KEY (`department_id`) REFERENCES `dept1` (`dept_id`))
# 在创建表后添加外键约束
mysql> CREATE TABLE dept2(dept_id INT PRIMARY KEY,dept_name VARCHAR(15));
mysql> CREATE TABLE emp2(emp_id INT PRIMARY KEY AUTO_INCREMENT,emp_name VARCHAR(15),department_id INT);
mysql> SELECT * FROM information_schema.table_constraints WHERE table_name = 'emp2';
mysql> ALTER TABLE emp2 ADD CONSTRAINT fk_emp2_dept2_id FOREIGN KEY (department_id) REFERENCES dept2(dept_id);
mysql> SELECT * FROM information_schema.table_constraints WHERE table_name = 'emp2';
删除外键约束:
- 查看约束名 + 删除外键约束
- 查看索引名 + 删除索引
SELECT * FROM information_schema.table_constraints WHERE table_name = '表名称'; # 查看某个表的约束名
ALTER TABLE 从表名 DROP FOREIGN KEY 外键约束名;
SHOW INDEX FROM 表名称; # 查看某个表的索引名
ALTER TABLE 从表名 DROP INDEX 索引名;
mysql> SELECT * FROM information_schema.table_constraints WHERE table_name = 'emp1';
mysql> ALTER TABLE emp1 DROP FOREIGN KEY fk_emp1_dept_id; # 删除外键约束
mysql> ALTER TABLE emp1 DROP INDEX fk_emp1_dept_id; # 删除外键约束对应普通索引
约束等级:
- Cascade:父表上更新、删除时,子表同步操作匹配记录
- Set null:父表上更新、删除记录时,子表上匹配记录的列设为 null
- No action:若子表中有匹配的记录,则不允许对父表相关键操作
- Restrict:同 no action,立即检查外键约束
- Set default:父表变更时,子表将外键列设置成默认的值(Innodb 不识)
- 没有指定等级相当于 Restrict
- 外键约束最好采用 ON UPDATE CASCADE ON DELETE RESTRICT
在 MySQL 里,外键约束是有成本的,需要消耗系统资源。对于大并发的 SQL 操作,有可能会不适合。比如大型网站的中央数据库,可能会因为外键约束的系统开销而变得非常慢。所以, MySQL 允许不使用系统自带的外键约束,在应用层面完成检查数据一致性的逻辑。也就是说,即使不用外键约束,也要想办法通过应用层面的附加逻辑,来实现外键约束的功能,确保数据的一致性。
《阿里开发规范》
【强制】不得使用外键与级联,一切外键概念必须在应用层解决。说明:(概念解释)学生表中的 student_id 是主键,那么成绩表中的 student_id 则为外键。如果更新学生表中的 student_id,同时触发成绩表中的 student_id 更新,即为级联更新。外键与级联更新适用于单机低并发,不适合分布式、高并发集群;级联更新是强阻塞,存在数据库更新风暴的风险;外键影响数据库的插入速度。
CHECK Constraint
CHECK 约束用于检查某个字段的值是否符合要求,一般是指值的范围。
MySQL 5.7 中 CHECK Constraint 无效(没有错误或警告提示)。
mysql> CREATE TABLE test10(id INT,last_name VARCHAR(15), salary DECIMAL(10,2) CHECK(salary > 3000));
mysql> INSERT INTO test10 VALUES(1,'hz',3500);
# 添加失败
mysql> INSERT INTO test10 VALUES(2,'hzcopy',1500); # ERROR 3819 (HY000): Check constraint 'test10_chk_1' is violated.
DEFAULT Constraint
DEFAULT 约束用于为列设置默认值。
若插入数据时未显示赋值,那么默认值将会被添加。
# 在创建表时添加默认值
CREATE TABLE 表名(字段 类型 DEFAULT 默认值);
# 在创建表后添加默认值
ALTER TABLE 表名 MODIFY 字段名 字段类型 DEFAULT 默认值;
# 删除默认值
mysql> ALTER TABLE test12 MODIFY salary DECIMAL (10,2);
其他数据库对象
常见的数据库对象 | 描述 |
---|---|
表 TABLE | 存储数据的逻辑单元,以行和列的形式存在,列就是字段,行就是记录 |
数据字典 | 存放数据库相关信息的系统表 => 系统表的数据通常由数据库系统维护,程序员通常不应该修改,只可查看 |
约束 CONSTRAINT | 执行数据校验的规则,用于保证数据完整性的规则 |
视图 VIEW | 一个或者多个数据表里数据的逻辑显示;视图并不存储数据 |
索引 INDEX | 用于提高查询性能,相当于书的目录 |
存储过程 PROCEDURE | 用于完成一次完整的业务处理,没有返回值,但可通过传出参数将多个值传给调用环境 |
存储函数 FUNCTION | 用于完成一次特定的计算,具有一个返回值 |
触发器 TRIGGER | 相当于一个事件监听器,当数据库发生特定事件后,触发器被触发,完成相应的处理 |
视图
视图可以使用表的一部分,也可以针对不同需求制定不同的查询视图,即针对指定的人员只展示部分的数据。本质是一段存储起来的 SELECT。
视图建立在已有表(基表)的基础上,可以看作是虚拟表,本身不具有数据,占用很少的内存空间。视图的创建和删除只影响视图本身,不影响对应的基表。但是当对视图中的数据进行增删改操作时,数据表中的数据会发生相应变化,反之亦然。
- 创建视图
# 在 CREATE VIEW 语句中嵌入子查询
CREATE [OR REPLACE] [ALGORITHM = {UNDEFINED | MERGE | TEMPTABLE}] VIEW 视图名称 [(字段列表)] AS 查询语句 [WITH [CASCADED|LOCAL] CHECK OPTION]
结束
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议,转载请注明出处!