【MySQL 二】DDL 与 DML
封面来源:本文封面来源于 MySQL 官网,如有侵权,请联系删除。
本文参考:MySQL数据库教程天花板,mysql安装到mysql高级,强!硬!
1. 一些基础知识
1.1 一条数据存储的过程
在 MySQL 中,一个完整的数据存储过程总共有 4 步,分别是创建数据库、确认字段、创建数据表、插入数据。
从系统架构的层次上看,MySQL 数据库系统从大到小依次是数据库服务器、数据库、数据表、数据表的行与列。因此,需要先创建数据库,而不是直接创建数据表。
1.2 标识符命名规则
- 数据库名、表名不得超过 30 个字符,变量名限制为 29 个
- 只能包含
A–Z
、a–z
、0–9
和_
共 63 个字符 - 数据库名、表名、字段名等对象名中间不能包含空格
- 同一个 MySQL 软件中,数据库不能同名;同一个库中,表不能重名;同一个表中,字段不能重名
- 必须保证字段没有和保留字、数据库系统或常用方法冲突。如果必须使用,需要在使用
`
(反引号)包裹 - 保持字段名和类型的一致性:在命名字段并为其指定数据类型的时候一定要保证一致性,假如数据类型在一个表里是整数,在另一个表里也应该是整数
1.3 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 | 从-231 到231-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 |
2. 创建和管理数据库
2.1 创建数据库
方式一:简单地创建数据库
1 | CREATE DATABASE 数据库名; |
方式二:创建数据库并指定字符集
1 | CREATE DATABASE 数据库名 CHARACTER SET 字符集; |
方式三(推荐):判断数据库是否已经存在,不存在才创建数据库
1 | CREATE DATABASE IF NOT EXISTS 数据库名; |
注意: MySQL 不支持直接修改数据库名,一些 DBMS 所支持的修改是先新建库,再把旧库中所有表的数据复制到新库,最后删除旧库完成的。
2.2 使用数据库
查看当前所有数据库:
1 | SHOW DATABASES; # 注意末尾的 S,表示多个数据库 |
查看当前正在使用的数据库:
1 | SELECT DATABASE(); # 使用 MySQL 中的一个全局函数 |
查看数据库的创建信息:
1 | SHOW CREATE DATABASE 数据库名; |
切换或使用数据库:
1 | USE 数据库名; |
注意: 在对某个数据表进行操作时,需要先声明使用的哪个库,否则需要采用 数据库名.表名
的方式。
2.3 修改数据库
修改数据库的字符集:
1 | ALTER DATABASE 数据库名 CHARACTER SET 字符集; |
2.4 删除数据库
方式一:
1 | DROP DATABASE 数据库名; |
方式二(推荐):
1 | DROP DATABASE IF EXISTS 数据库名; |
3. 表的创建
3.1 创建方式一
创建表时:
- 必须具备:
CREATE TABLE
权限;一定的存储空间。 - 必须指定:表名;列名(或字段名);数据类型;长度。
- 可选指定:约束条件;默认值。
语法格式:
1 | CREATE TABLE [IF NOT EXISTS] 表名( |
使用 IF NOT EXISTS
关键字后,如果当前数据库中不存在要创建的数据表,就创建数据表;否则忽略建表语句。
使用示例
1 | -- 创建表 |
上述建表语句中并没有指定 INT
类型的长度,但 MySQL 在执行建表语句时,默认将 INT
类型的长度设置为 11
,这也表示 INT
类型的默认长度是 11
,当然也可以在创建表时指定数据类型的长度。比如:
1 | CREATE TABLE dept( |
在 MySQL 8.x 版本中,不再推荐为 INT
类型指定显示长度,同时在未来的版本中可能去掉这样的语法。
3.2 创建方式二
可以使用 AS subquery
在创建表时一并插入数据,其语法结构如下:
1 | CREATE TABLE table_name |
注意: 指定的列和子查询中的列要一一对应。
使用示例
通过列名和默认值定义列:
1 | CREATE TABLE emp1 AS SELECT * FROM employees; |
1 | CREATE TABLE dept80 |
3.3 查看数据表结构
MySQL 支持使用 DESCRIBE
或 DESC
语句查看表结构,也支持使用 SHOW CREATE TABLE
语句查看数据表结构。语法格式如下:
1 | SHOW CREATE TABLE 表名\G |
相比于 DESCRIBE
或 DESC
语句,这种语句可以查看表创建时的详细语句,还可以查看存储引擎和字符编码。
4. 修改表
使用 ALTER TABLE
语句可以实现:
- 向已有的表中添加列
- 修改现有表中的列
- 删除现有表中的列
- 重命名现有表中的列
4.1 追加一个列
语法格式如下:
1 | ALTER TABLE 表名 ADD 字段名 字段类型 [FIRST|AFTER 字段名]; |
比如向 dept80 表中增加一个 job_id 列:
1 | ALTER TABLE dept80 ADD job_id varchar(15); |
存在列时就不创建
使用 IF NOT EXISTS
关键词后,当创建的数据库或数据表已经存在时,就不会再执行创建逻辑,那对列也可以进行这样的操作吗?
很遗憾 ALTER TABLE
并不支持 IF NOT EXISTS
关键词,可以使用 存储过程 来实现这个需求(关于存储过程的详细内容参考 【MySQL 六】变量、流程控制、游标与触发器):
1 | DROP PROCEDURE IF EXISTS add_column; |
4.2 修改一个列
使用 ALTER TABLE
修改列的数据类型、长度、默认值和位置。语法如下:
1 | ALTER TABLE 表名 MODIFY 字段名1 字段类型 [DEFAULT 默认值] [FIRST|AFTER 字段名2]; |
比如:
1 | ALTER TABLE dept80 MODIFY last_name VARCHAR(30); |
如果某列最开始没有设置默认值,后续设置的默认值只影响今后对表的修改。
4.3 重命名一个列
使用 CHANGE
可以重命名列,其语法如下:
1 | ALTER TABLE 表名 CHANGE 列名 新列名 新数据类型; |
比如:
1 | ALTER TABLE dept80 CHANGE department_name dept_name varchar(15); |
4.4 删除一个列
具体语法如下;
1 | ALTER TABLE 表名 DROP COLUMN 字段名; |
比如:
1 | ALTER TABLE dept80 DROP COLUMN job_id; |
5. 其他的表操作
5.1 重命名表
执行重命名操作的必须是对象的所有者。
使用
RENAME
重命名表
1 | RENAME TABLE emp TO myemp; |
使用
ALTER TABLE
重命名表
1 | ALTER table dept RENAME [TO] detail_dept; -- [TO]可以省略 |
5.2 删除表
在 MySQL 中,当一张数据表没有与其他任何数据表形成关联关系时,可以将当前数据表直接删除。删除表时,数据和结构都被删除,所有正在运行的相关事务被提交,所有相关索引都会被删除。
语法格式是:
1 | DROP TABLE [IF EXISTS] 数据表1 [, 数据表2, ..., 数据表n]; |
使用 IF EXISTS
后,如果当前数据库中存在相应的数据表,则删除数据表;否则忽略删除语句,不执行删除数据表的操作。
比如:
1 | DROP TABLE dept80; |
注意: DROP TABLE
语句不能回滚。
5.3 清空表
可以使用 TRUNCATE TABLE
语句删除表中所有数据,并释放表的存储空间。比如:
1 | TRUNCATE TABLE detail_dept; |
与 DROP TABLE
语句一样,TRUNCATE TABLE
语句也不能回滚。
而使用 DELETE
语句删除数据时,可以回滚。比如:
1 | SET autocommit = FALSE; |
阿里开发规范
TRUNCATE TABLE
比 DELETE
速度快,且使用的系统和事务日志资源少,但 TRUNCATE
无事务且不触发 TRIGGER
,有可能造成事故,故不建议在开发代码中使用此语句。
说明:TRUNCATE TABLE
在功能上与不带 WHERE
子句的 DELETE
语句相同。
6. 内容拓展
6.1 MySQL 字段命名
1、【强制】表名、字段名必须使用小写字母或数字,禁止出现数字开头,禁止两个下划线中间只出现数字。数据库字段名的修改代价很大,因为无法进行预发布,所以字段名称需要慎重考虑。比如:aliyun_admin、rdc_config、level3_name。
2、【强制】禁用保留字,如 DESC
、RANGE
、MATCH
、DELAYED
等。
3、【强制】表必须具备以下三个字段:id
、gmt_create
和 gmt_modified
。其中,id
是主键,类型是 BIGINT UNSIGNED
,单表时自增,步长为 1。gmt_create
和 gmt_modified
的类型都是 DATETIME
,前者表示创建时间,后者表示更新时间。
4、【推荐】表的命名最好是遵循“业务名称_表的作用”。比如:alipay_task、force_project、trade_config。
5、合适的字符存储长度,不但节约数据库表空间、节约索引存储,更重要的是提升检索速度。比如:无符号值可以避免误存负数,且扩大了表示范围。
对象 | 年龄区间 | 类型 | 字节 | 表示范围 |
---|---|---|---|---|
人 | 150 岁之内 | tinyint unsigned | 1 | 无符号值:0 - 255 |
龟 | 数百岁 | smallint unsigned | 2 | 无符号值:0 - 65535 |
恐龙化石 | 数千万年 | int unsigned | 4 | 无符号值:0 - 43亿 |
太阳 | 约 50 亿年 | bigint unsigned | 8 | 无符号值:0 - 10 的 19 次方 |
6.2 清空、删除表需谨慎
表删除操作将把表的定义和表中的数据一起删除,并且 MySQL 在执行删除操作时,不会有任何的确认信息提示,因此执行删除操时应当慎重。在删除表前,最好对表中的数据进行备份,这样当操作失误时可以对数据进行恢复,以免造成无法挽回的后果。
同样的,在使用 ALTER TABLE
进行表的基本修改操作时,在执行操作过程之前,也应该确保对数据进行完整的备份,因为 数据库的改变是无法撤销的, 如果添加了一个不需要的字段,可以将其删除;相同的,如果删除了一个需要的列,该列下面的所有数据都将会丢失。
6.3 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; |
MySQL 5.7 中的删除
在 MySQL 5.7 中的删除数据表 book1 和 book2,由于当前数据库中有且仅有 book1 表,因此在删除时不难猜到会报 Unknown table
错误。比如:
1 | mysql> DROP TABLE book1,book2; |
当使用 SHOW TABLES;
查询当前库中存在的表时, 会发现 book1 表已经被删除。
MySQL 8.0 中的删除
执行与在 MySQL 5.7 中相同的删除表语句,同样会报 Unknown table
错误,但是再使用 SHOW TABLES;
查询当前库中存在的表时, book1 表并没有被删除。 这是因为 DDL 执行失败后,操作回滚了。
7. 插入数据
7.1 VALUES 的方式添加
使用这种语法一次只能向表中插入一条数据。
为表中的所有字段按默认顺序插入数据
具体语法如下:
1 | INSERT INTO 表名 VALUES (value1, value2, ...); |
(value1, value2, ...)
值列表中的顺序 必须 和数据表中字段定义时的顺序相同。
比如:
1 | INSERT INTO departments VALUES (70, 'Pub', 100, 1700); |
为表的指定字段插入数据
具体语法如下:
1 | INSERT INTO 表名(column1 [, column2, ..., columnn]) |
这种语法就是在 INSERT
语句中只向部分字段插入值,而其他字段的值为表定义时的默认值。
INSERT
子句中列名的顺序随意,但是 VALUES
后的值列表顺序要与其一致,否则当类型不同时,不仅会插入失败,还会报错。
比如:
1 | INSERT INTO departments(department_id, department_name) VALUES (80, 'IT'); |
同时插入多条数据
INSERT
语句可以同时向表中插入多条数据,只需要指定多个值列表,并且彼此之间使用逗号隔开。具体语法如下:
1 | INSERT INTO table_name |
或者
1 | INSERT INTO table_name(column1 [, column2, ..., columnn]) |
比如:
1 | INSERT INTO emp(emp_id,emp_name) |
使用 INSERT
同时插入多条记录时,MySQL
会返回一些在执行单行插入时没有的额外信息,这些信息的含义如下:
Records:插入的记录条数。
Duplicates:插入时被忽略的记录,可能是这些记录包含了重复的主键值。
Warnings:有问题的数据值,例如发生数据类型转换。
小贴士
1、同时插入多行记录的 INSERT
语句等同于多个单行插入的 INSERT
语句,但是多行的 INSERT
语句在处理过程中效率更高。因为 MySQL 执行单条 INSERT
语句插入多行数据比使用多条 INSERT
语句快,所以在插入多条记录时最好选择使用单条 INSERT
语句的方式插入。
2、VALUES
也可以省略末尾的 S
,但 VALUES
的写法更标准。
3、字符和日期类型数据应该被单引号包裹。
7.2 将查询结果插入表中
INSERT
还可以将 SELECT
语句查询的结果插入到表中,并且不需要把每一条记录的值一个一个输入,而只需使用一条 INSERT
语句和一条 SELECT
语句组成的组合语句即可快速地从一个或多个表中向一个表中插入多行。
基本语法格式如下:
1 | INSERT INTO 目标表名 |
简单来说就是:在 INSERT
语句中加入子查询,并且不必再书写 VALUES
子句,子查询的值列表应与 INSERT
子句中的列名对应。
比如:
1 | INSERT INTO emp2 |
8. 更新数据
使用 UPDATE
关键词可以对表中数据进行更新,具体语法如下:
1 | UPDATE table_name |
由于 UPDATE
可以和 WHERE
一起使用,因此可以一次性更新多条数据。如果需要回滚数据,需要在执行 DML 语句前进行设置:
1 | SET AUTOCOMMIT = FALSE; |
使用 WHERE
子句对符合条件的数据进行更新:
1 | UPDATE employees |
如果省略 WHERE
子句,那么表中所有的数据都会被更新:
1 | UPDATE copy_emp |
9. 删除数据
使用 DELETE
关键词可以对表中数据进行删除,具体语法如下:
1 | DELETE FROM table_name [WHERE condition]; |
使用 WHERE
子句删除表中指定的数据:
1 | DELETE FROM departments WHERE department_name = 'Finance'; |
省略 WHERE
子句,删除指定表中所有的数据:
1 | DELETE FROM copy_emp; |
10. MySQL8.0 新特性:计算列
所谓计算列,简单来说就是通过别的列计算出某一列的值。例如,a 列值为 1、b 列值为 2,c 列不需要手动插入,定义 a + b
的结果为 c 的值,那么 c 就是计算列。
在 MySQL 8.0 中,CREATE
和 ALTER
都支持增加计算列。
以 CREATE
为例,定义数据表 tb1,然后定义字段 id、字段 a、字段 b和字段 c,其中字段 c 为计算列,用于计算 a + b
的值。 首先创建测试表 tb1,语句如下:
1 | CREATE TABLE tb1( |
执行以下语句,并查看字段 c 的值:
1 | INSERT INTO tb1(a, b) VALUES (100, 200); |
更新字段 a 的值,再查看字段 c 的值:
1 | UPDATE tb1 SET a = 500; |
11. 综合案例
1、创建数据库 test01_library
:
1 | CREATE DATABASE IF NOT EXISTS test01_library CHARACTER SET 'utf8'; |
2、创建表 books
,表结构如下:
字段名 | 字段说明 | 数据类型 |
---|---|---|
id | 书编号 | INT |
name | 书名 | VARCHAR(50) |
authors | 作者 | VARCHAR(100) |
price | 价格 | FLOAT |
pubdate | 出版日期 | YEAR |
note | 说明 | VARCHAR(100) |
num | 库存 | INT |
1 | CREATE TABLE books( |
3、向 books
表中插入以下数据,要求如下:
- 不指定字段名称,插入第一条记录
- 指定所有字段名称,插入第二记录
- 同时插入剩下所有数据
id | name | authors | price | pubdate | note | num |
---|---|---|---|---|---|---|
1 | Tal of AAA | Dickes | 23 | 1995 | novel | 11 |
2 | EmmaT | Jane lura | 35 | 1993 | joke | 22 |
3 | Story of Jane | Jane Tim | 40 | 2001 | novel | 0 |
4 | Lovey Day | George Byron | 20 | 2005 | novel | 30 |
5 | Old land | Honore Blade | 30 | 2010 | law | 0 |
6 | The Battle | Upton Sara | 30 | 1999 | medicine | 40 |
7 | Rose Hood | Richard haggard | 28 | 2008 | cartoon | 28 |
1 | # 插入第一条记录 |
4、将小说类型 novel 的书的价格都增加 5:
1 | UPDATE books SET price = price + 5 WHERE note = 'novel'; |
5、将名称为 EmmaT 的书的价格改为 40,并将说明改为 drama:
1 | UPDATE books SET price = 40, note = 'drama' WHERE name = 'EmmaT'; |
6、删除库存为 0 的记录:
1 | DELETE FROM books WHERE num = 0; |
7、统计书名中包含 a 字母的书:
1 | SELECT * FROM books WHERE name LIKE '%a%'; |
8、统计书名中包含 a 字母的书的数量和库存总量:
1 | SELECT COUNT(*), SUM(num) FROM books WHERE name LIKE '%a%'; |
9、找出 novel 类型的书,按照价格降序排列:
1 | SELECT * FROM books WHERE note = 'novel' ORDER BY price DESC; |
10、查询图书信息,按照库存量降序排列,如果库存量相同的按照 note 升序排列:
1 | SELECT * FROM books ORDER BY num DESC, note ASC; |
11、按照 note 分类统计书的数量:
1 | SELECT note, COUNT(*) FROM books GROUP BY note; |
12、按照 note 分类统计书的库存量后显示库存量超过 30 本的书籍:
1 | SELECT note, SUM(num) FROM books GROUP BY note HAVING SUM(num) > 30; |
13、查询所有图书,每页显示 5 本,显示第二页:
1 | SELECT * FROM books LIMIT 5, 5; |
14、按照 note 分类统计书的库存量,显示库存量最多的:
1 | SELECT note, SUM(num) sum_num FROM books GROUP BY note ORDER BY sum_num DESC LIMIT 0, 1; |
15、查询书名达到 10 个字符的书,不包括里面的空格:
1 | SELECT * FROM books WHERE CHAR_LENGTH(REPLACE(name,' ','')) >= 10; |
16、查询书名和类型,其中 note 值为 novel 显示小说,law 显示法律,medicine 显示医药,cartoon 显示卡通,joke 显示笑话:
1 | SELECT name AS "书名", note, CASE note |
17、查询书名、库存,其中 num 值超过 30 本的,显示滞销,大于 0 并低于 10 的,显示畅销,为 0 的显示需要无货:
1 | SELECT name, num, CASE |
18、统计每一种 note 的库存量,并合计总量:
1 | SELECT IFNULL(note, '合计总库存量') AS note, SUM(num) FROM books GROUP BY note WITH ROLLUP; |
19、统计每一种 note 的数量,并合计总量:
1 | SELECT IFNULL(note, '合计总数') AS note, COUNT(*) FROM books GROUP BY note WITH ROLLUP; |
20、统计库存量前三名的图书:
1 | SELECT * FROM books ORDER BY num DESC LIMIT 0, 3; |
21、找出最早出版的一本书:
1 | SELECT * FROM books ORDER BY pubdate ASC LIMIT 0, 1; |
22、找出 novel 中价格最高的一本书:
1 | SELECT * FROM books WHERE note = 'novel' ORDER BY price DESC LIMIT 0, 1; |
23、找出书名中字数最多的一本书,不含空格:
1 | SELECT * FROM books ORDER BY CHAR_LENGTH(REPLACE(name,' ','')) DESC LIMIT 0, 1; |