目录

一.基本SQL语句用法及概述

1.常用MySQL命令

2.语法规范

3.SQL语句分类

二.数据查询语言

1.基础查询

1)查询的字段列表可以是字段、常量、表达式、函数等

2)使用别名,字段名和别名之间可以用空格或关键字AS与as指定别名

3)去重    distinct

4)使用concat函数进行字符串拼接

2.条件查询

1)件运算符

2)逻辑运算符

3.模糊查询

4.排序    order by

三.数据查询语言之函数应用

1.常用函数分类

1)按使用方式分为:

2)按用途分为

2.函数应用

1)字符函数实例:

2)日期和时间函数实例

3)分组函数

四.数据查询语言之分组查询

1.分组查询的概述

五.数据查询语言之连接查询

1.连接查询的概述

2.连接分类

3.SQL99标准多表查询

1)内连接

2)等值连接

3)非等值连接 between

4)自连接

5)外连接的概述

6)左外连接

7)右外连接

8)交叉连接 cross join

六.数据查询语言之子查询

1.子查询的概述

2.子查询实例

1)单行单列

2)多行单列

3)单行多列

4)多行多列

5)分页查询

6)联合查询UNION

七.插入语句DDL

1.不指定列名的插入

2.指定列名的插入

3.使用set语句

4.修改语句

5.删除记录

6.删除多表记录

7.清空表

八.数据库管理

1.创建数据库

2.修改数据库

3.删除数据库

九.表管理

1.关系数据库的规范化

2.表管理语句

3.常用数据类型

4.示例:

1)修改表

2)修改列的类型或约束

3)添加新列

4)删除列

5)修改表名

6)删除表

7)表复制

8)复制表结构及数据

十.约束

1.约束分类

2.约束应用

1)列级应用

2)表级约束

3)删除约束

十一.事务控制语言DCL

1.事务

2.事务必须满足的4个条件

3.事务控制语句

4.MySQL事物处理的方法

5.事务的创建

1)隐式事务

7.事务示例

十二.事务隔离

1.事务隔离要解决的问题

2.事务隔离级别

3.设置事务隔离级别

1)查看当前事务隔离级别

2)设置隔离事务级别

3)测试

4.SAVEPOINT应用

1)基本用法

2)SAVEPOINT示例


一.基本SQL语句用法及概述

1.常用MySQL命令

# 查看所有数据库
SHOW DATABASES;
# 切换指定数据库
USE nsd2021;
# 查看当前库中所有的表
SHOW TABLES;
# 查看表结构
DESC departments;
# 查看当前所处的数据库
SELECT DATABASE();
# 查看当前登陆用户
SELECT USER();
# 查看版本
SELECT VERSION();
[root@mysql1 ~]# mysql --version #不进库查看
[root@mysql1 ~]# mysql -V

2.语法规范

关键字不区分大小写,但建议关键字大写

表名、列名建议小写

每条命令最好用分号结尾,当然,你用`\G结尾也可以

每条命令根据需要,可以进行缩进或换行(最好是关键字单独占一行),如:

mysql> SELECT
    -> name, email
    -> FROM
    -> employees;

注释

        单行注释

mysql> # select * from departments
mysql> -- select * from departments

        多行注释

mysql> /*
   /*> SELECT
   /*> *
   /*> FROM
   /*> departments;
   /*> */

3.SQL语句分类

数据查询语言(Data Query Language, )DQL

负责进行数据查询而不会对数据本身进行修改的语句,这是最基本的SQL语句。

数据定义语言 (Data Definition Language,)DDL

负责数据结构定义与数据库对象定义的语言,由CREATE、ALTER与DROP三个语法所组成

数据操纵语言(Data Manipulation Language,)DML

负责对数据库对象运行数据访问工作的指令集,以INSERT、UPDATE、DELETE三种指令为核心,分别代表插入、更新与删除。

数据控制语言 (Data Control Language)

它可以控制特定用户账户对数据表、查看表、预存程序、用户自定义函数等数据库对象的控制权。由 GRANT 和 REVOKE 两个指令组成。

二.数据查询语言

1.基础查询

SELECT 查询的字段列表 FROM 表;

1)查询的字段列表可以是字段、常量、表达式、函数等

# 查单个字段
select dept_name from departments;
# 查多个字段
select name, email from employees;
# 查所有字段
select * from departments;
# 使用表达式
select date, employee_id, basic+bonus from salary;
# 查询常量
select 100;
# 查询表达式
select 10+5;
# 查询函数
select version();
# 查询函数,统计salary共有多少行记录
select count(*) from salary;

2)使用别名,字段名和别名之间可以用空格或关键字AS与as指定别名

mysql> select dept_id 部门编号, dept_name AS 部门名 from departments;

3)去重    distinct

select dept_id from employees;
select distinct dept_id from employees;

4)使用concat函数进行字符串拼接

select concat(name, '-', phone_number) from employees;

2.条件查询

SELECT 查询的字段列表 FROM 表 WHERE 条件;

1)件运算符

>: 大于

<: 小于

=: 等于

>=: 大于等于

<=: 小于等于

!=: 不等于

select * from departments where dept_id>3; 
select * from departments where dept_id<3;
select * from departments where dept_id=3;
select * from departments where dept_id!=3;
select * from departments where dept_id>=3;
select * from departments where dept_id<=3;

2)逻辑运算符

and(&&)、or(||)、not(!)

select * from departments where dept_id>1 and dept_id<5;
select * from departments where dept_id<3 or dept_id>6;
select * from departments where not dept_id<=6;

3.模糊查询

- like: 包含
- between xxx and yyy :        在xxx和yyy之间的
- in:在列表中的
- is null:为空,相当于python的None
- is not null:非空

%匹配0到多个任意字符

 _匹配一个字符

# %匹配0到多个任意字符
select name, email from employees where name like '张%';
# _匹配一个字符
select name, email from employees where name like '张_';
select * from departments where dept_id between 3 and 5;
select * from departments where dept_id in (1, 3, 5, 8);
# 匹配部门名为空的记录
select * from departments where dept_name is null;
# 查询部门名不为空的记录
select * from departments where dept_name is not null;

4.排序    order by

SELECT 查询的字段列表 FROM 表 ORDER BY 排序列表 [asc|desc];

排序:默认升序

select name, birth_date from employees where birth_date>'19980101';
# 默认升序排列
select name, birth_date from employees where birth_date>'19980101' order by birth_date;
# 降序排列
select name, birth_date from employees where birth_date>'19980101' order by birth_date desc;

三.数据查询语言之函数应用

1.常用函数分类

1)按使用方式分为:

  • 单行函数

  • 分组函数

2)按用途分为

  • 字符函数

  • 数学函数

  • 日期函数

  • 流程控制函数

SELECT 函数(参数) FROM 表;

2.函数应用

1)字符函数实例:

  • LENGTH(str):返字符串长度,以(字节)为单位

select length('abc');
select length('你好');
select name, email, length(email) from employees where name='李平';
  • CHAR_LENGTH(str): 返回字符串长度,以(字符)为单位

select char_length('abc');
select char_length('你好');
  • CONCAT(s1,s2,...): 返回连接参数产生的字符串,一个或多个待拼接的内容,任意一个为NULL则返回值为NULL

# 拼接字符串
mysql> select concat(dept_id, '-', dept_name) from departments;
  • UPPER(str)和UCASE(str): 将字符串中的字母全部转换成大写

select name, upper(email) from employees where name like '李%';
  • LOWER(str)和LCASE(str):将str中的字母全部转换成小写

# 转小写
select lower('HelloWorld');
  • SUBSTR(s, start, length): 从子符串s的start位置开始,取出length长度的子串,位置(从1)开始计算

select substr('hello world', 7);
# 取子串,下标从7开始取出3个
select substr('hello world', 7, 3);
  • INSTR(str,str1):返回str1参数,在str参数内的位置

# 子串在字符串中的位置
select instr('hello world', 'or');
select instr('hello world', 'ol');
  • TRIM(s): 返回字符串(s删除了两边空格之后的字符串)

select trim('  hello world.  ');

数学函数实例

  • ABS(x):返回x的绝对值

select abs(-10);(负—>正)
  • PI(): 返回圆周率π,默认显示6位小数

select pi();
  • MOD(x,y): 返回x被y除后的余数

select mod(10, 3);
  • CEIL(x)、CEILING(x): 返回不小于x的最小整数

select ceil(10.1);
  • FLOOR(x): 返回不大于x的最大整数

select floor(10.9);
  • ROUND(x)、ROUND(x,y): 前者返回最接近于x的整数,即对x进行四舍五入;后者返回最接近x的数,其值保留到小数点后面y位,若y为负值,则将保留到x到小数点左边y位

select round(10.6666);返回最接近于x的整数,即对x进行四舍五入
select round(10.6666, 2);返回最接近x的数,其值保留到小数点后面y位

2)日期和时间函数实例

  • CURDATE()、CURRENT_DATE(): 将当前日期按照"YYYY-MM-DD"或者"YYYYMMDD"格式的值返回,具体格式根据函数用在字符串或是数字语境中而定

select curdate();当前日期按照"YYYY-MM-DD"
select curdate() + 0;格式根据函数用在字符串或是数字语境中而定
  • NOW(): 返回当前日期和时间值,格式为"YYYY_MM-DD HH:MM:SS"或"YYYYMMDDHHMMSS",具体格式根据函数用在字符串或数字语境中而定

select now();式为"YYYY_MM-DD HH:MM:SS"
select now() + 0;具体格式根据函数用在字符串或数字语境中而定
  • UNIX_TIMESTAMP()、UNIX_TIMESTAMP(date): 前者返回一个格林尼治标准时间1970-01-01 00:00:00到现在的秒数,后者返回一个格林尼治标准时间1970-01-01 00:00:00到指定时间的秒数

select unix_timestamp();
  • FROM_UNIXTIME(date): 和UNIX_TIMESTAMP互为反函数,把UNIX时间戳转换为普通格式的时间

select from_unixtime(0);
  • MONTH(date)和MONTHNAME(date):前者返回指定日期中的月份,后者返回指定日期中的月份的名称

select month('20211001120000');返回指定日期中的月份
select monthname('20211001120000');返回指定日期中的月份的名称
  • DAYNAME(d)、DAYOFWEEK(d)、WEEKDAY(d): DAYNAME(d)返回d对应的工作日的英文名称,如Sunday、Monday等;DAYOFWEEK(d)返回的对应一周中的索引,1表示周日、2表示周一;WEEKDAY(d)表示d对应的工作日索引,0表示周一,1表示周二

select dayname('20211001120000');返回星期*
select dayname('20211001');
  • WEEK(d): 计算日期d是一年中的第几周

select week('20211001');
  • DAYOFYEAR(d)、DAYOFMONTH(d): 前者返回d是一年中的第几天,后者返回d是一月中的第几天

select dayofyear('20211001');
  • YEAR(date)、QUARTER(date)、MINUTE(time)、SECOND(time): YEAR(date)返回指定日期对应的年份,范围是1970到2069;QUARTER(date)返回date对应一年中的季度,范围是1到4;MINUTE(time)返回time对应的分钟数,范围是0~59;SECOND(time)返回制定时间的秒值

select year('20211001');返回指定日期对应的年份
select quarter('20211001');回date对应一年中的季度

流程控制函数实例

  • IF(expr,v1,v2): 如果expr是TRUE则返回v1,否则返回v2

select if(3>0, 'yes', 'no');
select name, dept_id, if(dept_id=1, '人事部', '非人事部')  from employees where name='张亮';
  • IFNULL(v1,v2): 如果v1不为NULL,则返回v1,否则返回v2

select dept_id, dept_name, ifnull(dept_name, '未设置') from departments;
insert into departments(dept_id) values(9);
select dept_id, dept_name, ifnull(dept_name, '未设置') from departments; 
  • CASE expr (WHEN v1)( THEN r1) [WHEN v2 THEN v2] [ELSE rn] END: 如果expr等于某个vn,则返回对应位置THEN后面的结果,如果与所有值都不想等,则返回ELSE后面的rn

mysql> select dept_id, dept_name,
    -> case dept_nam
    -> when '运维部' then '技术部门'
    -> when '开发部' then '技术部门'
    -> when '测试部' then '技术部门'
    -> when null then '未设置'
    -> else '非技术部门'
    -> end as '部门类型'
    -> from departments;
# 查看字段,名字。匹配 名字是 当是v1就是r1 ,是v2 就是v2,否则就返回对应位置THEN后面的结果,如果与所有值都不想等,则返回ELSE后面的值
select dept_id, dept_name,
    -> case 
    -> when dept_name='运维部' then '技术部门'
    -> when dept_name='开发部' then '技术部门'
    -> when dept_name='测试部' then '技术部门'
    -> when dept_name is null then '未设置'
    -> else '非技术部门'
    -> end as '部门类型'
    -> from departments;

3)分组函数

用于统计,又称为聚合函数或统计函数

  • sum() :求和

select employee_id, sum(basic+bonus) from salary where employee_id=10 and year(date)=2018;
  • avg() :求平均值

select employee_id, avg(basic+bonus) from salary where employee_id=10 and year(date)=2018;
  • max() :求最大值

select employee_id, max(basic+bonus) from salary where employee_id=10 and year(date)=2018;
  • min() :求最小值

select employee_id, min(basic+bonus) from salary where employee_id=10 and year(date)=2018;
select count(*) from departments;

四.数据查询语言之分组查询

1.分组查询的概述

  • 在对数据表中数据进行统计时,可能需要按照一定的类别分别进行统计。比如查询每个部门的员工数。

  • 使用GROUP BY按某个字段,或者多个字段中的值,进行分组,字段中值相同的为一组

语法格式

  • 查询列表必须是分组函数和出现在(GROUP BY)后面的字段

  • 通常而言,分组前的数据筛选放在where子句中,分组后的数据筛选放在having子句中

SELECT 字段名1(要求出现在group by后面),分组函数(),……
FROM 表名
WHERE 条件
GROUP BY 字段名1,字段名2
HAVING 过滤条件
ORDER BY 字段;

应用实例

  • 查询每个部门的人数

select dept_id, count(*) from employees group by dept_id;
  • 查询每个部门中年龄最大的员工

select dept_id, min(birth_date) from employees group by dept_id;
  • 查询每个部门入职最晚员工的入职时间

select dept_id, max(hire_date) from employees group by dept_id;
  • 统计各部门使用tedu.cn邮箱的员工人数

mysql> select dept_id, count(*) from employees where email like '%@tedu.cn' group by dept_id;
+---------+----------+
| dept_id | count(*) |
+---------+----------+
|       1 |        5 |
|       2 |        2 |
|       3 |        4 |
|       4 |       32 |
|       5 |        7 |
|       6 |        5 |
|       7 |       15 |
|       8 |        1 |
+---------+----------+
8 rows in set (0.00 sec)
  • 查看员工2018年工资总收入,按总收入进行降序排列

mysql> select employee_id, sum(basic+bonus) as total from salary where year(date)=2018 group by employee_id order by total desc;
  • 查询部门人数少于10人

mysql> select dept_id, count(*) from employees where count(*)<10 group by dept_id;
ERROR 1111 (HY000): Invalid use of group function

mysql> select dept_id, count(*) from employees group by dept_id having count(*)<10;
+---------+----------+
| dept_id | count(*) |
+---------+----------+
|       1 |        8 |
|       2 |        5 |
|       3 |        6 |
|       6 |        9 |
|       8 |        3 |
+---------+----------+
5 rows in set (0.00 sec)

五.数据查询语言之连接查询

1.连接查询的概述

  • 也叫多表查询。常用于查询字段来自于多张表

  • 如果直接查询两张表,将会得到笛卡尔积

select name, dept_name from employees, departments;
  • 通过添加有效的条件可以进行查询结果的限定

select name, dept_name from employees, departments where employees.dept_id=departments.dept_id;

2.连接分类

按功能分类

  • 内连接(重要)

    • 等值连接

    • 非等值连接

    • 自连接

  • 外连接

    • 左外连接(重要)

    • 右外连接(重要)

    • 全外连接(mysql不支持,可以使用UNION实现相同的效果)

  • 交叉连接

按年代分类

  • SQL92标准:仅支持内连接

  • SQL99标准:支持所有功能的连接

3.SQL99标准多表查询

  • 语法格式

SELECT 字段... 
FROM 表1 [AS] 别名 [连接类型]
JOIN 表2 [AS] 别名
ON 连接条件
WHERE 分组前筛选条件
GROUP BY 分组
HAVING 分组后筛选条件
ORDER BY 排序字段

1)内连接

  • 语法格式

select 查询列表
from 表1 别名
inner join 表2 别名 on 连接条件
inner join 表3 别名 on 连接条件
[where 筛选条件]
[group by 分组]
[having 分组后筛选]
[order by 排序列表]

2)等值连接

  • 查询每个员工所在的部门名

select name, dept_name
    -> from employees
    -> inner join departments
    -> on employees.dept_id=departments.dept_id;
  • 查询每个员工所在的部门名,使用别名

select name, dept_name
    -> from employees as e
    -> inner join departments as d
    -> on e.dept_id=d.dept_id;
  • 查询每个员工所在的部门名,使用别名。两个表中的同名字段,必须指定表名

select name, d.dept_id, dept_name
    -> from employees as e
    -> inner join departments as d
    -> on e.dept_id=d.dept_id;
  • 查询11号员工的名字及2018年每个月工资

select name, date, basic+bonus as total
    -> from employees as e
    -> inner join salary as s
    -> on e.employee_id=s.employee_id
    -> where year(s.date)=2018 and e.employee_id=11;
  • 查询2018年每个员工的总工资

select name, sum(basic+bonus) from employees
    -> inner join salary
    -> on employees.employee_id=salary.employee_id
    -> where year(salary.date)=2018
    -> group by name;
  • 查询2018年每个员工的总工资,按工资升序排列

select name, sum(basic+bonus) as total from employees as e
    -> inner join salary as s
    -> on e.employee_id=s.employee_id
    -> where year(s.date)=2018
    -> group by name
    -> order by total;
  • 查询2018年总工资大于30万的员工,按工资降序排列

select name, sum(basic+bonus) as total from employees as e
    -> inner join salary as s
    -> on e.employee_id=s.employee_id
    -> where year(s.date)=2018
    -> group by name
    -> having total>300000
    -> order by total desc;

3)非等值连接 between

附:创建工资级别表

创建表语法:

CREATE TABLE 表名称
(
列名称1 数据类型,
列名称2 数据类型,
列名称3 数据类型,
....
)

创建工资级别表:

  • id:主键。仅作为表的行号

  • grade:工资级别,共ABCDE五类

  • low:该级别最低工资

  • high:该级别最高工资

mysql> use nsd2021;
mysql> create table wage_grade
    -> (
    -> id int,
    -> grade char(1),
    -> low int,
    -> high int,
    -> primary key (id));

向表中插入数据:

  • 语法:

INSERT INTO 表名称 VALUES (值1, 值2,....);
  • 向wage_grade表中插入五行数据:

mysql> insert into wage_grade values
    -> (1, 'A', 5000, 8000),
    -> (2, 'B', 8001, 10000),
    -> (3, 'C', 10001, 15000),
    -> (4, 'D', 15001, 20000),
    -> (5, 'E', 20001, 1000000);
  • 查询2018年12月员工基本工资级别

select employee_id, date, basic, grade
    -> from salary as s
    -> inner join wage_grade as g
    -> on s.basic between g.low and g.high
    -> where year(date)=2018 and month(date)=12;
  • 查询2018年12月员工各基本工资级别的人数

select grade, count(*)
    -> from salary as s
    -> inner join wage_grade as g
    -> on s.basic between g.low and g.high
    -> where year(date)=2018 and month(date)=12
    -> group by grade;
  • 查询2018年12月员工基本工资级别,员工需要显示姓名

select name, date, basic, grade
    -> from salary as s
    -> inner join employees as e
    -> on s.employee_id=e.employee_id
    -> inner join wage_grade
    -> on basic between low and high
    -> where date='20181210'
    -> order by grade, basic;

4)自连接

  • 要点:

    • 将一张表作为两张使用

    • 每张表起一个别名

  • 查看哪些员的生日月份与入职月份相同

select e.name, e.hire_date, em.birth_date
    -> from employees as e
    -> inner join employees as em
    -> on month(e.hire_date)=month(em.birth_date)
    -> and e.employee_id=em.employee_id;

5)外连接的概述

  • 常用于查询一个表中有,另一个表中没有的记录

  • 如果从表中有和它匹配的,则显示匹配的值

  • 如要从表中没有和它匹配的,则显示NULL

  • 外连接查询结果=内连接查询结果+主表中有而从表中没有的记录

  • 左外连接中,left join左边的是主表left outer join

  • 右外连接中,right join右边的是主表right outer join

  • 左外连接和右外连接可互换,实现相同的目标

6)左外连接

  • 语法

SELECT tb1.字段..., tb2.字段
FROM table1 AS tb1
LEFT OUTER JOIN table2 AS tb2 
ON tb1.字段=tb2.字段
  • 查询所有部门的人员以及没有员工的部门

select d.*, e.name
    -> from departments as d
    -> left outer join employees as e
    -> on d.dept_id=e.dept_id;

7)右外连接

  • 语法

SELECT tb1.字段..., tb2.字段
FROM table1 AS tb1
RIGHT OUTER JOIN table2 AS tb2 
ON tb1.字段=tb2.字段
  • 查询所有部门的人员以及没有员工的部门

select d.*, e.name
    -> from employees as e
    -> right outer join departments as d
    -> on d.dept_id=e.dept_id;

8)交叉连接 cross join

  • 返回笛卡尔积

  • 语法:

SELECT <字段名> FROM <表1> CROSS JOIN <表2> [WHERE子句]
  • 查询员工表和部门表的笛卡尔积

select name, dept_name
    -> from employees
    -> cross join departments;

附:授予管理员root可以通过任意地址访问数据库,密码是NSD2021@tedu.cn。默认情况下,root只允许在本机访问

grant all on *.* to root@'%' identified by 'NSD2021@tedu.cn';

向部门表中插入数据:

insert into departments(dept_name) values('采购部');


六.数据查询语言之子查询

1.子查询的概述

子查询就是指的在一个完整的查询语句之中,嵌套若干个不同功能的小查询,从而一起完成复杂查询的一种编写形式

子查询返回的数据分类
        单行单列:返回的是一个具体列的内容,可以理解为一个单值数据

        单行多列:返回一行数据中多个列的内容

        多行单列:返回多行记录之中同一列的内容,相当于给出了一个操作范围

        多行多列:查询返回的结果是一张临时表

子查询常出现的位置
        select之后:仅支持单行单列        

        from之后:支持多行多列

        where或having之后:支持单行单列、单行多列、多行单列

2.子查询实例

1)单行单列

查询运维部所有员工信息

分析:
        首先从departments表中查出运维部的编号

mysql> select dept_id from departments where dept_name='运维部';
+---------+
| dept_id |
+---------+
|       3 |
+---------+
1 row in set (0.00 sec)
再从employees表中查找该部门编号和运维部编号相同的员工
mysql> select *
    -> from employees
    -> where dept_id=(
    ->   select dept_id from departments where dept_name='运维部'
    -> );

查询2018年12月所有比100号员工基本工资高的工资信息

分析:

首先查到2018年12月100号员工的基本工资

mysql> select basic from salary
    -> where year(date)=2018 and month(date)=12 and employee_id=100;
+-------+
| basic |
+-------+
| 14585 |
+-------+
1 row in set (0.00 sec)
再查询2018年12月所有比100号员工基本工资高的工资信息

mysql> select * from salary
    -> where year(date)=2018 and month(date)=12 and basic>(
    ->   select basic from salary
    ->   where year(date)=2018 and month(date)=12 and employee_id=100
    -> );

查询部门员工人数比开发部人数少的部门

分析:

查询开发部部门编号

mysql> select dept_id from departments where dept_name='开发部';
+---------+
| dept_id |
+---------+
|       4 |
+---------+
1 row in set (0.00 sec)
查询开发部人数

mysql> select count(*) from employees
    -> where dept_id=(
    ->   select dept_id from departments where dept_name='开发部'
    -> );
+----------+
| count(*) |
+----------+
|       55 |
+----------+
1 row in set (0.00 sec)

分组查询各部门人数

mysql> select count(*), dept_id from employees group by dept_id;
+----------+---------+
| count(*) | dept_id |
+----------+---------+
|        8 |       1 |
|        5 |       2 |
|        6 |       3 |
|       55 |       4 |
|       12 |       5 |
|        9 |       6 |
|       35 |       7 |
|        3 |       8 |
+----------+---------+
8 rows in set (0.01 sec)

 查询部门员工人数比开发部人数少的部门

mysql> select count(*), dept_id from employees group by dept_id
    -> having count(*)<(
    ->   select count(*) from employees
    ->   where dept_id=(
    ->     select dept_id from departments where dept_name='开发部'
    ->   )
    -> );
+----------+---------+
| count(*) | dept_id |
+----------+---------+
|        8 |       1 |
|        5 |       2 |
|        6 |       3 |
|       12 |       5 |
|        9 |       6 |
|       35 |       7 |
|        3 |       8 |
+----------+---------+
7 rows in set (0.00 sec)

查询每个部门的人数

分析:

查询所有部门的信息

mysql> select d.* from departments as d;
+---------+-----------+
| dept_id | dept_name |
+---------+-----------+
|       1 | 人事部    |
|       2 | 财务部    |
|       3 | 运维部    |
|       4 | 开发部    |
|       5 | 测试部    |
|       6 | 市场部    |
|       7 | 销售部    |
|       8 | 法务部    |
|       9 | NULL      |
+---------+-----------+
9 rows in set (0.00 sec)

查询每个部门的人数

mysql> select d.*, (
    ->  select count(*) from employees as e
    ->   where d.dept_id=e.dept_id
    -> ) as amount
    -> from departments as d;
+---------+-----------+--------+
| dept_id | dept_name | amount |
+---------+-----------+--------+
|       1 | 人事部    |      8 |
|       2 | 财务部    |      5 |
|       3 | 运维部    |      6 |
|       4 | 开发部    |     55 |
|       5 | 测试部    |     12 |
|       6 | 市场部    |      9 |
|       7 | 销售部    |     35 |
|       8 | 法务部    |      3 |
|       9 | NULL      |      0 |
+---------+-----------+--------+
9 rows in set (0.00 sec)

2)多行单列


查询人事部和财务部员工信息

分析:

查询人事部和财务部编号

mysql> select dept_id from departments
    -> where dept_name in ('人事部', '财务部');
+---------+
| dept_id |
+---------+
|       1 |
|       2 |
+---------+
2 rows in set (0.00 sec)
查询部门编号是两个部门编号的员工信息

mysql> select * from employees
    -> where dept_id in (
    ->   select dept_id from departments
    ->   where dept_name in ('人事部', '财务部')
    -> );

查询人事部2018年12月所有员工工资

分析:

查询人事部部门编号

mysql> select dept_id from departments where dept_name='人事部';
+---------+
| dept_id |
+---------+
|       1 |
+---------+
1 row in set (0.00 sec)
查询人事部员的编号

mysql> select employee_id from employees
    -> where dept_id=(
    ->   select dept_id from departments where dept_name='人事部'
    -> );
+-------------+
| employee_id |
+-------------+
|           1 |
|           2 |
|           3 |
|           4 |
|           5 |
|           6 |
|           7 |
|           8 |
+-------------+
8 rows in set (0.00 sec)

查询2018年12月人事部所有员工工资

mysql> select * from salary
    -> where year(date)=2018 and month(date)=12 and employee_id in (
    ->   select employee_id from employees
    ->   where dept_id=(
    ->     select dept_id from departments where dept_name='人事部'
    ->   )
    -> );
+------+------------+-------------+-------+-------+
| id   | date       | employee_id | basic | bonus |
+------+------------+-------------+-------+-------+
| 6252 | 2018-12-10 |           1 | 17016 |  7000 |
| 6253 | 2018-12-10 |           2 | 20662 |  9000 |
| 6254 | 2018-12-10 |           3 |  9724 |  8000 |
| 6255 | 2018-12-10 |           4 | 17016 |  2000 |
| 6256 | 2018-12-10 |           5 | 17016 |  3000 |
| 6257 | 2018-12-10 |           6 | 17016 |  1000 |
| 6258 | 2018-12-10 |           7 | 23093 |  4000 |
| 6259 | 2018-12-10 |           8 | 23093 |  2000 |
+------+------------+-------------+-------+-------+
8 rows in set (0.00 sec)

3)单行多列

查找2018年12月基本工资和奖金都是最高的工资信息

分析:

查询2018年12月最高的基本工资

mysql> select max(basic) from salary
    -> where year(date)=2018 and month(date)=12;
+------------+
| max(basic) |
+------------+
|      25524 |
+------------+
1 row in set (0.00 sec)
查询2018年12月最高的奖金

mysql> select max(bonus) from salary
    -> where year(date)=2018 and month(date)=12;
+------------+
| max(bonus) |
+------------+
|      11000 |
+------------+
1 row in set (0.00 sec)
查询

mysql> select * from salary
    -> where year(date)=2018 and month(date)=12 and basic=(
    ->   select max(basic) from salary
    ->   where year(date)=2018 and month(date)=12
    -> ) and bonus=(
    ->   select max(bonus) from salary
    ->   where year(date)=2018 and month(date)=12
    -> );
+------+------------+-------------+-------+-------+
| id   | date       | employee_id | basic | bonus |
+------+------------+-------------+-------+-------+
| 6368 | 2018-12-10 |         117 | 25524 | 11000 |
+------+------------+-------------+-------+-------+
1 row in set (0.01 sec)

4)多行多列

查询3号部门及其部门内员工的编号、名字和email

分析

查询3号部门和员工的所有信息

mysql> select d.dept_name, e.*
    -> from departments as d
    -> inner join employees as e
    -> on d.dept_id=e.dept_id;
将上述结果当成一张临时表,必须为其起别名。再从该临时表中查询

mysql> select dept_id, dept_name, employee_id, name, email
    -> from (
    ->   select d.dept_name, e.*
    ->   from departments as d
    ->   inner join employees as e
    ->   on d.dept_id=e.dept_id
    -> ) as tmp_table
    -> where dept_id=3;
+---------+-----------+-------------+-----------+--------------------+
| dept_id | dept_name | employee_id | name      | email              |
+---------+-----------+-------------+-----------+--------------------+
|       3 | 运维部    |          14 | 廖娜      | liaona@tarena.com  |
|       3 | 运维部    |          15 | 窦红梅    | douhongmei@tedu.cn |
|       3 | 运维部    |          16 | 聂想      | niexiang@tedu.cn   |
|       3 | 运维部    |          17 | 陈阳      | chenyang@tedu.cn   |
|       3 | 运维部    |          18 | 戴璐      | dailu@tedu.cn      |
|       3 | 运维部    |          19 | 陈斌      | chenbin@tarena.com |
+---------+-----------+-------------+-----------+--------------------+
6 rows in set (0.00 sec)

5)分页查询

使用SELECT查询时,如果结果集数据量很大,比如几万行数据,放在一个页面显示的话数据量太大,不如分页显示,每次显示100条

要实现分页功能,实际上就是从结果集中显示第1至100条记录作为第1页,显示第101至200条记录作为第2页,以此类推

分页实际上就是从结果集中“截取”出从M开始,偏移到N的记录。这个查询可以通过LIMIT <M>, <N>子句实现

起始索引从0开始

每页显示内容速算:LIMIT (PAGE-1)*SIZE, SIZE

示例:

# 按employee_id排序,取出前5位员姓名
 

mysql> select employee_id, name from employees
    -> order by employee_id
    -> limit 0, 5;
+-------------+-----------+
| employee_id | name      |
+-------------+-----------+
|           1 | 梁伟      |
|           2 | 郭岩      |
|           3 | 李玉英    |
|           4 | 张健      |
|           5 | 郑静      |
+-------------+-----------+
5 rows in set (0.00 sec)


# 按employee_id排序,取出前15至20号员姓名

mysql> select employee_id, name from employees
    -> order by employee_id
    -> limit 15, 5;
+-------------+--------+
| employee_id | name   |
+-------------+--------+
|          16 | 聂想   |
|          17 | 陈阳   |
|          18 | 戴璐   |
|          19 | 陈斌   |
|          20 | 蒋红   |
+-------------+--------+
5 rows in set (0.00 sec)

6)联合查询UNION

作用:将多条select语句的结果,合并到一起,称之为联合操作。

语法:( ) UNION ( )

要求查询时,多个select语句的检索到的字段数量必须一致

每一条记录的各字段类型和顺序最好是一致的

UNION关键字默认去重,可以使用UNION ALL包含重复项

mysql> (select 'yes') union (select 'yes');
+-----+
| yes |
+-----+
| yes |
+-----+
1 row in set (0.00 sec)


mysql> (select 'yes') union all (select 'yes');
+-----+
| yes |
+-----+
| yes |
| yes |
+-----+
2 rows in set (0.00 sec)


例,某生产商有一张原材料表和一张商品表,需要把原材料价格和商品价格一起输出

查询1972年前或2000年后出生的员工

# 普通方法
 

mysql> select name, birth_date from employees
    -> where year(birth_date)<1972 or year(birth_date)>2000;
+-----------+------------+
| name      | birth_date |
+-----------+------------+
| 梁伟      | 1971-08-19 |
| 张建平    | 1971-11-02 |
| 窦红梅    | 1971-09-09 |
| 温兰英    | 1971-08-14 |
| 朱文      | 1971-08-15 |
| 和林      | 1971-12-10 |
+-----------+------------+
6 rows in set (0.01 sec)

# 联合查询的方法
 

mysql> (
    -> select name, birth_date from employees
    ->   where year(birth_date)<1972
    -> )
    -> union
    -> (
    ->   select name, birth_date from employees
    ->   where year(birth_date)>=2000
    -> );
+-----------+------------+
| name      | birth_date |
+-----------+------------+
| 梁伟      | 1971-08-19 |
| 张建平    | 1971-11-02 |
| 窦红梅    | 1971-09-09 |
| 温兰英    | 1971-08-14 |
| 朱文      | 1971-08-15 |
| 和林      | 1971-12-10 |
+-----------+------------+
6 rows in set (0.00 sec)

七.插入语句DDL

1.不指定列名的插入

语法格式:

        INSERT INTO 表名称 VALUES (值1, 值2,....)
需要为所有列指定值

值的顺序必须与表中列的顺序一致

示例:# 如果表中已有1号部门,则出错。因为dept_id是主键,不允许重复

mysql> insert into departments values(1, '行政部');
ERROR 1062 (23000): Duplicate entry '1' for key 'PRIMARY'

# mysql> insert into departments values(10, '行政部');
Query OK, 1 row affected (0.01 sec)

 支持多行插入

mysql> insert into employees values
    -> (134, '张三', '2019-5-10', '2000-10-12', 'zhangsan@tedu.cn', '15088772354', 9),
    -> (135, '李四', '2020-8-20', '1999-6-23', 'lisi@tedu.cn', '13323458734', 9);
Query OK, 2 rows affected (0.01 sec)
Records: 2  Duplicates: 0  Warnings: 0

2.指定列名的插入

语法格式:

INSERT INTO table_name (列1, 列2,...) VALUES (值1, 值2,....)
列和值的顺序要一致

列名先后顺序不重要

示例 :

mysql> insert into departments (dept_name, dept_id) values ('售后部', 11);
Query OK, 1 row affected (0.00 sec)
主键由于是自动增长的,也可以不指定主键的值

mysql> insert into departments (dept_name) values ('咨询部');
Query OK, 1 row affected (0.00 sec)


支持子查询

mysql> insert into employees
    -> (name, hire_date, birth_date, email, phone_number, dept_id)
    -> (
    ->   select name, hire_date, birth_date, email, phone_number, dept_id
    ->   from employees
    ->   where name='张三'
    -> );
Query OK, 1 row affected (0.00 sec)
Records: 1  Duplicates: 0  Warnings: 0

3.使用set语句

语法格式:

INSERT INTO 表名 SET 列名1=列值1, 列名2=列值2, ...
示例:

mysql> insert into departments set dept_name='采购部';
Query OK, 1 row affected (0.00 sec)

4.修改语句

修改单表记录
语法:

UPDATE 表名称 SET 列名称=新值, 列名称=新值, ... WHERE 筛选条件
示例:

# 修改人事部的名称为人力资源部

mysql> update departments set dept_name='人力资源部'
    -> where dept_name='人事部';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

修改多表记录
语法:

UPDATE 表1 AS 表1别名
INNER | LEFT | RIGHT JOIN 表2 AS 表2别名
ON 连接条件
SET 列=值, 列=值, ...
WHERE 连接条件


示例:

# 修改李四所在部门为企划部

mysql> update departments as d
    -> inner join employees as e
    -> on d.dept_id=e.dept_id
    -> set d.dept_name='企划部'
    -> where e.name='李四';

5.删除记录

删除单表记录
语法:

DELETE FROM 表名 WHERE 筛选条件;

删除的是满足条件的整行记录,而不是某个字段

示例:

# 删除重复的员工张三,只保留一个张三的信息
# 查询张三信息

mysql> select * from employees where name='张三';

# 根据员工编号删除重复的张三

mysql> delete from employees where employee_id=136;
Query OK, 1 row affected (0.00 sec)

6.删除多表记录

语法:

DELETE 表1别名, 表2别名
FROM 表1 AS 表1别名
INNER | LEFT | RIGHT JOIN 表2 AS 表2别名
ON 连接条件
WHERE 筛选条件

示例:

# 删除9号部门中所有的员工

mysql> delete e
    -> from employees as e
    -> inner join departments as d
    -> on e.dept_id=d.dept_id
    -> where d.dept_id=9;
Query OK, 2 rows affected (0.00 sec)

7.清空表

语法:

TRUNCATE TABLE 表名
TRUNCATE不支持WHERE条件

自增长列,TRUNCATE后从1开始;DELETE继续编号

TRUNCATE不能回滚,DELETE可以

效率略高于DELETE

示例:

# 清空wage_grade表
mysql> truncate table wage_grade;
Query OK, 0 rows affected (0.01 sec)

八.数据库管理

1.创建数据库

  • 语法:

CREATE DATABASE [IF NOT EXISTS] <数据库名>
[[DEFAULT] CHARACTER SET <字符集名>] 
[[DEFAULT] COLLATE <校对规则名>];
  • [ ]中的内容是可选的

  • <数据库名>:创建数据库的名称。MySQL 的数据存储区将以目录方式表示 MySQL 数据库,因此数据库名称必须符合操作系统的文件夹命名规则,不能以数字开头,尽量要有实际意义。

  • IF NOT EXISTS:在创建数据库之前进行判断,只有该数据库目前尚不存在时才能执行操作。此选项可以用来避免数据库已经存在而重复创建的错误。

  • [DEFAULT] CHARACTER SET:指定数据库的字符集。指定字符集的目的是为了避免在数据库中存储的数据出现乱码的情况。如果在创建数据库时不指定字符集,那么就使用系统的默认字符集。

  • [DEFAULT] COLLATE:指定字符集的默认校对规则。

  • MySQL 的字符集(CHARACTER)和校对规则(COLLATION)是两个不同的概念。字符集是用来定义 MySQL 存储字符串的方式,校对规则定义了比较字符串的方式。

2.修改数据库

  • 语法:

ALTER DATABASE [数据库名] { 
[ DEFAULT ] CHARACTER SET <字符集名> |
[ DEFAULT ] COLLATE <校对规则名>}
  • ALTER DATABASE 用于更改数据库的全局特性。

  • 使用 ALTER DATABASE 需要获得数据库 ALTER 权限。

  • 数据库名称可以忽略,此时语句对应于默认数据库。

  • CHARACTER SET 子句用于更改默认的数据库字符集。

3.删除数据库

  • 语法:

DROP DATABASE [ IF EXISTS ] <数据库名>
  • <数据库名>:指定要删除的数据库名。

  • IF EXISTS:用于防止当数据库不存在时发生错误。

  • DROP DATABASE:删除数据库中的所有表格并同时删除数据库。

  • 如果要使用 DROP DATABASE,需要获得数据库 DROP 权限。

九.表管理

1.关系数据库的规范化

良好的数据库设计表现在以下几方面:

  • 访问效率高

  • 减少数据冗余,节省存储空间,便于进一步扩展

  • 可以使应用程序的开发变得更容易

关系数据库的规范化理论为:关系数据库中的每一个关系都要满足一定的规范。根据满足规范的条件不同,可以分为6个等级:第一范式(1NF)、第二范式(2NF)……第五范式(5NF)。其中,NF是Normal Form的缩写。一般情况下,只要把数据规范到第三范式标准就可以满足需要了。

第一范式(1NF)

  • 在一个关系中,消除重复字段,且各字段都是最小的逻辑存储单位。即,要满足原子性。

  • 第一范式是第二和第三范式的基础,是最基本的范式。第一范式包括下列指导原则。 (1)数据组的每个属性只可以包含一个值。 (2)关系中的每个数组必须包含相同数量的值。 (3)关系中的每个数组一定不能相同。

  • 在任何一个关系数据库中,第一范式是对关系模式的基本要求,不满足第一范式的数据库就不是关系型数据库。

第二范式(2NF)

  • 第二范式是在第一范式的基础上建立起来的,即满足第二范式必先满足第一范式(1NF)。

  • 第二范式要求数据库表中的每个实体(即各个记录行)必须可以被唯一地区分。

  • 为实现区分各行记录通常需要为表设置一个“区分列”,用以存储各个实体的唯一标识。这个唯一属性列被称为主关键字或主键。

  • 第二范式要求实体的属性完完全依赖于主关键字,即不能存在仅依赖主关键字一部分的属性,如果存在,那么这个属性和主关键字的这一部分应该分离出来形成一个新的实体,新实体与原实体之间是一对多的关系。

第三范式(3NF)

  • 第三范式是在第二范式的基础上建立起来的,即满足第三范式必先满足第二范式。

  • 第三范式要求关系表不存在非关键字列对任意候选关键字列的传递函数依赖,也就是说,第三范式要求一个关系表中不包含已在其他表中包含的非主关键字信息。

  • 除主键外,其他字段必须依赖主键。

2.表管理语句

创建表

  • 语法:

CREATE TABLE 表名称
(
列名称1 数据类型 [(长度) 约束],
列名称2 数据类型 [(长度) 约束],
列名称3 数据类型 [(长度) 约束],
....
)

3.常用数据类型

数据类型描述
tinyint(m)1个字节 范围(-128~127)
smallint(m)2个字节 范围(-32768~32767)
mediumint(m)3个字节 范围(-8388608~8388607)
int(m)4个字节 范围(-2147483648~2147483647)
bigint(m)8个字节 范围(+-9.22*10的18次方)
float(m,d)单精度浮点型 8位精度(4字节) m总个数,d小数位
double(m,d)双精度浮点型 16位精度(8字节) m总个数,d小数位
decimal(m,d)m表示十进制数字总的个数,d表示小数点后面数字的位数。常用于货币
char(n)固定长度,最多255个字符
varchar(n)不固定长度,最多65535个字符
tinytext可变长度,最多255个字符
text可变长度,最多65535个字符
mediumtext可变长度,最多2的24次方-1个字符
longtext可变长度,最多2的32次方-1个字符
date日期 '2008-12-2'
time时间 '12:25:36'
datetime日期时间 '2008-12-2 22:06:44'
timestamp自动存储记录修改时间
enum(选项1, 选项2, ...)单选字符串数据类型,适合存储表单界面中的“单选值”
set(选项1,选项2, ...)多选字符串数据类型,适合存储表单界面的“多选值”。

4.示例:

# 创建数据库mydb
mysql> create database mydb default charset utf8mb4;
Query OK, 1 row affected (0.00 sec)
​
mysql> use mydb;
Database changed
# 创建部门表
mysql> create table departments (
    ->   id int,
    ->   dept_name varchar(20)
    -> );
Query OK, 0 rows affected (0.01 sec)

1)修改表

修改列名

  • 语法:

ALTER TABLE 表
CHANGE [COLUMN] 列表 数据类型
  • 示例:

mysql> alter table departments
    -> change id dept_id int;
Query OK, 0 rows affected (0.00 sec)
Records: 0  Duplicates: 0  Warnings: 0

2)修改列的类型或约束

  • 语法:

ALTER TABLE 表
MODIFY [COLUMN] 列名 类型
  • 示例:

mysql> alter table departments
    -> modify dept_name varchar(10);
Query OK, 0 rows affected (0.01 sec)
Records: 0  Duplicates: 0  Warnings: 0

3)添加新列

  • 语法:

ALTER TABLE 表
ADD [COLUMN] 列名 类型
  • 示例:

mysql> alter table departments
    -> add manager_id int;
Query OK, 0 rows affected (0.01 sec)
Records: 0  Duplicates: 0  Warnings: 0

4)删除列

  • 语法:

ALTER TABLE 表
DROP [COLUMN] 列名
  • 示例:

mysql> alter table departments
    -> drop manager_id;
Query OK, 0 rows affected (0.03 sec)
Records: 0  Duplicates: 0  Warnings: 0

5)修改表名

  • 语法:

ALTER TABLE 表名
RENAME TO 新表名
  • 示例:

mysql> alter table departments
    -> rename to depts;
Query OK, 0 rows affected (0.00 sec)

6)删除表

  • 语法:

DROP TABLE [IF EXISTS] 表名
  • 示例:

mysql> drop table depts;
Query OK, 0 rows affected (0.01 sec)

7)表复制

仅复制表结构

  • 语法:

CREATE TABLE 待创建的表名 LIKE 已有表名
  • 示例:

mysql> create table departments like nsd2021.departments;
Query OK, 0 rows affected (0.01 sec)

8)复制表结构及数据

  • 语法:

CREATE TABLE 待创建的表名
SELECT 字段, ... FROM 已有表名
  • 示例:

mysql> create table departments2
    -> select * from nsd2021.departments;
Query OK, 13 rows affected (0.01 sec)
Records: 13  Duplicates: 0  Warnings: 0

十.约束

  • 约束是一种限制,用于限制表中的数据,为了保证表中数据的准确性和可靠性。

  • 创建表时可以添加约束

  • 修改表时可以添加约束

1.约束分类

  • PRIMARY KEY:主键,用于保证该字段的值具有唯一性并且非空。

  • NOT NULL :非空,用于保证该字段的值不能为空。

  • DEFAULT:默认值,用于保证该字段有默认值。

  • UNIQUE:唯一,用于保证该字段的值具有唯一性,可以为空。

  • FOREIGN KEY:外键,用于限制两个表的关系,用于保证该字段的值必须来自于主表的关联列的值,在从表添加外键约束,用于引用主表中某些的值。

约束可应用在列级或表级。列表所有约束均支持,但外键约束没有效果;表级约束可以支持主键、唯一、外键约束。

2.约束应用

1)列级应用

  • 创建表时使用约束

mysql> create table employees(
    ->   employee_id int primary key auto_increment,       -- 主键约束
    ->   name varchar(20) not null,         -- 非空约束
    ->   gender enum('男', '女'),
    ->   email varchar(20) unique,          -- 唯一约束
    ->   nation varchar(10) default '汉族'  -- 默认值约束
    -> );
Query OK, 0 rows affected (0.00 sec)

2)表级约束

  • 创建表时使用约束

mysql> create table employees2 (
    ->   employee_id int auto_increment,
    ->   name varchar(20),
    ->   email varchar(20),
    ->   dept_id int,
    ->   primary key (employee_id),   -- 主键
    ->   unique (email),              -- 唯一
    ->   foreign key (dept_id) references departments(dept_id)  -- 外键
    -> );
 
 
# 查看约束
mysql> select * from information_schema.table_constraints  where table_name='employees2' \G

  • 自定义约束名称

mysql> create table employees3 (
    ->   employee_id int,
    ->   name varchar(20),
    ->   dept_id int,
    ->   constraint pk primary key(employee_id),   # 不报错,不生效
    ->   constraint fk_employees3_departments foreign key(dept_id) references departments(dept_id)
    -> );

3)删除约束

  • 语法:

ALTER TABLE <表名> DROP FOREIGN KEY <外键约束名>
  • 示例:

mysql> alter table employees3
    -> drop foreign key fk_employees3_departments;

例:创建员工数据库的三张表

# 创建部门表
create table departments(
    dept_id int AUTO_INCREMENT PRIMARY KEY,
    dept_name VARCHAR(10) UNIQUE
);
# 创建员工表
create table employees(
    employee_id INT auto_increment primary key,
    name VARCHAR(10) not null,
    hire_date DATE,
    birth_date DATE,
    email varchar(25) UNIQUE,
    phone_number varchar(11),
    dept_id int,
    FOREIGN KEY(dept_id) references departments(dept_id)
);
# 创建工资表
create table salary(
    id int AUTO_INCREMENT PRIMARY KEY,
    date DATE,
    employee_id int,
    basic int,
    bonus int,
    FOREIGN KEY(employee_id) references employees(employee_id)
);

十一.事务控制语言DCL

1.事务

  • 数据库事务指的是一组数据操作。

  • 主要用于处理操作量大,复杂度高的数据。

  • 在 MySQL 中只有使用了 Innodb 数据库引擎的数据库或表才支持事务。

  • 事务处理可以用来维护数据库的完整性,保证成批的 SQL 语句要么全部执行,要么全部不执行。

  • 事务用来管理 insert,update,delete 语句

2.事务必须满足的4个条件

  • 原子性(Atomicity):一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。

  • 一致性(Consistency):在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。

  • 隔离性(Isolation):数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。

  • 持久性(Durability):事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

3.事务控制语句

  • BEGIN 或 START TRANSACTION 显式地开启一个事务;

  • COMMIT 也可以使用 COMMIT WORK,不过二者是等价的。COMMIT 会提交事务,并使已对数据库进行的所有修改成为永久性的;

  • ROLLBACK 也可以使用 ROLLBACK WORK,不过二者是等价的。回滚会结束用户的事务,并撤销正在进行的所有未提交的修改;

  • SAVEPOINT identifier,SAVEPOINT 允许在事务中创建一个保存点,一个事务中可以有多个 SAVEPOINT;

  • RELEASE SAVEPOINT identifier 删除一个事务的保存点,当没有指定的保存点时,执行该语句会抛出一个异常;

  • ROLLBACK TO identifier 把事务回滚到标记点;

  • SET TRANSACTION 用来设置事务的隔离级别。InnoDB 存储引擎提供事务的隔离级别有READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ 和 SERIALIZABLE。

4.MySQL事物处理的方法

  • 用 BEGIN, ROLLBACK, COMMIT来实现

    • BEGIN开始一个事务

    • ROLLBACK事务回滚

    • COMMIT提交事务

  • 直接用 SET 来改变 MySQL 的自动提交模式

    • SET AUTOCOMMIT=0 禁止自动提交

    • SET AUTOCOMMIT=1*开启自动提交

5.事务的创建

1)隐式事务

  • 事务没有明显的开启和结束的标记。如INSERT、UPDATE、DELETE语句。

mysql> show variables like '%autocommit%';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit    | ON    |
+---------------+-------+
1 row in set (0.00 sec)

2)显式事务

  • 事务具有明显的开启和结束的标记

  • 必须先设置自动提交功能为禁用

mysql> set autocommit=0;  # 只对当前会话生效
Query OK, 0 rows affected (0.00 sec)

mysql> show variables like '%autocommit%';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit    | OFF   |
+---------------+-------+
1 row in set (0.00 sec)

6.创建事务步骤

1)开启事务

set aotocommit=0;
start transaction;    # 可选
  1. 编写事务语句:INSERT、UPDATE、DELETE语句

  2. 结束事务

commit | rollback;

7.事务示例

  • 创建银行表

mysql> use mydb;
mysql> create table bank(
    ->   id int primary key,
    ->   name varchar(20),
    ->   balance int
    -> );
Query OK, 0 rows affected (0.01 sec)
  • 插入数据

mysql> insert into bank values
    -> (1, 'kali', 10000), (2, 'yao', 10000);
Query OK, 2 rows affected (0.00 sec)
Records: 2  Duplicates: 0  Warnings: 0
  • 使用事务:正常提交

mysql> set autocommit=0;
mysql> update bank set balance=balance-1000 where name='kali';
mysql> update bank set balance=balance+1000 where name='yao';
# 此时在另一终端查看bank表,数据并未改变
mysql> commit;
  • 使用事务:回滚

mysql> set autocommit=0;
mysql> update bank set balance=balance+1000 where name='kali';
mysql> update bank set balance=balance-1000 where name='yao';
# 此时在另一终端查看bank表,数据并未改变
mysql> rollback;

十二.事务隔离

1.事务隔离要解决的问题

  • 脏读:脏读指的是读到了其他事务未提交的数据,未提交意味着这些数据可能会回滚,也就是可能最终不会存到数据库中,也就是不存在的数据。读到了并不一定最终存在的数据,这就是脏读。

  • 可重复读:可重复读指的是在一个事务内,最开始读到的数据和事务结束前的任意时刻读到的同一批数据都是一致的。通常针对数据更新(UPDATE)操作。

  • 不可重复读:对比可重复读,不可重复读指的是在同一事务内,不同的时刻读到的同一批数据可能是不一样的,可能会受到其他事务的影响,比如其他事务改了这批数据并提交了。通常针对数据更新(UPDATE)操作。

  • 幻读:幻读是针对数据插入(INSERT)操作来说的。假设事务A对某些行的内容作了更改,但是还未提交,此时事务B插入了与事务A更改前的记录相同的记录行,并且在事务A提交之前先提交了,而这时,在事务A中查询,会发现好像刚刚的更改对于某些数据未起作用,但其实是事务B刚插入进来的,让用户感觉很魔幻,感觉出现了幻觉,这就叫幻读。

2.事务隔离级别

  • 读未提交(READ UNCOMMITTED)

  • 读提交 (READ COMMITTED)

  • 可重复读 (REPEATABLE READ)

  • 串行化 (SERIALIZABLE)

从上往下,隔离强度逐渐增强,性能逐渐变差。采用哪种隔离级别要根据系统需求权衡决定,其中,可重复读是 MySQL 的默认级别。

事务隔离其实就是为了解决上面提到的脏读、不可重复读、幻读这几个问题。只有串行化的隔离级别解决了全部这 3 个问题,其他的 3 个隔离级别都有缺陷。

隔离级别出现脏读出现不可重读出现幻读
读未提交可能可能可能
读提交不可能可能可能
可重复读不可能不可能可能
串行化不可能不可能不可能

3.设置事务隔离级别

1)查看当前事务隔离级别

mysql> select @@tx_isolation;
+-----------------+
| @@tx_isolation  |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set (0.00 sec)

2)设置隔离事务级别

mysql> set session transaction isolation level read uncommitted;
Query OK, 0 rows affected (0.01 sec)

mysql> select @@tx_isolation;
+------------------+
| @@tx_isolation   |
+------------------+
| READ-UNCOMMITTED |
+------------------+
1 row in set (0.00 sec)

3)测试

# 在第一个终端上执行以下2条语句
mysql> set autocommit=0;
mysql> update bank set balance=balance+1000 where name='kali';

# 以下3条语句在第二个终端上执行
mysql> set session transaction isolation level read uncommitted;
mysql> set autocommit=0;
mysql> select * from bank;   # 此时kali账户上已经增加1000

# 回到第一个终端回滚
mysql> rollback;

# 在第2个终端上重新查询
mysql> select * from bank;   # 此时kali账户上又减少了1000

4.SAVEPOINT应用

1)基本用法

  • 使用mysql中的savepoint保存点来实现事务的部分回滚

  • 语法:

SAVEPOINT identifier
ROLLBACK [WORK] TO [SAVEPOINT] identifier
RELEASE SAVEPOINT identifier
  • 使用 SAVEPOINT identifier 来创建一个名为identifier的回滚点

  • ROLLBACK TO identifier,回滚到指定名称的SAVEPOINT,这里是identifier

  • 使用 RELEASE SAVEPOINT identifier 来释放删除保存点identifier

  • 如果当前事务具有相同名称的保存点,则将删除旧的保存点并设置一个新的保存点。

  • 如果执行START TRANSACTION,COMMIT和ROLLBACK语句,则将删除当前事务的所有保存点。

2)SAVEPOINT示例

mysql> set autocommit=0;
mysql> update bank set balance=balance+1000 where name='kali';
mysql> savepoint aaa;    # 创建保存点
mysql> select * from bank;   # kali老师账号已增加1000
mysql> update bank set balance=balance-1000 where name='yao';
mysql> select * from bank;   # yao账号已减少1000
mysql> rollback to aaa;  # 回滚到保存点aaa
mysql> select * from bank;   # kali老师账号已增加1000,yao账号未改变
mysql> exit;  # 退出
# 再连入之后查询
mysql> select * from bank;  # 因为从未执行过commit。所以查到的结果与执行事务之前查到的结果一样。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐