文章目录
- 前言
- 总览
- 数据定义语言DDL
-
- 数据操作语言DML
-
- 数据查询语言DQL
-
- 从简单的开始
-
- 进阶条件查询
-
- 多表查询
-
- 子查询(嵌套查询)
- 数据控制语言DCL
-
- 约束
-
- 查看表中的约束
- 主键约束(primary key)
-
- 非空约束(NOT NULL)
- 唯一约束(UNIQUE)
- 外键约束(FOREIGN KEY)
-
- 事务
-
- 范式
-
- 索引
-
- 视图
-
- 存储过程
-
- 触发器
-
- 数据库引擎
前言
- 这篇文章是我个人的学习笔记,可能无法做到面面俱到,也可能会有各种纰漏。如果任何疑惑的地方,欢迎一起讨论~
- 这个系列的文章是建立在小伙伴们很熟悉其他数据库,但是没有学习过MySQL的基础上来讲的。所以对数据库完全没有基础的小伙伴,不建议直接阅读哈。
- 如果想完整阅读,欢迎关注《MySQL基础》系列文章~
- MySQL和Oracle的SQL语法很接近,但是依然存在一定区别。这些区别俗称“方言”(很接地气哈)。下面讲的SQL都是MySQL的,小伙伴们不要搞错了哈。
总览
- SQL语句可以单行或者多行书写,但一条语句必须以英文分号结尾(Sqlyog中可以不用写分号,因为会自动给你加上)。
- 可以使用空格和缩进来增加语句的可读性。
- MySql中使用SQL不区分大小写。
- 注释方式和Oracle有些不一样:
注释语法 |
说明 |
– XXXXX |
单行注释。注意和Oracle不一样,MySQL中,–后面必须带一个空格。 |
/* XXX */ |
多行注释。这个和Java的类似。 |
# |
MySQL特有的单行注释,不需要带空格。 |
分类 |
说明 |
数据定义语言(Data Definition Language) |
用来定义数据库对象,例如数据库、表、列等。 |
数据操作语言(Data Manipulation Language) |
用来对数据库中表的记录进行更新。 |
数据查询语言(Data Query Language) |
用来查询数据库中表的记录。 |
数据控制语言(Date Control Language) |
用来定义数据库的访问权限、安全级别及创建用户。 |
数据库 |
中文名 |
说明 |
information_schema |
信息数据库 |
用于保存其他数据库的信息。 |
mysql |
核心数据库 |
主要用于保存用户和权限相关的数据。 |
performance_schema |
表现数据库 |
保存了性能相关数据,从而用于监控MySQL的性能指标。 |
sys |
系统数据库 |
保存了数据库管理员常用的信息,从而让数据库管理员了解数据库的运行情况。 |
数据类型 |
说明 |
int |
整型 |
double |
浮点型 |
bigdecimal |
对应的就是Java中的BigDecimal类 |
varchar(n) |
最长为n的可变字符型。字符串长度实际为多少,就分配多少空间来存储,因此效率较char略微降低。适合存储长度不固定的字符串。 |
char(n) |
长度为n的不可变字符型。无论字符串有多长,都分配n个字符的空间来存储。适合存储长度固定的字符串。 |
date |
日期型,只记录年月日。 |
datetime |
日期时间型,记录了年月日时分秒。 |
数据定义语言DDL
对数据库的操作
创建数据库
命令 |
说明 |
create database 数据库名; |
创建指定名称的数据库。 |
create database 数据库名 character set 字符集; |
创建指定名称的数据库,并且指定字符集。常用值:utf8、gbk |
查看及使用数据库
命令 |
说明 |
use 数据库; |
切换当前使用的数据库。 |
select database(); |
查看当前正在使用的数据库。 |
show databases; |
查看MySQL中都有哪些数据库。 |
show create database 数据库名; |
查看一个数据库的定义信息。 |
修改、删除数据库
命令 |
说明 |
alter database 数据库名 character set 字符集; |
将制定数据库的字符集修改为指定字符集。 |
drop database 数据库名; |
永久删除指定数据库。慎用!!!!! |
对数据表的操作
创建表
- 创建表的语法格式如下,最后一个字段的定义后面不需要逗号。通过default关机键子可以设置该字段的默认值。
create table 表名(
字段名 数据类型 [default 默认值],
字段名 数据类型 [default 默认值],
...
);
- 此外,还可以快速创建一个与旧表结构相同的新表的方法:
create table 新表名 like 旧表名;
查看表
命令 |
说明 |
show tables; |
查看当前数据库中的所有表的表名。 |
show create table 表名; |
查询创建该表的SQL。 |
desc 表名; |
查看该表的表结构。 |
删除表
命令 |
说明 |
drop table 表名; |
从数据库中永久删除某一张表,如果删除失败(例如表不存在)则报错。 |
drop table if exists 表名; |
从数据库中永久删除某一张表,如果表不存在,则只是警告,而不报错。 |
修改表
命令 |
说明 |
rename table 旧表名 to 新表名; |
修改数据表的表名。 |
alter table 表名 character set 字符集名; |
修改数据表的字符集。 |
alter table 表名 add 字段名 数据类型; |
在表中新增列。除此以外,还可以新增主键,下文会讲。 |
alter table 表名 modify 字段名 数据类型; |
修改表中指定列的数据类型及长度。除此以外,还可以修改字段约束,下文会讲。 |
alter table 表名 change 旧字段名 新字段名 数据类型; |
修改表中指定列的列名、数据类型及长度。 |
alter table 表名 drop 字段名; |
删除表中指定列。 |
- 当然DDL语言的内容可不只有这些,例如约束之类的语句,我将在下文再讲。
数据操作语言DML
插入数据
- 插入数据大致两种方式:
- 最常见的插入数据的语法格式如下。其中,字段名不要求按照表结构顺序,也只需要把需要插入值的字段写出来即可。
insert into 表名 (字段名1, 字段名2, ...) values (字段值1, 字段值2, ...);
- 如果说要给所有字段都插入值,则可以省略字段名。此时,字段值的顺序必须与表结构顺序对应。
insert into 表名 values (字段值1, 字段值2, ...);
- 插入数据时,有以下要点:
- 值与字段必须要对应,即个数相同且数据类型相同;
- varchar、char、date类型的值必须使用英文单引号或者双引号包裹,但是建议统一使用单引号;
- 如果要插入空值,除了忽略不写,还可以把值写为null。
- 举个栗子,比如要在student表中插入数据:id:123、name:张三、age:不填,则可以如下插入:
insert into student
(id, name, age)
values
('123', '张三', null);
insert into student
(id, name)
values
('123', '张三');
修改数据
- 修改数据语法格式如下。[]代表其中的代码可写可不写,如果不写where条件,则会对表中所有的数据进行更改。如果小伙伴们不清楚where条件的话,可以看下文。
update 表名 set 列名 = 值[, 列名 = 值, ...] [where 条件];
- 例如将student表中name为张三的数据的name改为李四、年龄改为20:
UPDATE student
SET NAME = '李四',
age = 20
WHERE NAME = '张三';
删除数据
- 删除数据语法格式如下。同理,如果不写where,则会删除掉表中的所有数据。
delete from 表名 [where 条件];
- 但即使真想删除所有数据,也不建议使用delete,因为delete是逐条删除,当数据量很大的时候,效率较低。
- 如果需要删除表中所有数据,建议使用以下语句。truncate的原理是先将表整体删除,然后再创建一个一模一样的新表,因此效率较高。
truncate table 表名;
数据查询语言DQL
- 数据查询在实际开发中是一个用得最多,同时也是业务灵活性最高的一件事情。所以数据查询需要一点一点给弄明白了。
从简单的开始
从最简单的开始
- 数据查询基本的语法格式如下。如果想要查询所有的列的话,那么列名可以替换为*。
select [distinct] 列名|表达式 [[as] 别名] [, 列名|表达式 [[as] 别名], ...] from 表名 [表别名] [where 条件];
- 那么我们可以发现,最简单的一个查询语句就是下面这样:
select * from student;
- 如果我们想给查询出来的年龄都+1的话,就可以如下编写SQL。注意了,这里的+1只是针对查询结果,并不会实际改变数据的值。表达式不光可以支持加减乘除,它几乎可以支持MySQL中所有的语句,这个东西以后遇到了,再来慢慢说。
select name, age + 1
from student;
- 此时我们会发现,查询结果表格中,表头的名字都是表达式的内容,可读性很差。如果我们想要给表头换个名字,就可以如下给列取别名,其中as可以省略。
select name as 姓名, age + 1 as 年龄
from student;
- 同样,表也可以取别名,不过给表取别名的作用不是为了可读性。给表取别名后,可以通过别名.字段名的方式来精准指定字段名。这个东西主要用于多表查询,下面会讲。
select s.name, s.age
from student s;
- 有时候我们查询的结果中,可能会有多条数据一模一样。如果想要去重一模一样的数据,我们就可以使用distinct关键字。例如,我们想要查询表中有多少种年龄,就可以如下编写:
select distinct age from student;
简单的条件查询
- 查询难就难在条件查询上面,但是东西喃还是得一点一点啃的,所以我们先从简单的开始。
- 说到查询条件,就不得不先说一下运算符,运算符是条件的重要组成部分:
运算符 |
说明 |
>、>= = <> != |
大于、大于等于 |
<、<= |
小于、小于等于 |
= |
等于,注意,和java不同哦。 |
<>、!= |
不等于,两种写法都可以。 |
BETWEEN A AND B |
介于A与B组成的闭区间的值。例如: 2000-10000之间: Between 2000 and 10000。 |
IN(值1, 值2, …) |
在集合中的值。例如: name in (‘悟空’,‘八戒’)。只要值与in中的任意一个匹配,则条件成立。这个关键字要想真正发光发热,需要与嵌套查询配合使用,这个以后再讲。 |
LIKE |
模糊查询,涉及到两个通配符:%:表示任意多个任意字符;_:表示一个任意字符。 |
IS [NOT] NULL |
为NULL(或者不为NULL)的值。注意,不能写成 = NULL。 |
AND、&& |
与,两种写法都可以。 |
OR、|| |
或,两种写法都可以。 |
NOT |
取反。 |
- 这个时候我们再来通过一些例子来认识他们。SQL的学习基本就是这样,光看概念没啥用,需要不断地接触案例,熟能生巧。
案例
select * from student where name = '李四';
select * from student where age <> 18;
- 查询年龄在18至25岁之间的学生。这个有两种写法:
select *
from student
where age between 18 and 25;
select *
from student
where age >= 18
and age <= 25;
- 查询年龄不为18和25岁的学生。这个同样有两种等价写法:
select *
from student
where age not in (18, 25);
select *
from student
where age <> 18
and age <> 25;
- 查询姓名包含“安”的学生。也就是说安字前面可以有任意多个字符,后面也可以有任意多个字符,所以写法如下:
select *
from student
where name like '%安%';
- 查询姓氏为“吴”的学生。也就是说查询第一个字符为吴的学生。
select *
from student
where name like '吴%';
select *
from student
where name like '_安&';
select *
from student;
where name is null;
进阶条件查询
查询排序
- 通过以下方式可以对查询结果进行排序。
- 其中ASC代表升序,DESC代表降序,这俩字段可以省略,此时默认按照升序排序。
- 而对多个列进行排序叫组合排序。此时会从前到后判断,当字段1的值相等时,会判断字段2,以此类推。
select * from 表名 [查询条件] [order by 字段1 [ASC|DESC] [, 字段2 [ASC|DESC]]];
- 举个栗子,比如我想将查询结果按照姓名升序排序,如果姓名相同,则按照年龄降序。
select *
from student
order by name,
age desc;
聚合函数
- 聚合函数用于计算数据表中的指定列,这里列举5个最常用的聚合函数:
聚合函数 |
说明 |
count |
统计字段值不为NULL的数据行数。 |
sum |
累加指定字段的值。空值视为0。 |
max |
计算指定字段的最大值。空值视为0。 |
min |
计算指定字段的最小值。空值视为0。 |
avg |
计算指定字段的平均值。空值视为0。 |
分组查询
- 在查询中,可以将某些字段值相同的数据归为同一组。分组一定是与聚合函数配合使用的,只分组,不使用聚合函数是没有实际意义的。聚合函数只会统计同一分组中的数据。
- 分组查询的基本语法格式如下。分组字段可以有一个至多个,而查询结果字段均应该为分组字段或者聚合函数,聚合函数中的字段可以不是分组字段。至于having是啥,下面会讲。
select 分组字段|聚合函数 [, ...] from 表名 [条件] group by 分组字段 [, ...] [having 条件];
分组的过程
- 首先会根据select … where … 来将数据完整查询出来;
- 然后观察分组字段,将分组字段值完全相同的数据作为一组;
- 取出所有分组的第一行数据,然后拼起来。如果有聚集函数,则会计算聚集函数的结果;
- 如果有having判断,则对拼起来之后的数据进行筛选,然后成为最终的查询结果。
- 由此呢就可以解释为什么说查询结果字段不应该填非分组字段了,因为MySQL只会取分完组之后第一行的值,所以这样填并无实际意义。
通过例子来说明
- 查询学生表中所有的班级。正常来说会想到使用distinct,但是这里为了学习分组查询,所以用分组来实现。
select class from student group by class;
SELECT
class, COUNT(*), AVG(age)
FROM
student
GROUP BY class;
- 查询学生表中,每个班级年龄大于等于20岁的学生人数。
SELECT
class, COUNT(*), AVG(age)
FROM
student
WHERE age >= 20
GROUP BY class;
- 查询学生表中,每个平均年龄大于20岁的班级的学生数量和平均年龄。
SELECT
class, COUNT(*), AVG(age)
FROM
student
GROUP BY class
HAVING AVG(age) > 20;
- 这个时候,就不得不讲一下having是什么了。having和where类似,都是作为筛选条件的关键字,但是二者有以下区别:
- where是在分组前进行筛选,having是在分组后进行筛选;
- where后面不能写聚合函数,having后面可以写聚合函数,而且通常也只写聚合函数。
指定查询行数
- 通过limit关键字可以指定查询哪几行数据。注意了,limit限制的是查询结果出来后,数据的行数,也就是说会受到排序影响。
- 语法如下。起始行代表从哪一行开始,行数代表查询的数据的行数。
select * from 表名 [条件] limit 起始行, 行数;
- 例如查询学生表中,年龄第2小和第3小的学生。也就是说升序排序好的结果中,取第2、3行。
select *
from student
order by age
limit 1,2;
多表查询
- 对于需要同时查询多个表中的数据,并且一并展示的时候,需要用到多表查询。
- MySQL和Oracle的多表查询一样,分为内连接、左外连接、右外连接。
- 内连接查询出来的数据是左表与右表的交集;
- 左外连接查询出来的数据是左表与右表的交集并上左表;
- 右外连接查询出来的数据是左表与右表的交集并上右表。
内连接
- 内连接分为隐式内连接和显式内连接。
- 内连接是将两表数据做笛卡尔积查询,然后再根据连接条件来筛选数据。
- 隐式内连接语法如下。如果不写连接条件,则查询出来的数据会是两表的笛卡尔积,没有实际意义,所以连接条件几乎是必备的。
SELECT 字段名 FROM 表1, 表2 [, 表3, ...] [WHERE 连接条件];
SELECT c.name 班级, COUNT(s.id) 学生数
FROM student s, class c
WHERE s.class = c.id
GROUP BY c.name;
- 显示内连接语法如下。inner可写可不写,效果一样。
SELECT 字段列表 FROM 左表 [INNER] JOIN 右表 ON 连接条件 [where 查询条件];
- 例如还是上面的查询,用显示内连接写法如下。效果是一样的,只是显示内连接可读性更强,可以将查询条件和连接条件分开来。
SELECT c.name 班级, COUNT(s.id) 学生数
FROM student s JOIN class c ON s.class = c.id
GROUP BY c.name;
外连接
- 左外连接以左表为基准,匹配右边表中的数据。如果匹配的上,就展示匹配到的数据。如果匹配不到,左表中的数据正常展示,右边的展示为null。
- 左外连接语法如下。outer可以省略。
SELECT 字段名 FROM 左表 LEFT [OUTER] JOIN 右表 ON 连接条件 [where 查询条件];
- 举个栗子,查询所有班级名称以及班级下的学生姓名。此时,及时某个班级下面没有学生,依然会查询出改班级名称,只是学生姓名为null。
SELECT c.name, s.name
FROM class c LEFT JOIN student s ON s.class = c.id;
子查询(嵌套查询)
- 子查询就是把一个select的结果作为另一个select的一部分。
- 子查询必须被小括号包裹。
- 子查询非常灵活,但是最常用的基本就三种:
- 查询出一列一行数据,作为where的条件。例如查询大于平均年龄的学生:
SELECT *
FROM student
WHERE age > (SELECT AVG(age) FROM student);
- 查询出一列多行数据,作为in的集合。例如查询哪些班级有年龄为19、20岁的学生。此时的思路是:先查询年龄为19、20岁的学生的班级id的并集,然后根据班级id查询班级名称。
select name
from class
where id in (select distinct class from student where age in (19,20));
- 查询出多列多行数据,作为from的一张表。此时必须给该“表”取别名,否则无法访问其字段。例如我还是如查询哪些班级有年龄为19、20岁的学生。同一种需求往往有多种实现方法,所以需要去寻找最优解(很明显,下面这个是个非常糟糕的解哈哈哈)。
SELECT c.name
FROM class c JOIN (SELECT DISTINCT class
FROM student
WHERE age IN (19, 20)) s
ON s.class = c.id;
数据控制语言DCL
创建和删除用户
- MySQL的用户模块相较于Oracle简化了许多,并且没有Oracle中“角色”相关的东西。
- 创建数据库的时候,默认会创建root用户,该用户拥有最高权限。然而实际使用中,往往还需要很多拥有不同权限的用户,此时就需要创建用户。
- 创建用户语法如下。
CREATE USER '用户名'@'主机名' IDENTIFIED BY '密码';
- 其中主机名是MySQL的特色,其可以规定可以使用该用户登录的客户端IP,通配符为%。* MySQL通过主机名和用户名来唯一确定一个用户。
- 例如我希望以192.108开头的主机可以用test用户登录,则可以如下创建:
create user 'test'@'192.108.%' identified by '123';
- 如果我希望所用ip都可以登录test用户,则如下创建:
create user 'test'@'%' identified by '123';
create user 'test'@'localhost' identified by '123';
DROP USER '用户名'@'主机名';
用户授权和回收
- MySQL的常见权限有以下。还有很多比较少用的权限,就不一一列举了。
权限 |
说明 |
All |
数据库所有权限。 |
Create |
创建数据库和表的权限。 |
Alter |
修改表结构的权限。 |
Drop |
删除数据库、表、视图的权限。还包括truncate权限。 |
Usage |
所有用户都拥有的默认权限。仅仅拥有连接数据库的权限。 |
Update |
修改表中数据的权限。 |
Select |
查询表中数据的权限。 |
Insert |
插入数据的权限。 |
Delete |
删除数据的权限。 |
GRANT 权限1, 权限2, ... ON 数据库名.表名 TO '用户名'@'主机名';
- 例如给从localhost登录的test用户赋予所有数据库、所有表的所有权限:
grant all on *.* to 'test'@'localhost';
revoke 权限1, ... on 数据库名.表名 from '用户名'@'主机名';
查看用户和权限
- MySQL的用户信息均存在mysql数据库的user表中。因此只需要将数据库切换为mysql,然后查询user表即可。
- 通过以下语句可以查看用户拥有的权限,以授权语句的形式呈现。查看用户权限无需切换数据库。
SHOW GRANTS FOR '用户名'@'主机名';
约束
- 约束可谓是数据库的一大精髓。通过约束可以对表中的数据进行进一步的限制,从而保证数据的正确性、有效性、完整性。违反约束的数据将无法插入到表中。
查看表中的约束
- 怎么查看表中的约束和约束名呢?还是使用我们熟悉的
show create table 表名;
即可。
CREATE TABLE `test` (
`id` int(11) DEFAULT NULL,
`name` varchar(50) DEFAULT NULL,
UNIQUE KEY `id` (`id`,`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
- 可以看到unique key后面有个’id’,那么就说明这个约束的约束名是id。创建约束的时候可以不指定约束名,MySQL会自动按照一定规则生成。
主键约束(primary key)
- 主键用来唯一表示数据表中的一条数据。当一个或多个字段被设置为主键后,这些字段的值不能为空且不能重复,也就是说通过这些字段要能够唯一确认出一条数据。
- 一张表只能有一个主键。
创建和删除方式
- 主键主要有下面三种创建方法。肯定有小伙伴要问auto_increment是啥子,下面马上就讲~
create table student(
id int primary key [auto_increment],
name varchar(50)
) [auto_increment = 数值];
create table student(
id int,
name varchar(50),
primary key(id, name)
);
alter table student add primary key(id);
- 删除主键主要就一种写法。因为一张表只有一个主键,所以并不需要指定字段名。
alter table 表名 drop primary key;
主键自增
- auto_increment是设置主键自增长的关键字。
- 因为主键不能重复且非空,所以通常都是按照一定规律自增的。为了方便实现自增,MySQL提供了这个关键字。
- 这个关键字只能用于修饰int类型的主键,并且可以在建表语句最后指定自增的初始值,不设置则默认为1。
- 当使用delete删除数据的时候,无论删掉多少条数据,都不会对自增主键的计数造成影响。而如果使用truncate删除数据,则会将主键自增计数重置为1(注意,不是重置为建表时设置的初始值哦)。
非空约束(NOT NULL)
- 非空约束很好理解,就是说指定该字段不能为空值(NULL)。
- 一张表可以有多个非空约束。
- 主要有两种定义方式。非空约束不能像主键那样在末尾一次性指定多个字段。
create table student(
id int NOT NULL
);
alter table student modify id int not null;
- 删除非空约束主要也是使用modify,定义为可为可空即可,null关键字可写可不写:
alter table student modify id int [null];
唯一约束(UNIQUE)
- 唯一约束也是很好理解的,即指定字段的值不能够重复,但是对于NULL值不做唯一限制。
- 一张表可以有多个唯一约束。
- 和主键类似,主要有三种定义方式:
create table student(
id int unique,
name varchar(50) unique
);
create table student(
id int,
name varchar(50),
unique [约束名] (id, name)
);
alter table student add unique(id, name);
- 删除唯一约束的方法如下。这其实是删除索引的方法,但是为什么删除唯一约束用的语句是删除索引呢?下面讲索引的时候会讲。
alter table 表名 drop index 约束名;
外键约束(FOREIGN KEY)
- 外键约束适用于多表关联的情况。外键指的是在从表中与主表的主键对应的字段。
- 建立外键后,从表该字段的值必须在主表中存在。使用外键约束可以让两张表之间产生一个强制的对应关系,从而保证子表能够正确引用数据。
外键的创建和删除
- 创建新表时,可以在建表语句末尾创建外键。其中constraint关键字和外键名可以省略。on delete cascade是级联删除的意思,级联删除可以实现删除主表数据的同时,也删除从表相应数据。
create table 表名(
各种字段...,
[CONSTRAINT] [外键名] FOREIGN KEY(外键字段) REFERENCES 主表名(主键字段) [ON DELETE CASCADE]
);
ALTER TABLE 从表名
ADD [CONSTRAINT] [外键名] FOREIGN KEY(外键字段)
REFERENCES 主表(主键字段) [ON DELETE CASCADE];
- 删除外键。外键名还是使用
show create table 表名;
来查看。
alter table 从表名 drop foreign key 外键名;
外键使用时的注意事项
- 从表外键字段类型必须与主表主键字段类型完全一致。
- 添加数据时, 必须先添加主表中的数据,才能在子表中引用相应数据。
- 删除数据时,必须先删除从表中的数据,否则主表中数据无法删除(级联删除除外)。
事务
- MySQL的事务与Oracle存在一定差异。如果不知道啥是事务的小伙伴,建议自己查阅一下资料哦。
事务的提交
- MySQL有两种提交事务的方式:手动提交事务和自动提交事务。
- 手动提交事务类似于Oracle的事务提交,流程如下:
start transaction;
...
commit;
- 自动提交事务是MySQL的默认方式:每一条DML语句都视为一个单独的事务。
- 自动提交事务是由MySQL的一个系统参数控制的,可以使用下面的语句查询该参数。可以看到该参数的value默认是ON。
SHOW VARIABLES LIKE 'autocommit';
- 如果想要关闭自动提交事务,则可以通过下面的语句实现。同理,开启也是使用该语句。
SET @@autocommit = off;
- 如果关闭了自动提交事务,则默认的事务提交方式就和Oracle一样了:
...
commit;
事务的四大特性
- 虽然我相信小伙伴们都知道,但是我还是说一下哇哈哈哈~
特性 |
含义 |
原子性 |
每个事务都是一个整体,不可再拆分,事务中所有的SQL要么都执行成功,要么都失败。 |
一致性 |
数据在事务执行前后的状态必须保持一直。例如转账业务,事务开始时所有数据都是转账前的状态,事务提交后所有数据都是转账成功后的状态。 |
隔离性 |
事务与事务之间不能产生影响。 |
持久性 |
一旦事务提交,数据应该永久被保存下来。 |
事务隔离级别
数据并发访问
- 一个数据库可能拥有多个客户端连接,这些连接可以并发方式访问数据库。
- 当同一段数据被多个事务同时访问,如果不采取隔离措施,就会导致以下各种问题:
问题 |
说明 |
脏读 |
一个事务读取到另一个事务中尚未提交的数据。这是由于一个事务执行查询之前,另一个事务修改了数据导致。 |
不可重复读 |
一个事务中两次进行相同的查询,但是获取到的数据不同。这是由于一个事务两次读取数据之间,另一个事务修改并提交了数据导致。 |
幻读 |
一个事务中,查询到一条数据不存在,但插入时却发生数据重复的问题。这是由于一个事务在查询之后,另一个事务插入并提交了数据导致的。 |
隔离级别
- 为了解决数据并发访问导致的问题,数据库规定了四种隔离级别。随着隔离能力的加强,数据库的效率会不断下降。所以根据业务需求选取合适的隔离级别才是最优的。
级别 |
名字 |
脏读 |
不可重复读 |
幻读 |
数据库使用情况 |
1 |
读未提交(read uncommitted) |
可能发生 |
可能发生 |
可能发生 |
|
2 |
读已提交(read committed) |
不会发生 |
可能发生 |
可能发生 |
Oracle和SQLServer默认使用该级别 |
3 |
可重复读(repeatable read) |
不会发生 |
不会发生 |
可能发生 |
MySQL默认使用该级别 |
4 |
序列化(serializable) |
不会发生 |
不会发生 |
不会发生 |
|
- 读未提交级别下,select语句始终会读取最新的数据值,从而可能读取到其他事务修改过,但还未提交的值。
- 读已提交通过给事务和数据加上版本号,从而通过识别版本号来避免脏读的问题。
- 可重复读也是通过版本号来解决,只是对于版本号的处理逻辑不同。
- 序列化通过在事务中给所有查询出来的数据加锁,让其他事务无法修改数据,从而解决幻读问题。
MySQL操作隔离级别
select @@tx_isolation;
- 通过下列语句更改数据库的隔离级别。注意,更改隔离级别后,得在新的连接中才会生效,当前连接不会发生变化。
set global transaction isolation level 级别名称;
- 级别名称可以填写以下四种:
- read uncommitted:读未提交;
- read committed:读已提交;
- repeatable read:可重复读;
- serializable:可序列化。
范式
- 为了建立冗余较小、结构合理的数据库,就需要遵循范式。范式要求从低到高分为:第一范式(1NF)、第二范式(2NF)、第三范式(3NF)、BCNF、第四范式、第五范式。实际生产中最常用的应该就是第三范式了。
- 范式是数据库设计的内容。数据库设计这个东西,主要还是靠经验的积累,所以这里就大致介绍一下范式就行。
- 真正要细讲范式的话,就需要先弄明白关系表中的码、主属性、完全函数依赖、部分函数依赖和传递函数依赖是什么东西。要讲这些的话内容就太多了,范式不是这篇文章的重点。
第一范式
- 第一范式规定了每一列应该具有原子性。例如下表就是一个不满足第一范式的例子:
第二范式
- 正第一范式的基础上,要求一张表只能表达一件事情。例如下表就不符合2NF:
id |
学生姓名 |
年龄 |
课程 |
1 |
帅哥 |
12 |
java |
2 |
帅哥 |
12 |
MySQL |
3 |
郝帅 |
13 |
java |
- 学生信息和课程信息应该是一个多对多的关系,应该参照上文所讲,将表格拆分为3个表(学生表、课程表、学生课程关系表)。
第三范式
- 在第二范式的基础上,要求非主键不能对主键有传递依赖。
- 例如下表就存在这个问题。学号是主键,学号可以决定班级,班级可以决定班主任,产生了传递依赖。所以班主任这个字段应该拆分到别的表中。聪明的小伙伴们一定已经知道怎么分了。
学号 |
姓名 |
班级 |
班主任 |
1 |
帅哥 |
一班 |
苏翻开 |
2 |
臻美丽 |
一班 |
苏翻开 |
3 |
郝帅 |
二班 |
蒋一天 |
- 至于后面的BCNF范式,不弄懂码、完全函数依赖就真不好讲了,有兴趣的小伙伴们可以自行去了解一下。
反三范式
- 在实际生产中,如果严格按照第三范式来设计数据库,虽然节省了大量空间,但是有时候会造成数据查询的不方便。
- 反三范式指的是通过增加冗余字段来提高数据库的读性能。虽然浪费了存储空间、增加了写入操作的复杂度,但是合理的设计下,能够节省大量查询时间。
- 实际生产中,数据库的设计往往是在三范式基础上,增加一定的反三范式。
索引
- 索引用于提升表中字段的访问速度(例如作为查询条件、分组字段时)。
- 索引既有优点,也有缺点,所以索引不能滥用。
- MySQL中,索引文件是单独保存在,和表文件同目录。表文件后缀为frm,索引文件后缀为ibd。
- 最常见的三种索引是:主键索引、唯一索引和普通索引。
- 主键索引就是主键,对!就是约束中讲的那个主键~所以我就不讲了。下面来说说唯一索引和普通索引。
索引的优缺点
- 索引就一个特别明显的优点:
- MySQL的索引采用的数据结构是B+树,所以可以显著提高访问速度。
- 但索引的缺点也很突出:
- 索引需要单独保存在磁盘上,所以索引大幅度增加了空间的开销。
- 此外,因为在插入数据后需要重新维护索引,当数据量大、写入操作频繁时,索引会严重降低写入性能。
索引的应用场景
- 索引不能滥用,因为其是一把双刃剑,那索引应该用在什么地方呢?
- 索引的添加一定要在创建表时就添加索引。因为当数据量特别大的时候,加索引效率非常低,并且可能让数据库崩溃;
- 十分频繁地作为where条件的字段应当加索引;
- 主键会强制加上索引;
- 如果表关联十分频繁,应该适当为外键加上索引;
- 经常用于排序的列可以加上索引。因为索引已经排好序了,对索引列进行排序,速度会非常快。
索引的使用
唯一索引
- 说到唯一索引,可能小伙伴们会想到唯一约束。他们之间是什么关系呢?
- 从理论上来讲,二者是不同的。
- 创建唯一索引的时候,会自动创建唯一约束。删除唯一索引的时候,唯一约束会自动删除。
- 创建唯一约束,并不会自动创建唯一索引。
- 由以上两点,MySQL在就统一使用删除索引的语句来删除唯一索引和唯一约束。
- 但从实际效果来讲,二者是完全一样的,都是限制一列中的数据不能重复。
- 唯一索引的创建方式和唯一约束不太一样。如果只想创建唯一索引,那么只能在建表完成后如下创建:
create unique index 索引名 on 表名(列名);
普通索引
- 普通索引的作用就很直白了,仅仅用于提升访问速度,没有别的特殊含义。
- 普通索引有两种创建方式,效果都是一样的:
create index 索引名 on 表名(列名);
ALTER TABLE 表名 ADD INDEX 索引名 (列名);
删除索引
ALTER TABLE 表名 DROP INDEX 索引名;
视图
- 视图是一种建立在已有表的基础上的虚拟表。
- 可以将视图理解为存储起来的SELECT语句。每一次访问视图,都会执行一遍视图对应的查询语句。
- 视图只能被查询,无法进行新增和修改操作。
- 当被引用的表被删除后,再查询视图会报错。
视图的作用
- 视图主要有以下两个作用
- 权限控制。例如只想给用户提供某些表的某些字段的查询权限。则可以通过视图很方便地实现这个需求。
- 简化复杂的、高频的多表查询。本身就是一条查询SQL,可以将一次复杂的查询构建成视图,以后只需要查询视图即可。
视图的创建和删除
- 创建语法格式如下。列名列表可写可不写,就是起到一个别名的作用,不写时就按照查询结果的列名来。
create view 视图名 [列名列表]
as
select语句;
drop view 视图名;
- 至于视图的查询,和普通表没有任何区别,我就不在这里赘述了。
存储过程
- 存储过程是数据库版的程序代码。它是一种可以完成指定逻辑功能的SQL语句集,并且支持传入参数。存储过程经编译后保存在数据库中,用户可以直接调用存储过程。
存储过程优缺点
- 优点:
- 和Java代码一样,存储过程一旦调试完成后,只要业务需求不发生变化,就可以一直稳定运行。
- 存储过程可以减少业务系统与数据库的交互,降低耦合;
- 如果应用程序的某段逻辑需要频繁与数据库交互,则将其更改为存储过程,可以减少交互次数,大幅提高数据在服务器与数据库之间传输的时间消耗。
- 缺点:
- 存储过程的调试和维护十分困难,如果公司业务需求变化频繁,则不适合存储过程;
- 存储过程移植十分困难。在数据库集群环境下,保证各个库之间存储过程的一致十分困难。
存储过程的使用
- 创建存储过程的语法如下。
- delimiter用于定义界定符,界定符适用于确定存储过程逻辑开始和结束的标志。例如这里就将界定符定义为了“$$”,小伙伴们写的时候也可以自由地定义为别的符号。
- 存储过程可以声明入参,声明后可以直接使用,就和java的变量类似。参数值在调用存储过程的时候传入。
- 存储过程可以声明返回值,返回值通过
SET 出参名 = 值;
来赋值,通过在调用时声明接收变量来接收返回值。随后可以对该变量进行任何操作。
DELIMITER $$
CREATE PROCEDURE 存储过程名称([IN 入参名 类型 [, IN 入参名 类型, ...]] [, OUT 出参名 类型 [, OUT 出参名 类型, ...]])
BEGIN
END $$
CALL 存储过程名(入参值..., @接收返回值的变量);
drop procedure 存储过程名;
- 举个栗子。先创建一个存储过程。CONCAT_WS是MySQL提供的拼接字符串的函数。
DROP PROCEDURE test;
DELIMITER ff
CREATE PROCEDURE test(IN para1 INT, IN para2 VARCHAR(50), OUT o1 INT, OUT o2 VARCHAR(52))
BEGIN
SET o1 = (SELECT COUNT(*) FROM student WHERE age = para1);
SET o2 = CONCAT_WS('','%',para2,'%');
END ff
- 然后调用该存储过程。存储过程的创建和调用不能同时执行。
CALL test(19, '王', @age, @name);
SELECT @age, @name;
- 结果如下。当然根据表中内容不同,@age的值也会不同。
触发器
- 触发器可以实现执行一条sql语句的时,自动去触发执行其他的sql语句。
- 但是触发器和存储过程类似,难以维护,所以存储过程、触发器通常都很少用,转而用程序代码实现。
- 触发器的逻辑可以用一句话概括:某张表在执行某个操作的前后额外执行某种操作。
触发器的使用
- 创建触发器的语法如下。
- for each row是固定的,没有别的写法,意思是MySQL的触发器会对表中每一行数据生效。
- before和after表示是在事件之前还是之后执行触发器逻辑。
- insert、update、delete分别代表插入、更新、删除事件。
- 触发器没法获取触发事件时的相应参数,所以触发器的作用比较有限。
delimiter $
CREATE TRIGGER 触发器名称
before|after(insert|update|delete) on 表名
for each row
begin
end $
DROP TRIGGER 触发器名;
- 因为触发器的使用确实少,并且语法和存储过程差不太多,所以就不举例了。
数据库引擎
- 用非常简单的几句话来介绍一下数据库引擎(因为我也不太懂哈哈哈尬)。
- 数据库引擎是真正与磁盘进行交互的东西。当执行sql语句的时候,数据库引擎就会解析这些语句,来进行相应的处理。
- 数据库引擎主要使用的有InnoDB和MyISAM。MySQL在5.1及之前的版本都默认使用的是MyISAM,后来的版本默认为InnoDB。
- MySQL中,每一张表都可以单独指定数据库引擎。
- 这两个引擎有什么区别呢?
- MyISAM最大的特点就是查询效率很高,但是缺失很多功能。例如不支持事务、外键、主键自增长。并且该引擎采用的表级锁,没有行级锁。所以适合用在查询非常多、修改非常少的表中。
- InnoDB是最泛用的引擎。虽然他的查询效率没有MyISAM高,但是能够支持所有MyISAM不支持的功能。并且InnoDB默认使用的行级锁。所以适合用在普通的表中。