MySQL基础(一)——MySQL的基本概念和使用

版本 说明 发布日期
1.0 发布文章第一版

前言

  • 这篇文章是我个人的学习笔记,可能无法做到面面俱到,也可能会有各种纰漏。如果任何疑惑的地方,欢迎一起讨论~
  • 这个系列的文章是建立在小伙伴们很熟悉其他数据库,但是没有学习过MySQL的基础上来讲的。所以对数据库完全没有基础的小伙伴,不建议直接阅读哈。
  • 如果想完整阅读,欢迎关注《MySQL基础》系列文章~
  • MySQL和Oracle的SQL语法很接近,但是依然存在一定区别。这些区别俗称“方言”(很接地气哈)。下面讲的SQL都是MySQL的,小伙伴们不要搞错了哈。

总览

  • SQL语句可以单行或者多行书写,但一条语句必须以英文分号结尾(Sqlyog中可以不用写分号,因为会自动给你加上)。
  • 可以使用空格和缩进来增加语句的可读性。
  • MySql中使用SQL不区分大小写。
  • 注释方式和Oracle有些不一样:
注释语法 说明
– XXXXX 单行注释。注意和Oracle不一样,MySQL中,–后面必须带一个空格。
/* XXX */ 多行注释。这个和Java的类似。
# MySQL特有的单行注释,不需要带空格。
  • SQL类别
分类 说明
数据定义语言(Data Definition Language) 用来定义数据库对象,例如数据库、表、列等。
数据操作语言(Data Manipulation Language) 用来对数据库中表的记录进行更新。
数据查询语言(Data Query Language) 用来查询数据库中表的记录。
数据控制语言(Date Control Language) 用来定义数据库的访问权限、安全级别及创建用户。
  • MySQL有4个自带数据库:
数据库 中文名 说明
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 条件];
  • 那么我们可以发现,最简单的一个查询语句就是下面这样:
#查询student表中所有的数据
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的学习基本就是这样,光看概念没啥用,需要不断地接触案例,熟能生巧。

案例

  • 查询name为李四的学生。
select * from student where name = '李四';
  • 查询年龄不等于18的学生。
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。
  • 那聚合函数到底怎么用呢?我觉得要用文字描述挺麻烦,直接举几个例子一下就明白了。
    • 查询age大于18的学生中,name不为空的学生数量。因为count会自动忽略空值,所以直接如下编写即可。由此可见,聚合函数是在数据查询结果出来之后,再进行处理的。也就是先把所有age大于18的数据查出来,然后在count计数。
    select count(name)
      from student
     where age > 18;
    
    • 统计学生表中数据条数。常见的写法有两种,效果是一模一样的。这种需求下,就不推荐使用具体的列名了,因为要防止NULL值漏算。
    select count(*) from student;
    select count(1) from student;
    
    • 其余的几个聚合函数也是类似的,就不一一举例了。

分组查询

  • 在查询中,可以将某些字段值相同的数据归为同一组。分组一定是与聚合函数配合使用的,只分组,不使用聚合函数是没有实际意义的。聚合函数只会统计同一分组中的数据。
  • 分组查询的基本语法格式如下。分组字段可以有一个至多个,而查询结果字段均应该为分组字段或者聚合函数,聚合函数中的字段可以不是分组字段。至于having是啥,下面会讲。
select 分组字段|聚合函数 [, ...] from 表名 [条件] group by 分组字段 [, ...] [having 条件];

分组的过程

  1. 首先会根据select … where … 来将数据完整查询出来;
  2. 然后观察分组字段,将分组字段值完全相同的数据作为一组;
  3. 取出所有分组的第一行数据,然后拼起来。如果有聚集函数,则会计算聚集函数的结果;
  4. 如果有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类似,都是作为筛选条件的关键字,但是二者有以下区别:
    1. where是在分组前进行筛选,having是在分组后进行筛选;
    2. where后面不能写聚合函数,having后面可以写聚合函数,而且通常也只写聚合函数。

指定查询行数

  • 通过limit关键字可以指定查询哪几行数据。注意了,limit限制的是查询结果出来后,数据的行数,也就是说会受到排序影响。
  • 语法如下。起始行代表从哪一行开始,行数代表查询的数据的行数。
select * from 表名 [条件] limit 起始行, 行数;
  • 例如查询学生表中,年龄第2小和第3小的学生。也就是说升序排序好的结果中,取第2、3行。
select *
  from student
 order by age
 limit 1,2;

多表查询

  • 对于需要同时查询多个表中的数据,并且一并展示的时候,需要用到多表查询。
  • MySQL和Oracle的多表查询一样,分为内连接、左外连接、右外连接。
    • 内连接查询出来的数据是左表与右表的交集;
    • 左外连接查询出来的数据是左表与右表的交集并上左表;
    • 右外连接查询出来的数据是左表与右表的交集并上右表。

内连接

  • 内连接分为隐式内连接和显式内连接。
  • 内连接是将两表数据做笛卡尔积查询,然后再根据连接条件来筛选数据。
  • 隐式内连接语法如下。如果不写连接条件,则查询出来的数据会是两表的笛卡尔积,没有实际意义,所以连接条件几乎是必备的。
SELECT 字段名 FROM1,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';
  • 如果我希望test用户只能本地登录,则如下创建:
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是啥子,下面马上就讲~
#方式1:创建表的时候把一个字段定义为主键。这种情况只能设置单字段主键。
create table student(
    id int primary key [auto_increment],
    name varchar(50)
) [auto_increment = 数值];

#方式2:创建表的时候,在末尾指定主键。这种情况可以指定多个字段作为主键,但是不能设置自增
create table student(
    id int,
    name varchar(50),
    primary key(id, name)
);

#方式3:创建完表格后,修改表格时新增主键。此时也可以指定多个字段。
alter table student add primary key(id);
  • 删除主键主要就一种写法。因为一张表只有一个主键,所以并不需要指定字段名。
alter table 表名 drop primary key;

主键自增

  • auto_increment是设置主键自增长的关键字。
  • 因为主键不能重复且非空,所以通常都是按照一定规律自增的。为了方便实现自增,MySQL提供了这个关键字。
  • 这个关键字只能用于修饰int类型的主键,并且可以在建表语句最后指定自增的初始值,不设置则默认为1。
  • 当使用delete删除数据的时候,无论删掉多少条数据,都不会对自增主键的计数造成影响。而如果使用truncate删除数据,则会将主键自增计数重置为1(注意,不是重置为建表时设置的初始值哦)。

非空约束(NOT NULL)

  • 非空约束很好理解,就是说指定该字段不能为空值(NULL)。
  • 一张表可以有多个非空约束。
  • 主要有两种定义方式。非空约束不能像主键那样在末尾一次性指定多个字段。
#方式1:建表时定义
create table student(
    id int NOT NULL
);

#方式2:修改表时,使用modify定义
alter table student modify id int not null;
  • 删除非空约束主要也是使用modify,定义为可为可空即可,null关键字可写可不写:
alter table student modify id int [null];

唯一约束(UNIQUE)

  • 唯一约束也是很好理解的,即指定字段的值不能够重复,但是对于NULL值不做唯一限制。
  • 一张表可以有多个唯一约束。
  • 和主键类似,主要有三种定义方式:
#方式1:建表时定义
create table student(
    id int unique,
    name varchar(50) unique
);

#方式2:建表时,在末尾定义
create table student(
    id int,
    name varchar(50),
    unique [约束名] (id, name)
);

#方式3:修改表时,使用modify定义
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的事务提交,流程如下:
#开启事务,还可以使用关键字begin,效果是一样的
start transaction;

#执行各种DML
...

#提交事务,或者使用rollback;回滚事务
commit;
  • 自动提交事务是MySQL的默认方式:每一条DML语句都视为一个单独的事务。
  • 自动提交事务是由MySQL的一个系统参数控制的,可以使用下面的语句查询该参数。可以看到该参数的value默认是ON。
SHOW VARIABLES LIKE 'autocommit';
  • 如果想要关闭自动提交事务,则可以通过下面的语句实现。同理,开启也是使用该语句。
SET @@autocommit = off;
  • 如果关闭了自动提交事务,则默认的事务提交方式就和Oracle一样了:
#执行各种DML
...

#commit提交事务,或者rollback回滚事务。
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、第四范式、第五范式。实际生产中最常用的应该就是第三范式了。
  • 范式是数据库设计的内容。数据库设计这个东西,主要还是靠经验的积累,所以这里就大致介绍一下范式就行。
    • 真正要细讲范式的话,就需要先弄明白关系表中的码、主属性、完全函数依赖、部分函数依赖和传递函数依赖是什么东西。要讲这些的话内容就太多了,范式不是这篇文章的重点。

第一范式

  • 第一范式规定了每一列应该具有原子性。例如下表就是一个不满足第一范式的例子:
id 省市
1 四川成都
2 浙江杭州
  • 因为省市不具有原子性,应该拆为:
id
1 四川 成都
2 浙江 杭州

第二范式

  • 正第一范式的基础上,要求一张表只能表达一件事情。例如下表就不符合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
    #SQL集
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的值也会不同。
@age @name
2 %王%

触发器

  • 触发器可以实现执行一条sql语句的时,自动去触发执行其他的sql语句。
  • 但是触发器和存储过程类似,难以维护,所以存储过程、触发器通常都很少用,转而用程序代码实现。
  • 触发器的逻辑可以用一句话概括:某张表在执行某个操作的前后额外执行某种操作。

触发器的使用

  • 创建触发器的语法如下。
    • for each row是固定的,没有别的写法,意思是MySQL的触发器会对表中每一行数据生效。
    • before和after表示是在事件之前还是之后执行触发器逻辑。
    • insert、update、delete分别代表插入、更新、删除事件。
    • 触发器没法获取触发事件时的相应参数,所以触发器的作用比较有限。
delimiter $
CREATE TRIGGER 触发器名称
before|after(insert|update|delete) on 表名
for each row
begin
    #SQL集
end $
  • 删除触发器的方法如下:
DROP TRIGGER 触发器名;
  • 因为触发器的使用确实少,并且语法和存储过程差不太多,所以就不举例了。

数据库引擎

  • 用非常简单的几句话来介绍一下数据库引擎(因为我也不太懂哈哈哈尬)。
  • 数据库引擎是真正与磁盘进行交互的东西。当执行sql语句的时候,数据库引擎就会解析这些语句,来进行相应的处理。
  • 数据库引擎主要使用的有InnoDB和MyISAM。MySQL在5.1及之前的版本都默认使用的是MyISAM,后来的版本默认为InnoDB。
  • MySQL中,每一张表都可以单独指定数据库引擎。
  • 这两个引擎有什么区别呢?
    • MyISAM最大的特点就是查询效率很高,但是缺失很多功能。例如不支持事务、外键、主键自增长。并且该引擎采用的表级锁,没有行级锁。所以适合用在查询非常多、修改非常少的表中。
    • InnoDB是最泛用的引擎。虽然他的查询效率没有MyISAM高,但是能够支持所有MyISAM不支持的功能。并且InnoDB默认使用的行级锁。所以适合用在普通的表中。

猜你喜欢

转载自blog.csdn.net/w764476876/article/details/114943492
今日推荐