数据库
概念
数据库的英文单词: DataBase 简称 : DB
什么数据库?
- 存储数据的仓库
- 本质上是一个文件系统,还是以文件的方式存在服务器的电脑上的。
- 所有的关系型数据库都可以使用通用的SQL语句进行管理 DBMS DataBase Management System
常见数据库排行榜
数据库的特点:
- 持久化存储数据的。其实数据库就是一个文件系统
- 方便存储和管理数据
- 使用了统一的方式操作数据库 – SQL
MySQL数据库软件
安装
参见《MySQL基础.pdf》
卸载
去
mysql
的安装目录找到my.ini
文件复制
datadir="C:/ProgramData/MySQL/MySQL Server 5.5/Data/"
卸载
MySQL
停止window的MySQL服务。 找到“控制面板”-> “管理工具”-> “服务”,停止MySQL后台服务
卸载MySQL安装程序。找到“控制面板”-> “程序和功能”,卸载MySQL程序
删除
C:/ProgramData
目录下的MySQL
文件夹。
配置
MySQL服务启动
- 手动。
cmd
–>services.msc
打开服务的窗口- 使用管理员权限打开`cmd``
net start mysql
: 启动mysql的服务net stop mysql
: 关闭mysql服务
MySQL登录
mysql -uroot -p密码
mysql -hip -uroot -p
连接目标的密码mysql --host=ip --user=root --password=连接目标的密码
MySQL退出
net start mysql
: 启动mysql的服务net stop mysql
: 关闭mysql服务
注意:如果出现mysql’不是内部或外部命令,也不是可运行的程序
解决:在Windows平台下,需要将mysql路径添加到环境变量中,
右键点击“我的电脑”-“属性”-“高级”-“环境变量”-“系统 变量”-双击“Path”
将mysql安装目录下的bin目录添加到环境变量中
MySQL目录结构
MySQL安装目录:
basedir=G:/mysql-5.7.29-winx64
配置文件 my.ini
MySQL数据目录:
datadir=G:/mysql-5.7.29-winx64/data
SQL
SQL概述
什么是SQL?
Structured Query Language:结构化查询语言
其实就是定义了操作所有关系型数据库的规则。每一种数据库操作的方式存在不一样的地方,称为“方言”。
SQL通用语法
- SQL 语句可以
单行
或多行
书写,以分号
结尾。 - 可使用空格和缩进来增强语句的可读性。
- MySQL 数据库的 SQL 语句
不区分大小写
,关键字建议使用大写
。 - 3 种注释
- 单行注释:
-- 注释内容
(注意–后有空格) 或# 注释内容
(mysql 特有) - 多行注释:
/* 注释 */
- 单行注释:
SQL分类
DDL(Data Definition Language)
数据定义语言
用来定义数据库对象:数据库,表,列等。关键字:create
,drop
,alter
等DML(Data Manipulation Language)
数据操作语言
用来对数据库中表的数据进行增删改。关键字:insert
,delete
,update
等DQL(Data Query Language)
数据查询语言
用来查询数据库中表的记录(数据)。关键字:select
,where
等DCL(Data Control Language)
数据控制语言(了解)
用来定义数据库的访问权限和安全级别,及创建用户。关键字:GRANT
,REVOKE
等
DDL: 操作数据库、表
1. 操作数据库:CRUD
1.1 C(Create):创建
创建数据库:
create database 数据库名称;
创建数据库,判断不存在,再创建:
create database if not exists 数据库名称;
创建数据库,并指定字符集 :
create database 数据库名称 character set 字符集名;
练习: 创建db4数据库,判断是否存在,并制定字符集为
gbk
1
create database if not exists db4 character set gbk;
1.2 R(Retrieve):查询
查询所有数据库的名称:
show databases;
查询某个数据库的字符集:查询某个数据库的创建语句:
show create database 数据库名称;
示例:
1.3 U(Update):修改
修改数据库的字符集:alter database 数据库名称 character set 字符集名称;
1.4 D(Delete):删除
删除数据库:
drop database 数据库名称;
判断数据库存在,存在再删除:
drop database if exists 数据库名称;
1.5 使用数据库
查询当前正在使用的数据库名称:
select database();
使用数据库 :
use 数据库名称;
2. 操作表
2.1 C(Create):创建
语法:
1 | create table 表名( |
注意:最后一列,不需要加逗号','
数据库类型:
int
:整数类型 ; 如age int
double
:小数类型; 如score double(5,2)
date
:日期,只包含年月日:yyyy-MM-dd
datetime
:日期,包含年月日时分秒 :yyyy-MM-dd HH:mm:ss
timestamp
:时间戳类型,包含年月日时分秒:yyyy-MM-dd HH:mm:ss
注意:如果将来不给这个字段赋值,或赋值为
null
,则默认使用当前的系统时间
,来自动赋值varchar
:字符串name varchar(20)
:姓名最大20个字符注意:zhangsan 8个字符 张三 2个字符
创建表:
1 | create table student( |
复制表:create table 表名 like 被复制的表名;
2.2 R(Retrieve):查询
- 查询某个数据库中所有的表名称:
show tables;
- 查询表结构:
desc 表名;
2.3 U(Update):修改
- 修改表名:
alter table 表名 rename to 新的表名;
- 修改表的字符集:
alter table 表名 character set 字符集名称;
- 添加一列:
alter table 表名 add 列名 数据类型;
- 修改列名称类型:
alter table 表名 change 列名 新列别 新数据类型;
alter table 表名 modify 列名 新数据类型;
- 删除列:
alter table 表名 drop 列名;
2.4 D(Delete):删除
drop table 表名;
drop table if exists 表名 ;
DML:增删改表中数据
1. 添加数据:
语法
1 | INSERT [INTO] 表名 [字段名] VALUES (字段值) |
插入全部字段
所有的字段名都写出来
1
INSERT INTO 表名(字段1,字段2,字段3,...) VALUES(值1,值2,值3,...)
不写字段名
1
INSERT INTO 表名 VALUES(值1,值2,值3,...)
插入部分数据
1 | INSERT INTO 表名(字段1,字段2,...) VALUES(值1,值2,...) |
注意:
列名和值要一一对应。
如果表名后不定义列名,则默认给所有列添加值
1
insert into 表名 values(值1,值2,...值n);
除了数字类型,其他类型需要使用引号(单双都可以)引起来
2. 删除数据:
语法:
1 | delete from 表名 [where 条件] |
注意:
- 如果不加条件,则删除表中所有记录。
- 如果要删除所有记录
delete from 表名;
– 不推荐使用。有多少条记录就会执行多少次删除操作TRUNCATE TABLE 表名;
– 推荐使用,效率更高 :先删除表,然后再创建一张一样的空表
。
3. 修改数据:
语法:
1 | update 表名 set 列名1 = 值1, 列名2 = 值2,... [where 条件]; |
不带条件修改数据
1 | UPDATE 表名 SET 字段名=值; -- 修改所有的行 |
带条件修改数据
1 | UPDATE 表名 SET 字段名=值 WHERE 字段名=值; |
注意:
- 如果不加任何条件,则会将表中所有记录全部修改。
示例:
1 | -- 不带条件修改数据,将所有的性别改成女 |
DQL:查询表中的记录
1 | select * from 表名; |
语法:
1 | select 字段列表 |
创建学生表
1 | CREATE TABLE student3 ( |
基础查询
多个字段的查询
1
select 字段名1,字段名2... from 表名;
注意:如果查询所有字段,则可以使用
*
来替代字段列表。去除重复:
distinct
注意结果集完全一样的列才可以去重
计算列
- 一般可以使用四则运算计算一些列的值。(一般只会进行
数值型
的计算) ifnull(表达式1,表达式2)
:null
参与的运算,计算结果都为null
表达式1
:哪个字段需要判断是否为null
表达式2
:如果该字段为null
后的替换值。
- 一般可以使用四则运算计算一些列的值。(一般只会进行
起别名:
as
,as
也可以省略
条件查询
where子句后跟条件
运算符
>、< 、<= 、>= 、= 、<>
BETWEEN...AND
IN
( 集合)LIKE
:模糊查询占位符:
_
:单个任意字符%
:多个任意字符
IS NULL
and
或&&
or
或||
not
或!
案例
1 | -- 1.查询年龄大于20岁 |
排序查询
语法:order by 子句
1 | order by 排序字段1 排序方式1 ,排序字段2 排序方式2,... |
排序方式:
ASC
:升序,默认的。DESC
:降序。
注意:如果有多个排序条件,则当前边的条件值一样时,才会判断第二条件。
聚合函数:
将一列数据作为一个整体,进行纵向的计算。
count
:计算个数一般选择非空的列:主键
count(*)
max
:计算最大值min
:计算最小值sum
:计算和avg
:计算平均值
注意:聚合函数的计算,排除null
值。
解决方案:
1. 选择不包含非空的列进行计算
2. `IFNULL`函数
分组查询:
语法:group by 分组字段;
注意:
- 分组之后查询的字段:分组字段、聚合函数
where
和having
的区别?where
在分组之前
进行限定,如果不满足条件,则不参与分组。having
在分组之后
进行限定,如果不满足结果,则不会被查询出来- where 后不可以跟聚合函数,having可以进行聚合函数的判断。
案例:
1 | -- 按照性别分组。分别查询男、女同学的平均分 |
分页查询
语法
1 | limit 开始的索引,每页查询的条数; |
公式
1 | 开始的索引 = (当前的页码 - 1) * 每页显示的条数 |
案例
1 | -- 每页显示3条记录 |
DCL(Data Control Language)
SQL分类:
DDL
:操作数据库和表DML
:增删改表中数据DQL
:查询表中数据- DCL:管理用户,授权
DBA:数据库管理员
DCL:管理用户,授权
管理用户
1. 添加用户:
语法:
1 | CREATE USER '用户名'@'主机名' IDENTIFIED BY '密码'; |
2. 删除用户
语法:
1 | DROP USER '用户名'@'主机名'; |
3. 修改用户密码:
1 | UPDATE USER SET PASSWORD = PASSWORD('新密码') WHERE USER = '用户名'; |
mysql中忘记了root用户的密码?
cmd -- > net stop mysql
停止mysql
服务需要管理员运行该
cmd
使用无验证方式启动mysql服务:
mysqld --skip-grant-tables
打开新的cmd窗口,直接输入mysql命令,敲回车。就可以登录成功
use mysql;
update user set password = password('你的新密码') where user = 'root';
关闭两个窗口
打开任务管理器,手动结束
mysqld.exe
的进程启动mysql服务
使用新密码登录。
4. 查询用户:
1 | -- 1. 切换到mysql数据库 |
权限管理:
- 查询权限:
1 | -- 查询权限 |
授予权限:
1
2
3
4-- 授予权限
grant 权限列表 on 数据库名.表名 to '用户名'@'主机名';
-- 给张三用户授予所有权限,在任意数据库任意表上
GRANT ALL ON *.* TO 'zhangsan'@'localhost';撤销权限:
1
2
3-- 撤销权限:
revoke 权限列表 on 数据库名.表名 from '用户名'@'主机名';
REVOKE UPDATE ON db3.`account` FROM 'lisi'@'%';
约束
概念
对表中的数据进行限定,保证数据的正确性、有效性和完整性。
一个表如果添加了约束,不正确的数据将无法插入到表中。约束在创建表的时候添加比较合适。
分类:
- 主键约束:
primary key
- 非空约束:
not null
- 唯一约束:
unique
- 外键约束:
foreign key
非空约束:not null,值不能为null
什么是非空约束:
某一列不能为null。
创建表时添加
约束
1 | CREATE TABLE stu( |
创建表完后,添加
非空约束
1 | ALTER TABLE stu MODIFY NAME VARCHAR(20) NOT NULL; |
创建表完后删除
name的非空约束
1 | ALTER TABLE stu MODIFY NAME VARCHAR(20); |
唯一约束:unique,值不能重复
什么是唯一约束:
表中某一列不能出现重复的值
创建表时,添加
唯一约束
1 | CREATE TABLE stu( |
创建完后,删除
唯一约束
1 | ALTER TABLE stu DROP INDEX phone_number; |
创建表后,添加
唯一约束
1 | ALTER TABLE stu MODIFY phone_number VARCHAR(20) UNIQUE; |
主键约束:primary key
哪个字段应该作为表的主键?
通常不用业务字段作为主键,单独给每张表设计一个id的字段,把id作为主键。主键是给数据库和程序使用的,不是给最终的客户使用的。所以主键有没有含义没有关系,只要不重复,非空就行。
如:身份证,学号不建议做成主键
特点
- 含义:
非空且唯一
- 一张表只能有一个字段为主键
- 主键就是表中记录的唯一标识
疑问
如果一个字段设置了非空与唯一约束,该字段与主键的区别?
- 主键数在一个表中,只能有一个。不能出现多个主键。主键可以单列,也可以是多列。
- 自增长只能用在主键上
在创建表时,添加主键约束
1 | create table stu( |
删除主键
1 | -- 错误 alter table stu modify id int ; |
创建完表后,添加主键
1 | ALTER TABLE stu MODIFY id INT PRIMARY KEY; |
自动增长AUTO_INCREMENT
:
概念:
主键如果让我们自己添加很有可能重复,我们通常希望在每次插入新记录时,数据库自动生成主键字段的值
如果某一列是数值类型的,使用auto_increment
可以来完成值得自动增长
修改自增长的默认值起始值
默认地AUTO_INCREMENT
的开始值是1
,如果希望修改起始值,请使用下列SQL语法
创建表时指定起始值
1
2
3CREATE TABLE 表名(
列名 int primary key AUTO_INCREMENT
) AUTO_INCREMENT=起始值;示例:
1
2
3
4
5
6
7-- 指定起始值为1000
create table st4 (
id int primary key auto_increment,
name varchar(20)
) auto_increment = 1000;
insert into st4 values (null, '孔明');
select * from st4;创建好以后修改起始值
1
ALTER TABLE 表名 AUTO_INCREMENT=起始值;
示例:
1
2alter table st4 auto_increment = 2000;
insert into st4 values (null, '刘备');
在创建表时,添加主键约束,并且完成主键自增长
1 | create table stu( |
创建完后删除
自动增长
1 | ALTER TABLE stu MODIFY id INT; |
创建完之后添加
自动增长
1 | ALTER TABLE stu MODIFY id INT AUTO_INCREMENT; |
外键约束:foreign key
让表于表产生关系,从而保证数据的正确性。
单表的缺点
创建一个员工表包含如下列(id, name, age, dep_name, dep_location),id主键并自动增长,添加5条数据
1 | CREATE TABLE emp ( |
数据表的缺点:
- 数据冗余
- 后期还会出现增删改的问题
解决方案:
1 | -- 解决方案:分成2张表 |
存在的问题:
当我们在
employee
的dep_id
里面输入不存在的部门
,数据依然可以添加. 但是并没有对应的部门,实际应用中不能出现这种情况。employee
的dep_id
中的数据只能是department
表中存在的id
目标:
需要约束dep_id只能是department表中已经存在id
解决方式:
使用外键约束
什么是外键约束
什么是外键:
在从表中与主表主键对应的那一列,如:员工表中的dep_id
什么是主表:
一方,用来约束别人的表
什么是从表:
多方,被别人约束的表
创建约束的语法
在创建表时,可以添加
外键
1 | create table 表名( |
示例:
1 | -- 1) 删除副表/从表 employee |
创建表之后,删除
外键
1 | ALTER TABLE 表名 DROP FOREIGN KEY 外键名称; |
创建表之后,添加
外键
1 | ALTER TABLE 表名 ADD CONSTRAINT 外键名称 FOREIGN KEY (外键字段名称) REFERENCES 主表名称(主表列名称); |
示例
1 | -- 删除employee表的emp_depid_fk外键 |
外键的级联操作
新的问题:
1 | select * from department; |
什么是级联操作:
在修改和删除主表的主键时,同时更新或删除副表的外键值,称为级联操作
添加级联操作语法
1 | ALTER TABLE 表名 ADD CONSTRAINT 外键名称 |
分类:
级联更新:
ON UPDATE CASCADE
级联更新,只能是
创建表
的时候创建级联关系。更新主表中的主键,从表中的外键列也自动同步更新级联删除:
ON DELETE CASCADE
示例:
1 | -- 删除employee表,重新创建employee表,添加级联更新和级联删除 |
数据库的设计
多表之间的关系
分类:
一对一
(了解):如:人和身份证
分析:一个人只有一个身份证,一个身份证只能对应一个人一对多
(多对一):如:部门和员工分析:一个部门有多个员工,一个员工只能对应一个部门
多对多
:如:学生和课程分析:一个学生可以选择很多门课程,一个课程也可以被很多学生选择
实现关系:
一对多(多对一)
:如:部门和员工实现方式:
在多的一方建立外键,指向一的一方的主键
。多对多
:如:学生和课程实现方式:
多对多关系实现需要借助第三张中间表。中间表至少包含两个字段,这两个字段作为第三张表的外键,分别指向两张表的主键
一对一
(了解):如:人和身份证实现方式:
一对一关系实现,可以在任意一方添加唯一外键指向另一方的主键。
一对多关系案例
需求:
一个旅游线路分类中有多个旅游线路
分析:
具体实现:
1 | -- 创建旅游线路分类表 tab_category |
多对多关系案例
需求:
一个用户收藏多个线路,一个线路被多个用户收藏
分析:
具体实现:
1 | /*创建用户表 tab_user |
数据库设计的范式
概念:
设计数据库时,需要遵循的一些规范。要遵循后边的范式要求,必须先遵循前边的所有范式要求
设计关系数据库时,遵从不同的规范要求,设计出合理的关系型数据库,这些不同的规范要求被称为不同的范式,各种范式呈递次规范,越高的范式数据库冗余越小。
目前关系数据库有六种范式:第一范式(1NF)、第二范式(2NF)、第三范式(3NF)、巴斯-科德范式(BCNF)、第四范式(4NF)和第五范式(5NF,又称完美范式)。
分类:
1.第一范式(1NF
):
每一列都是不可分割的原子数据项, 不能是集合、数组等非原子数据项。即表中的某个列有多个值时,必须拆分为不同的列。
简而言之,第一范式每一列不可再拆分,称为原子性
。
2.第二范式(2NF
):
在
1NF
的基础上,非码属性必须完全依赖于码(在1NF基础上消除非主属性对主码的部分函数依赖
)
几个概念:
函数依赖
:A–>B, 如果通过A属性(属性组)的值,可以确定唯一B属性的值。则称B依赖于A
例如:学号–>姓名。 (学号,课程名称) –> 分数完全函数依赖
:A–>B,如果A是一个属性组,则B属性值得确定需要依赖于A属性组中所有的属性值。
例如:(学号,课程名称) –> 分数部分函数依赖
:A–>B,如果A是一个属性组,则B属性值得确定只需要依赖于A属性组中某一些值即可。
例如:(学号,课程名称) – > 姓名传递函数依赖
:A–>B, B – >C . 如果通过A属性(属性组)的值,可以确定唯一B属性的值,在通过B属性(属性组)的值可以确定唯一C属性的值,则称 C 传递函数依赖于A
例如:学号–>系名,系名–>系主任码
:如果在一张表中,一个属性或属性组,被其他所有属性所完全依赖,则称这个属性(属性组)为该表的码
例如:该表中码为:(学号,课程名称)主属性
:码属性组中的所有属性非主属性
:除过码属性组的属性
3.第三范式(3NF)
:
在2NF基础上,任何非主属性不依赖于其它非主属性(在2NF基础上消除传递依赖)
多表查询:
查询语法:
1 | select |
准备sql
1 | -- 创建部门表 |
1 | -- 创建员工表 |
笛卡尔积:
有两个集合A,B .取这两个集合的所有组成情况。
要完成多表查询,需要消除无用的数据
1 | -- 需求:查询所有的员工和所有的部门 |
如何清除笛卡尔积现象的影响
我们发现不是所有的数据组合都是有用的,只有员工表.dept_id = 部门表.id 的数据才是有用的。所以需要通过条件过滤掉没用的数据
1 | -- 设置过滤条件 Column 'id' in where clause is ambiguous |
多表查询的分类:
1. 内连接查询:
1.1 隐式内连接:
看不到
JOIN
关键字,条件使用WHERE指定, 使用where条件消除无用数据
例子:
1 | -- 查询所有员工信息和对应的部门信息 |
1.2 显式内连接:
使用INNER JOIN … ON语句, 可以省略INNER
语法:
1 | select 字段列表 from 表名1 [inner] join 表名2 on 条件s |
需求:查询唐僧的信息,显示员工id,姓名,性别,工资和所在的部门名称,我们发现需要联合2张表同时才能查询出需要的数据,使用内连接
确定查询哪些表
1
select * from emp inner join dept;
确定表连接条件,员工表.dept_id = 部门表.id 的数据才是有效的
1
2SELECT * FROM emp INNER JOIN dept ON emp.`dept_id` = dept.`id`;
SELECT * FROM emp JOIN dept ON emp.`dept_id` = dept.`id`;确定查询条件,我们查询的是唐僧的信息,员工表.name=’唐僧’
1
select * from emp e inner join dept d on e.`dept_id` = d.`id` where e.`name`='唐僧';
确定查询字段,查询唐僧的信息,显示员工id,姓名,性别,工资和所在的部门名称
1
select e.`id`,e.`name`,e.`gender`,e.`salary`,d.`name` from emp e inner join dept d on e.`dept_id` = d.`id` where e.`name`='唐僧';
我们发现写表名有点长,可以给表取别名,显示的字段名也使用别名
1
select e.`id` 编号,e.`name` 姓名,e.`gender` 性别,e.`salary` 工资,d.`name` 部门名字 from emp e inner join dept d on e.`dept_id` = d.`id` where e.`name`='唐僧';
1.3. 内连接查询步骤:
- 确定查询哪些表
- 确定表连接的条件
- 确定查询的条件
- 确定查询的字段
2. 外链接查询:
2.1 左外连接:
使用LEFT OUTER JOIN … ON,OUTER可以省略, 查询的是左表所有数据以及其交集部分。
用左边表的记录去匹配右边表的记录,如果符合条件的则显示;否则,显示NULL
可以理解为:在内连接的基础上保证左表的数据全部显示(左表是部门,右表员工)
语法:
1 | select 字段列表 from 表1 left [outer] join 表2 on 条件; |
例子:
1 | -- 在部门表中增加一个销售部 |
2.2 右外连接:
使用RIGHT OUTER JOIN … ON,OUTER可以省略,查询的是右表所有数据以及其交集部分。
用右边表的记录去匹配左边表的记录,如果符合条件的则显示;否则,显示NULL
可以理解为:在内连接的基础上保证右表的数据全部显示
语法:
1 | select 字段列表 from 表1 right [outer] join 表2 on 条件; |
例子:
1 | -- 在员工表中增加一个员工 |
1 | -- 使用内连接查询 |
3. 子查询:
什么是子查询
1 | --需求:查询开发部中有哪些员工 |
概念:
- 一个查询的结果做为另一个查询的条件
- 有查询的嵌套,内部的查询称为子查询
- 子查询要使用括号
1 | -- 查询工资最高的员工信息 |
子查询三种情况
子查询的结果是单行单列的:
子查询可以作为条件,使用运算符去判断。 运算符: > >= < <= =
1
2
3
4-- 1.查询员工工资小于平均工资的人
SELECT * FROM emp WHERE emp.salary < (SELECT AVG(salary) FROM emp);
-- 2.查询工资最高的员工
SELECT * FROM emp WHERE salary= (SELECT MAX(salary) FROM emp);子查询的结果是多行单列的:
子查询可以作为条件,使用运算符
in
来判断1
2
3
4
5
6
7
8
9
10
11
12
13-- 1.查询'财务部'和'市场部'所有的员工信息
-- 1.1 先查询开发部与财务部的id
SELECT id FROM dept WHERE NAME in ('财务部','市场部');
-- 1.2 再查询在这些部门id中有哪些员工
SELECT * FROM emp WHERE dept_id = 3 OR dept_id = 2;
-- 子查询
SELECT * FROM emp WHERE dept_id IN (SELECT id FROM dept WHERE NAME in ('财务部','市场部'));
-- 2.查询工资大于5000的员工,来自于哪些部门的名字
-- 2.1 先查询大于5000的员工所在的部门id
SELECT dept_id FROM emp WHERE salary>5000;
-- 2.2 再查询在这些部门id中部门的名字
SELECT dept.NAME FROM dept WHERE dept.id in (SELECT e.dept_id FROM emp e WHERE e.salary>5000);子查询的结果是多行多列的:
子查询结果只要是多列,肯定在FROM后面作为表,子查询可以作为一张虚拟表参与查询
子查询作为表需要取别名,否则这张表没有名称则无法访问表中的字段
1
2
3
4
5
6
7
8
9
10
11
12
-- 1.查询员工入职日期是2011-11-11日之后的员工信息和部门信息
-- 1.1 在员工表中查询2011-1-1以后入职的员工
SELECT * FROM emp WHERE emp.`join_date` > '2011-11-11'
-- 1.2 查询所有的部门信息,与上面的虚拟表中的信息组合,找出所有部门id等于的dept_id
-- 子查询
SELECT * FROM dept t1 ,(SELECT * FROM emp WHERE emp.`join_date` > '2011-11-11') t2
WHERE t1.id = t2.dept_id;
-- 普通内连接
SELECT * FROM emp t1,dept t2 WHERE t1.`dept_id` = t2.`id` AND t1.`join_date` > '2011-11-11'
或者
SELECT * FROM emp INNER JOIN dept on emp.dept_id = dept.id WHERE emp.join_date>'2011-11-11';
子查询小结
- 子查询结果只要是单列,则在WHERE后面作为条件
- 子查询结果只要是多列,则在FROM后面作为表进行二次查询
多表查询练习
数据准备
1 | -- 部门表 |
1. 查询所有员工信息。查询员工编号,员工姓名,工资,职务名称,职务描述
1 | -- 需求: |
2. 查询员工编号,员工姓名,工资,职务名称,职务描述,部门名称,部门位置
1 | -- 2.查询员工编号,员工姓名,工资,职务名称,职务描述,部门名称,部门位置 |
3. 查询员工姓名,工资,工资等级
1 | -- 3.查询员工姓名,工资,工资等级 |
4. 查询员工姓名,工资,职务名称,职务描述,部门名称,部门位置,工资等级
1 | -- 4.查询员工姓名,工资,职务名称,职务描述,部门名称,部门位置,工资等级 |
5. 查询出部门编号、部门名称、部门位置、部门人数
1 | -- 5.查询出部门编号、部门名称、部门位置、部门人数 |
6.查询所有员工的姓名及其直接上级的姓名,没有领导的员工也需要查询
1 | -- 6.查询所有员工的姓名及其直接上级的姓名,没有领导的员工也需要查询 |
事务
事务的基本介绍
什么是事务:
- 在实际的开发过程中,一个业务操作如:转账,往往是要多次访问数据库才能完成的。转账是一个用户扣钱,另一个用户加钱。如果其中有一条SQL语句出现异常,这条SQL就可能执行失败。
- 事务执行是一个整体,所有的SQL语句都必须执行成功。如果其中有1条SQL语句出现异常,则所有的SQL语句都要回滚,整个业务执行失败
- 如果一个包含多个步骤的业务操作,被事务管理,那么这些操作要么同时成功,要么同时失败。
操作:
- 开启事务:
start transaction
; - 回滚:
rollback
; - 提交:
commit
;
例子:转账的操作
数据准备
1 | CREATE TABLE account ( |
模拟操作
张三给李四转500元钱,一个转账的业务操作最少要执行下面的2条语句:
- 张三账号-500
- 李四账号+500
1 | INSERT INTO account (name, balance) VALUES ('张三',1000), ('李四',1000); |
问题:
假设当张三账号上-500元,服务器崩溃了。李四的账号并没有+500元,数据就出现问题了。我们需要保证其中一条SQL语句出现问题,整个转账就算失败。只有两条SQL都成功了转账才算成功。这个时候就需要用到事务
事务提交的两种方式:
- 自动提交:
mysql
就是自动提交的- 一条
DML(增删改)语句
会自动提交一次事务。
- 手动提交:
Oracle
数据库默认是手动提交事务- 需要先开启事务,再提交
手动提交事务的SQL语句
手动提交事务使用过程
- 执行成功的情况:
开启事务
->执行多条SQL语句
->成功提交事务
- 执行失败的情况:
开启事务
->执行多条SQL语句
->事务的回滚
案例演示1:事务提交
模拟张三给李四转500元钱(成功) 目前数据库数据如下:
执行完 1.开启事务, 2.张三账号-500, 3.李四账号+500发现数据并没有变化
执行commit提交事务之后,数据发生变化
案例演示2:事务回滚
模拟张三给李四转500元钱(失败) 目前数据库数据如下:
1 | SELECT * FROM account; |
总结:
如果事务中SQL语句没有问题,commit提交事务,会对数据库数据的数据进行改变。 如果事务中SQL语句有问题,rollback回滚事务,会回退到开启事务时的状态。
自动提交事务
MySQL默认每一条DML(增删改)语句
都是一个单独的事务,每条语句都会自动开启一个事务,语句执行完毕自动提交事务,MySQL默认开始自动提交事务
修改事务的默认提交方式:
查看事务的默认提交方式:
1
SELECT @@autocommit; -- 1 代表自动提交 0 代表手动提交
修改默认提交方式:
1
set @@autocommit = 0;
事务原理
事务开启之后, 所有的操作都会临时保存到事务日志
中, 事务日志只有在得到commit命令
才会同步到数据表中,其他任何情况都会清空事务日志(rollback,断开连接)
原理图:
事务的步骤:
- 客户端连接数据库服务器,创建连接时创建此用户临时日志文件
- 开启事务以后,所有的操作都会先写入到临时日志文件中
- 所有的查询操作从表中查询,但会经过日志文件加工后才返回
- 如果事务提交则将日志文件中的数据写到表中,否则清空日志文件。
回滚点
什么是回滚点
在某些成功的操作完成之后,后续的操作有可能成功有可能失败,但是不管成功还是失败,前面操作都已经成功,可以在当前成功的位置设置一个回滚点。可以供后续失败操作返回到该位置,而不是返回所有操作,这个点称之为回滚点。
回滚点的操作语句
具体操作:
- 将数据还原到1000
- 开启事务
- 让张三账号减3次钱,每次10块
- 设置回滚点:savepoint three_times;
- 让张三账号减4次钱,每次10块
- 回到回滚点:rollback to three_times;
- 分析执行过程
总结:
设置回滚点可以让我们在失败的时候回到回滚点,而不是回到事务开启的时
事务的四大特征:
原子性
:是不可分割的最小操作单位,要么同时成功,要么同时失败。持久性
:当事务提交或回滚后,数据库会持久化的保存数据。隔离性
:多个事务之间。相互独立。一致性
:事务操作前后,数据总量不变
事务的隔离级别(了解)
概念:
多个事务之间隔离的,相互独立的。但是如果多个事务操作同一批数据,则会引发一些问题,设置不同的隔离级别就可以解决这些问题。
存在问题:
脏读
:一个事务,读取到另一个事务中没有提交的数据脏读非常危险的,比如张三向李四购买商品,张三开启事务,向李四账号转入500块,然后打电话给李四说钱已经转了。李四一查询钱到账了,发货给张三。张三收到货后回滚事务,李四的再查看钱没了。
不可重复读(虚读)
:在同一个事务中,两次读取到的数据不一样。两次查询输出的结果不同,到底哪次是对的?不知道以哪次为准。 很多人认为这种情况就对了,无须困惑,当然是后面的为准。我们可以考虑这样一种情况,比如银行程序需要将查询结果分别输出到电脑屏幕和发短信给客户,结果在一个事务中针对不同的输出目的地进行的两次查询不一致,导致文件和屏幕中的结果不一致,银行工作人员就不知道以哪个为准了。
结论:同一个事务中为了保证多次查询数据一致,必须使用repeatable read隔离级别
幻读
:一个事务操作(DML)数据表中所有记录,另一个事务添加了一条数据,则第一个事务查询不到自己的修改。
隔离级别:
read uncommitted
:读未提交产生的问题:脏读、不可重复读、幻读
read committed
:读已提交 (Oracle)产生的问题:不可重复读、幻读
repeatable read
:可重复读 (MySQL默认)产生的问题:幻读
serializable
:串行化可以解决所有的问题
注意:隔离级别从小到大安全性越来越高,但是效率越来越低
数据库查询隔离级别:select @@tx_isolation;
数据库设置隔离级别:set global transaction isolation level 级别字符串;
演示:
1 | set global transaction isolation level read uncommitted; |