数据库基础与SQL:表操作、查询、事务、索引、存储过程。

数据库的相关概念

数据

数据就是描述事物的符号,可以是文本、图形、音频、视频等形式

数据库(DB)

存放数据的仓库。存放到计算机中,按照一定的格式存放,可以为用户共享

数据库管理系统(DBMS)

科学的维护和管理数据库

Oracle、MySQL、SQL Server、DB2、PostgreSQL

数据库应用程序

在句酷管理系统基础上,使用数据库系统的语法,开发的直接面向最终用户的应用程序

数据库管理员

数据库管理系统的操作者

最终用户

数据库应用程序的使用者

数据库系统

数据库+数据库管理系统+数据库应用程序+最终用户

数据库的发展阶段

  1. 网状数据库
  2. 层次数据库
  3. 关系数据库

关系数据库使用结构化查询语言(sql)作为客户端与服务器沟通的桥梁,采用二维表结构存储于管理数据

  1. 对象数据库

NoSQL

如今泛指非关系型数据库

Redis,MongoDB

数据库基础操作

连接数据库

1
mysql -u用户名 -p密码

显示所有数据库

1
show databases;

使用某个数据库

1
use 数据库名

修改密码

1
2
3
4
-- 将root用户的密码修改为111111
update user set authentication_string=PASSWORD('111111') where user='root';
-- 刷新
flush privileges;

创建用户

1
create user '用户名'@'主机信息' IDENTIFIED BY '密码';

用户名和主机信息共同构成一个账户,比如 pig@localhost 和pig@192.168.31.11 是两套权限

给用户授权

1
grant privileges on 数据库名.表名 to '用户名'@'主机信息';

privileges:权限信息,如SELECT、UPDATE、INSERT、ALL

如果想让用户操作指定数据库下的所有表,就是 数据库名.*。如果想让用户操作所有的数据库,就是 *.*

创建一个数据库

第一种语法

1
create database 数据库名 default charset 编码;

第二种语法

1
create database if not exists 数据库名 default charset 编码;

在创建数据库时要指定编码

事实上mysql中一般不用utf8编码,绝大多数情况下一个中文字符占3字节,但极少情况下会有占4字节的中文字符,这些字符不能使用utf8存储,而需要使用utf8mb4存储。

删除数据库

1
2
3
4
-- 直接删除
drop database 数据库名;
-- 先检查,存在才删除
drop database if exists 数据库名;

什么是SQL

SQL是一个面向过程的结构化查询语言。我们对数据库的任何操作都是基于SQL语句的。

SQL分为四个部分:DDL、DML、DQL、TCL

DDL数据定义语言

主要用于创建和删除结构

创建/修改/删除表、视图、索引、数据

DML数据操纵语言

主要用于对表和视图进行插入、删除、更新操作

DQL数据查询语言

对表和视图进行查询操作

TCL事务控制语言

对事务进行管理,提交、回滚等操作

MySQL表操作(DDL)

创建表

语法

1
2
3
4
5
6
7
create table 表名 
(
列名1 数据类型1,
列名2 数据类型2,
....
列名n 数据类型n
) default charset=编码;

创建一个学生表

1
2
3
4
5
6
7
create table student2(
id int(11),
stu_name varchar(50),
stu_sex bit,
stu_hobby varchar(50),
stu_age int(11)
) default charset=utf8mb4;

注意:

  1. 数据类型后面使用括号加上字段长度
  2. 字段名称一般使用下划线规则

查看数据库下所有的表

1
show tables;

查看表信息

1
2
3
4
-- 语法1
desc 表名;
-- 语法2
show create table 表名;

修改表结构

基本语法

1
alter table 表名 修改语句;

案例1

添加列,比如像student表新增一列stu_height学生身高

1
2
3
4
-- 语法
alter table 表名 add column 列名 数据类型;
-- sql
alter table student add column stu_height int(11);

案例2

删除列,比如删除student表中的身高

1
2
3
4
5
-- 语法
# 注释
alter table 表名 drop column 列名;
-- sql
alter table student drop column stu_height;

案例3

修改列名称和数据类型,比如修改stu_sex为student_sex,数据类型修改成varchar

1
2
3
4
-- 语法
alter table 表名 change column 原列名 新列名 数据类型;
-- sql
alter table student change column stu_sex student_sex varchar(30);

案例4

修改某一列的数据类型,把年龄改成int类型

1
2
3
4
-- 语法
alter table 表名 modify column 列名 数据类型;
-- sql
alter table student modify column stu_age tinyint;

删除表

1
2
3
4
-- 语法
drop table 表名;
-- 语法2
drop table if exists 表名;

数据操作(DML)

添加数据

单个添加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
-- 语法,值列表的个数要与表字段个数保持一致
insert into 表名 values(值1, 值2, 值3......);
-- 向student表中添加数据
insert into student values(1, '迪丽热巴', '女', '唱歌', 20);

-- 查看表中所有的数据
select * from 表名;
-- 查询student中所有数据
select * from student;
insert into student values(2, '古力娜扎', '女', '跳舞', null);

-- 当我们想插入指定某些字段时,可以使用下面的语法,列的个数需要与值的个数一致,并且一一对应
insert into 表名(列1, 列2) values (值1, 值2);
-- 向student表中插入数据,只有id和姓名
insert into student(id, stu_name) values(3, '马尔扎哈');

批量添加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-- 有时候我们想向表中批量添加数据。注意,所有待插入的值,个数必须要一致,与字段个数保持一致
insert into 表名
(列1, 列2, 列3...)
values
(值1, 值2, 值3...),
(值1, 值2, 值3...),
(值1, 值2, 值3...);

-- 向student表中插入数据,字段有id、姓名、年龄、性别
insert into student
(id, stu_name, stu_age, student_sex)
values
(4, '真皮沙发', 10, '男'),
(5, '阿拉斯加', 6, '男'),
(6, '阿里巴巴', 30, '女');

修改数据

批量修改

1
2
3
4
-- 语法
update 表名 set 列名 = 值;
-- 将所有人年龄修改成20岁
update student set stu_age = 20;

单个修改

1
2
3
4
5
6
-- 语法
update 表名 set 列1=值1, 列2=值2... where 匹配条件
-- 给id是3的用户年龄修改成50岁,爱好修改为 唱跳rap篮球
update student set stu_age = 50, stu_hobby = '唱、跳、rap、篮球' where id = 3;
-- 给name是阿拉斯加的学生姓名修改成张三
update student set stu_name='张三' where stu_name='阿拉斯加';

删除数据

删除单个

1
2
3
4
5
6
-- 语法
delete from 表名 where 条件;
-- 删除id是1的学生
delete from student where id = 1;
-- 删除所有性别是男的学生
delete from student where student_sex = '男';

批量删除

1
2
-- 语法
delete from 表名;

truncate关键字

1
2
3
-- 语法
truncate 表名;
-- truncate是DDL,delete是DML,truncate是对表的操作,性能比delete要高

数据完整性

为MySQL数据库中的表定义一个规则,确保表中的数据的有效性、一致性、完全性,减少脏数据、重复数据

主键约束

一个表中数据创建了主键,那么该表中所有的数据的这一列必须是唯一的,不能有重复,且不能是空。

一般的主键为数值类型,呈底层趋势,且设置成主键约束

1
2
3
4
5
6
7
8
9
10
11
12
create table tb_class (
id int(11) auto_increment, -- 不需要手动插入id,自动递增
class_name varchar(30),
class_desc varchar(100),
primary key (id) -- 给id字段设置成主键约束
);
-- 尝试插入数据
insert into tb_class(class_name) values
('1班'),
('2班'),
('3班'),
('4班');

非空约束

非空约束关键字是 not null,在建表或者修改字段时指定即可

1
2
-- 给class_name字段设置成非空
alter table tb_class modify class_name varchar(30) not null;

唯一约束

唯一约束的作用是让某个字段保持唯一性(可以为空)

1
2
3
4
5
6
7
8
9
10
11
12
13
-- 创建一张新表
create table stu2 (
id int(11) auto_increment,
stu_name varchar(30) not null,
stu_code varchar(30) unique, -- 唯一约束
card_no varchar(30) not null unique,
primary key (id)
);
-- 插入数据
insert into stu2(stu_name, stu_code, card_no) values
('张三', '123', '123'),
('张三', '234', '322'),
('李四', '345', '122');

默认约束

1
2
3
4
5
-- 语法:default 默认值,可以用在建表和修改表语句
-- 首先给tb_student表插入一些数据
insert into tb_student(stu_name) values ('天七'), ('刘八'), ('李九'), ('丁一');
-- 给tb_student的性别字段改成默认值是1
alter table tb_student modify stu_sex bit not null default 1;

外键约束

外键又叫外部关键字,为多张表中建立联系,确保表与表之间的数据安全性、一致性,能够减少数据冗余

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-- 创建tb_student
create table tb_student (
id int(11) auto_increment,
stu_name varchar(50) not null,
stu_mobile varchar(20) unique,
stu_sex bit not null default 1,
class_id int(11) not null, -- 班级id
primary key (id),
-- 指定外键,指定学生表的class_id建立外键去关联tb_class的id字段
foreign key (class_id) references tb_class(id)
);

-- 插入数据
insert into tb_student(stu_name, stu_mobile, stu_sex, class_id) values
('迪丽热巴', '123456465', 1, 2);

表关联

一对一

一对一指的是两张表的数据存在一一对应的关系

1
2
3
4
5
6
7
8
9
10
11
12
13
-- 创建地址表
create table tb_address (
id int(11) auto_increment,
address_info varchar(50) not null,
primary key (id)
);
-- 插入数据
insert into tb_address(address_info) values('北京'), ('上海'), ('广州'), ('曹县');

-- 修改表结构,给tb_student增加地址id字段
alter table tb_student add column address_id int(11) not null;
-- 修改表结构,给刚才添加的字段设置成外键
alter table tb_student add foreign key(address_id) references tb_address(id);

一对多/多对一

存在 最普遍最常见的映射关系,前面的案例中,班级和学生的关系就是一对多,学生和班级的关系就是多对一。

多对多

比如学生与课程的关系,一个学生可以有多个课程,一个课程可以被多个学生去选,可以创建一张中间表来进行关联

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
-- 创建课程表
create table tb_course(
id int(11) auto_increment,
course_name varchar(30) not null,
primary key (id)
);
-- 创建中间表,维护两张表之间的关系
create table tb_student_cource (
stu_id int(11),
course_id int(11),
course_socre int(11),
primary key(stu_id, course_id), -- 复合主键
foreign key(stu_id) references tb_student(id),
foreign key(course_id) references tb_course(id)
);
-- 添加课程数据
insert into tb_course(course_name) values ('语文'), ('数学'), ('英语');
-- 关联数据
insert into tb_student_cource(stu_id, course_id) values (2,1), (2,2), (2,3), (3,1), (3,2);

员工系统表结构设计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
-- 部门表
create table dept (
dept_no int(11) auto_increment, -- 部门id,自增
dept_name varchar(33) not null, -- 部门名称
dept_loc varchar(30), -- 部门位置
primary key(dept_no)
);

-- 员工表
create table emp(
emp_no int(11) auto_increment, -- 员工id
emp_name varchar(30) not null, -- 员工姓名
manager_name varchar(30), -- 员工领导姓名
manager_id int(11), -- 员工领导id
workdate date, -- 入职日期
job varchar(20), -- 职位
sal decimal(10, 2), -- 员工工资
comm decimal(10, 2), -- 员工奖金
dept_no int(11), -- 所属部门
primary key(emp_no),
foreign key(dept_no) references dept(dept_no)
);

-- 工资等级表
create table salgrade(
grade int(11) auto_increment, -- 工资等级
lo_sal decimal(10, 2), -- 最低工资
hi_sal decimal(10, 2), -- 最高工资
primary key(grade)
);
-- 初始化数据
-- ----------------------------
-- 部门表插入数据
-- ----------------------------
INSERT INTO DEPT VALUES (10, '财务部', '武汉');
INSERT INTO DEPT VALUES (20, '研发部', '武汉');
INSERT INTO DEPT VALUES (30, '销售部', '深圳');
INSERT INTO DEPT VALUES (40, '业务部', '上海');
-- ----------------------------
-- 员工表插入数据
-- ----------------------------
INSERT INTO EMP values (7369, 'SMITH', 'CLERK', 7902, '1980-12-17', null, 800.00, null, 20);
INSERT INTO EMP values (7499, 'ALLEN', 'SALESMAN', 7698, '1981-02-20', null, 1600.00, 300.00, 30);
INSERT INTO EMP values (7521, 'WARD', 'SALESMAN', 7698, '1981-02-22', null, 1250.00, 500.00, 30);
INSERT INTO EMP values (7566, 'JONES', 'MANAGER', 7839, '1981-04-02', null, 2975.00, null, 20);
INSERT INTO EMP values (7654, 'MARTIN', 'SALESMAN', 7698, '1981-09-28', null, 1250.00, 1400.00, 30);
INSERT INTO EMP values (7698, 'BLAKE', 'MANAGER', 7839, '1981-05-01', null, 2850.00, null, 30);
INSERT INTO EMP values (7782, 'CLARK', 'MANAGER', 7839, '1981-06-09', null, 2450.00, null, 10);
INSERT INTO EMP values (7788, 'SCOTT', 'ANALYST', 7566, '1987-04-19', null, 3000.00, null, 20);
INSERT INTO EMP values (7839, 'KING', 'PRESIDENT', null, '1981-11-17', null, 5000.00, null, 10);
INSERT INTO EMP values (7844, 'TURNER', 'SALESMAN', 7698, '1981-09-08', null, 1500.00, 0.00, 30);
INSERT INTO EMP values (7876, 'ADAMS', 'CLERK', 7788, '1987-05-23', null, 1100.00, null, 20);
INSERT INTO EMP values (7900, 'JAMES', 'CLERK', 7698, '1981-12-03', null, 950.00, null, 30);
INSERT INTO EMP values (7902, 'FORD', 'ANALYST', 7566, '1981-12-03', null, 3000.00, null, 20);
INSERT INTO EMP values (7934, 'MILLER', 'CLERK', 7782, '1982-01-23', null, 1300.00, null, 10);
-- ----------------------------
-- 工资等级表插入数据
-- ----------------------------
INSERT INTO SALGRADE values (1, 700, 1200);
INSERT INTO SALGRADE values (2, 1201, 1400);
INSERT INTO SALGRADE values (3, 1401, 2000);
INSERT INTO SALGRADE values (4, 2001, 3000);
INSERT INTO SALGRADE values (5, 3001, 9999);

单表数据查询

最简单的查询方式

1
2
3
4
5
6
7
-- 直接查全表的所有数据
select * from emp;
-- 只查询指定的字段
-- 语法
select 字段1, 字段2, 字段3 from 表名;
-- sql
select emp_no, emp_name from emp;

查询中进行计算

1
2
3
-- 查询工资以及年终奖
select emp_no, emp_name, sal, sal*1.5 from emp;
-- 使用算数表达式的前提是字段为数值型

查询中使用字段别名

1
2
3
4
5
6
7
8
9
10
11
-- 语法
select 字段1 as 别名1, 字段2 as 别名2 from 表名;
-- 其中,as关键字可以省略

-- 改造上面的sql,起别名
select
emp_no as 员工编号,
emp_name as 员工姓名,
sal as 员工工资,
sal*1.5 as 年终奖
from emp;

熟练地使用别名,可以给sql查询结果增加可读性。如果字段或者别名中包含了sql的关键字,需要使用两个反引号包裹起来

去重查询

1
2
3
4
-- 有时候查询出来的结果有重复数据,此时可以使用去重
select distinct 查询字段 from 表名;
-- sql演示
select distinct manager_id from emp;

排序查询

1
2
3
4
5
-- 语法
正常的查询语法 order by 排序字段 asc/desc;
-- 演示,根据工资排序
select * from emp order by sal desc;
多个用逗号隔开

分页查询

1
2
-- 语法
select * from 表名 limit n1, n2;

n1:表示偏移量,即跳过前面多少条数据开始查询

n2:表示查询多少行

n1=(页数-1) * 每页条数

1
2
3
4
5
6
-- 查询第1页,每页3条
select * from emp limit 0, 3;
-- 查询第2也,每页3条
select * from emp limit 3, 3;
-- 查询工资最高的三个员工
select * from emp order by sal desc limit 3;

合并查询

将多个select语句的查询结果合并成一个查询结果

union all:不去重合并

union:去重合并

1
2
3
4
-- 演示
select * from emp where sal > 2000
union
select * from emp where sal < 2500;

where子句

作用:指定条件匹配对应的数据,可以应用于select、update、delete

select * from emp where sal > 2000;

where 中使用比较运算符

MySQL中支持以下比较运算符 >、>=、<、<=、!=、=(没有双等号)、<>(不等于)

此外还支持以下的关键字

运算符 含义
between A and B 查询A和B之间的数据(包含边界)
in(collecton) 查询出现在collection集合中的数据
like 模糊匹配
is null 为空
not like 模糊匹配取反
not in(collection) 查询没有出现在集合中的数据
is not null 不为空
1
2
3
4
5
6
7
8
-- 查询工资大于3000的数据
select * from emp where sal > 3000;
-- 查询没有领导的数据
select * from emp where manager_id is null;
-- 查询有奖金的员工,并根据奖金排序
select * from emp where comm is not null order by comm desc;
-- 查询领导id是7902、7698、7782的员工信息
select * from emp where manager_id in (7902,7698,7782);

使用like关键字进行模糊匹配

1
2
-- 语法
like '_或%模糊匹配的字符_或%'

_的作用是匹配一个字符,%的作用是匹配0-多个字符

1
2
3
4
5
6
-- 查询所有名字是A开头的员工
select * from emp where emp_name like 'A%';
-- 查询名字中含有T的员工
select * from emp where emp_name like '%T%';
-- 查询名字S开头,且名字是5个字母的员工
select * from emp where emp_name like 'S____';

_可以精确匹配制定个数的字符,%则只能模糊匹配,符合条件就匹配到

查询使用逻辑运算符

与:and,或:or,非:not

1
2
3
4
-- 查询dept_no等于10且工资大于2000的数据
select * from emp where dept_no = 10 and sal > 2000;
-- 查询有奖金或者工资大于等于3000的员工
select * from emp where comm is not null or sal >= 3000;

and和or在使用时有一些小细节

and:把检索结果较少的条件放到后面

or:把检索结果较多的条件放到后面

到这里为止,总结一下目前学习到的sql查询语法

1
2
select 查询字段 from 表名 
[where 查询子句] [order by 排序字段] [limit n1, n2];

聚合函数

聚合函数可以大大提高select语句操作数据库的能力

单行函数

数学函数

  • ceil(x)、ceiling(x) 返回不小于x的最小整数(向上取整)
  • floor(x) 向下取整
  • round(x) 四舍五入
  • rand() 返回0-1之间的随机小数
1
select *, ceil(sal * 0.3) 奖金 from emp;

字符串函数

  • char_length(s) 计算字符串的字符个数
  • concat(s1, s2, s3….) 拼接字符串,如果存在一个null,则最终结果就是null
  • concat_ws(x, s1, s2, s3….) 拼接字符串,每个字符串拼接之前中间都会用x隔开
  • lower(s)和lcase、upper和ucase:前两个将字符串全部转成小写,后两个全部转换成大写
  • replace(s, s1, s2) 将s中的字符串s1全部替换成s2
  • substring(s, n ,len)从字符串s中返回第n的字符开始,长度为len的字符串
  • reverse(s) 将字符串翻转
1
2
3
4
5
6
7
8
9
10
11
12
-- 查询每个名字的长度
select char_length(emp_name) from emp;
-- 将id和姓名拼接起来,中间使用 - 连接
select concat(emp_no, '-', emp_name) as full_name from emp;
-- 姓名全部转成小写
select *, lower(emp_name) l_name from emp;
-- 将姓名中所有的a换成b
select *, replace(emp_name, 'A', 'B') as name from emp;
-- 取出第一个字符作为姓氏
select *, substring(emp_name, 1, 1) as f_name from emp;
-- 名字倒转
select emp_name, reverse(emp_name) as re_name from emp;

日期时间函数

  • curdate()、current_date(),将当前日期返回
  • current_timestamp()、localtime()、now()、sysdate(),将当前日期时间放回
  • month(date)、monthname(date),返回指定日期中的月份
  • week(d)、weekofyead(d),前者是计算日期d是一年中的第几周,后者是计算某天位与一年的第几周
  • dayofyear(d),dayofmonth(d),前者是查询d是一年中的第几天,后者查询d是一个月中的第几天
  • extract(type from date) 从date中提取一部分,type可以是year、year_month、day_hour、day_minute、day_second、day、DAY_MICROSECOND
  • timestampdiff(间隔类型, 前一个日期, 后一个日期) 查询两个日期之间间隔了多久,间隔类型是时间单位,可以是second、minute、hour、day、week、month、quarter、year
  • date_add(date, interval expr type)、date_add(),返回将起始时间加上 expr type 之后的时间
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
-- 查询当前日期
select curdate();
-- 查询当前时期时间
select now();
-- 查询每个员工入职的月份
select emp_name, month(workdate) from emp;
-- 查询每个员工入职的周
select emp_name, week(workdate) from emp;
-- 查询每个员工入职的天
select emp_name, dayofyear(workdate) from emp;
-- 演示extract
select emp_name, extract(year_month from workdate) from emp;
-- 查询每个员工入职了多少年
select emp_name, workdate, timestampdiff(year, workdate, now()) as d from emp;
-- 将每个员工入职时间加3年
select emp_name, workdate, date_add(workdate, interval 3 year) as d from emp;

条件判断函数

  • if(expr, v1, v2) 如果表达式expr为true,则返回v1,否则返回v2
  • ifnull(v1, v2) 如果v1的值不为空,就返回v1,否则返回v2
  • case expr when v1 then r1 when v2 then r2 else rn end,类似于if…else if …else
1
2
3
4
5
6
7
8
9
10
11
12
13
-- 如果奖金是空,就返回0
select *, ifnull(comm, 0) from emp;
-- 如果工资大于等于3000,有一个半月的年终奖
select *, if(sal >= 3000, sal*1.5, 0) as m from emp;
-- 判断dept_no,如果是10,则为技术部,如果是20,则为运营部,否则则为销售部
select *,
(case dept_no
when 10 then '技术部'
when 20 then '运营部'
else '销售部'
end
) as dept_name
from emp;

其他函数

  • version() 查看当前MySQL版本号
  • password(s) 将s加密成一个密码,这个加密是不可逆的

多行函数

对一些数据进行运算,最终只返回一个结果

  • sum():求和
  • avg():求平均
  • count():求总数
  • max():最大值
  • min():最小值
1
2
3
4
5
6
7
8
9
-- 统计平均工资、总工资、总数据条数、最大工资、最小工资
select
sum(sal) as 总工资,
count(sal) as 总数,
max(sal) as 最大工资,
min(sal) as 最小工资,
-- 总工资 / 总数 as 平均工资 注意:别名不能在select中参与计算
avg(sal) as 平均工资
from emp;

注意:count函数建议使用count(*) 来统计,因为这样效率更高

count的字段如果存在null,只会统计非null的字段,因此count(*)结果只与count(非空字段)一致

group by 子句

mysql中的group by 也是用于分组

1
2
3
4
5
6
select 查询字段 
from 表名
[where 查询子句]
[group by 分组字段1, 字段2]
[order by 排序字段1, 字段2]
[limit n1, n2];

注意:如果使用了group by 子句,select中的字段如果没有出现在group by 中,则必须出现在多行函数中,如果出现在了group by 中,则不能出现在多行函数中。

1
2
3
4
5
6
7
-- 查询每个部门的平均工资
select
dept_no,
avg(sal) as avg_sal
from emp
group by dept_no
order by avg_sal;

having子句

如果想对group by查询的结果进行再一次的筛选过滤,需要使用having,having支持where所有的操作符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
-- 语法
select 查询字段
from 表名
[where 查询子句]
[group by 分组字段1, 字段2]
[having 查询子句]
[order by 排序字段1, 字段2]
[limit n1, n2];
-- 查询所有部门的平均工资,并筛选出平均工资在2000以上的部门
select
dept_no,
avg(sal) as avg_sal
from emp
group by dept_no
having avg_sal > 2000
order by avg_sal;

随堂练习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
-- 统计人数小于4的部门的平均工资。
select
dept_no,
avg(sal) as avg_sal,
count(*) as emp_count
from emp
group by dept_no
having emp_count > 4
order by avg_sal;

-- 统计各部门的最高工资,排除最高工资小于3000的部门。
select
dept_no,
max(sal) as max_sal
from emp
group by dept_no
having max_sal >= 3000
order by max_sal;


-- 显示部门编号大于10 的部门的人数,要求人数大于3
select
count(*) emp_count,
dept_no
from emp
where dept_no > 10
group by dept_no
having emp_count > 3
order by emp_count;

连接查询 SQL92

语法

1
2
3
select t1.column1, t1.column2, t2.column1
from t1, t2
where t1.columnA=t2.columnB;

连接查询分为等值连接、非等值连接、左连接、右连接、自连接

连表查询是基于笛卡尔积的

不写where条件的情况下,查询出来的结果就是笛卡尔积,笛卡尔积是没有任何意义的,因为笛卡尔积会带出很多错误的数据(没有关联),因此在查询时一定要加上连表条件

等值查询

使用相等作为连接条件进行连接查询的方式

1
2
3
4
5
6
7
8
9
10
-- 常规的连接查询
select * from emp, dept where emp.dept_no=dept.dept_no;
-- 查询的每一张表都可以设置别名
select * from emp as e, dept as d where e.dept_no=d.dept_no;
-- 连表查询也可以指定查询字段
select e.emp_no, e.emp_name, e.sal, d.dept_no, d.dept_name from emp as e, dept as d where e.dept_no=d.dept_no;
-- 连表查询的where条件中依然可以加其他的查询条件,同样也可以使用order by、group by、having、聚合函数等
select e.emp_no, e.emp_name, e.sal, d.dept_no, d.dept_name
from emp as e, dept as d
where e.dept_no=d.dept_no and e.sal >= 3000 order by e.sal desc;

非等值查询

查询员工的工资等级

1
2
3
select e.emp_no, e.emp_name, e.sal, s.grade
from emp e, salgrade s
where e.sal < s.hi_sal and e.sal > lo_sal;

连接查询可以同时连两张以上的表

1
2
3
4
select 
e.emp_no, e.emp_name, e.sal, s.grade, d.dept_no, d.dept_name
from emp as e, dept as d, salgrade s
where e.dept_no=d.dept_no and e.sal < s.hi_sal and e.sal > lo_sal;

自连接

自己连自己

1
2
3
select e1.emp_no, e1.emp_name, e1.manager_id, e2.emp_no, e2.emp_name as 领导名称
from emp e1, emp e2
where e1.manager_id = e2.emp_no;

连接查询 SQL99

SQL92版本的连接查询存在一些问题:SQL92版本的连表查询将连表条件和筛选条件都放到了where语句中,容易造成混淆。

SQL99则修正了这个缺点,将二者分离开来

语法

1
2
3
4
5
6
7
8
9
10
11
select *
from table1 as t1
[cross join table2 as t2] -- 交叉连接
[natural join table2 as t2] -- 自然连接
[join table2 using(字段名)] -- using子句
[join table2 on (连表条件)] -- on 子句
[(left | right | full outer) join table2 on (连表条件) ] -- 左/右/满外连接
where 条件
group by 排序字段
having 过滤条件
order by 排序字段;

交叉连接

交叉连接查询结果会产生一个笛卡尔积,其效果相当于SQL92版本中不使用连表条件查询出来的结果,想增加连接条件,可以使用where语句

1
2
3
select * from emp as e
cross join dept as d
where e.dept_no = d.dept_no;

自然连接

自然连接基于两个表中的 全部 同名字段建立连接

  • 从两个表中选出同名的列的值均对应相等的所有行
  • 如果两个表中同名的列数据类型不同,则出错
  • 不允许在参照裂伤使用表名或者别名作为前缀
  • 自然连接的结果不保留重复的属性
1
2
select * from emp as e
natural join dept as d;

using子句

如果不希望参照被连接表所有的同名字段进行等值连接,自然连接就无法满足要求,此时可以使用using子句来设置用于等值连接的列

using子句使用到的字段同样不能使用表别名

1
2
3
4
select * from emp as e
join dept d
using(dept_no)
where e.sal >= 3000;

on子句

自然连接和using子句条件是基于两张表中存在同名字段的前提下,可以使用on子句

on子句能够更加灵活的指定两张表之间使用哪两个字段进行关联

1
2
3
4
select * from emp as e
inner join dept as d
on e.deptno=d.dept_no
where e.deptno = 20;

外连接

内连接

内连接使用 inner join 关键字进行连接,只会返回匹配到的所有数据

1
2
3
select * from emp e
inner join dept d
on e.deptno = d.dept_no;

左外连接

左外连接特点是除了返回满足条件的行以外,还返回左表中不满足条件的行

1
2
3
select * from emp e
left join dept d
on e.deptno = d.dept_no;

右外连接

右外连接除了返回满足条件的行以外,还返回右表中不满足条件的行

1
2
3
select * from emp e
right join dept d
on e.deptno = d.dept_no;

满外连接

除了返回满足条件的行以外,还会返回两张表中不满足条件的行

1
2
3
select * from emp e
full join dept d
on e.deptno = d.dept_no;

MySQL5.7中不支持满外连接

子查询

问题引入

如何查询所有比CLARK 工资高的员工信息

此时我们可以使用子查询,第一步先查询CLARK的工资,第二步将上一步查询的工资作为筛选条件进行查询

语法

1
2
3
4
5
select 字段列表 from table1
where 字段名 运算符 (select 字段列表 from table2);
-- 实现上面的需求
select * from emp
where sal > (select sal from emp where emp_name = 'CLARK');

子查询的特点

  • 子查询会在主查询前执行一次,主查询使用子查询的查询结果
  • 在查询条件是基于未知数据时考虑使用子查询
  • 子查询必须放到括号内部
  • 建议将子查询放到条件末尾
  • 一般不建议在子查询中使用order by 子句

单行子查询

单行子查询只返回一行记录,因此对于单行子查询可以使用比较运算符

1
2
3
4
5
6
7
8
9
-- 查询职务与SCOTT相同,但是雇佣时间比他早的员工信息
select * from emp
where job = (select job from emp where emp_name='SCOTT')
and workdate < (select workdate from emp where emp_name='SCOTT');
-- 查询工资比SCOTT高或者雇佣时间比SCOTT早的雇员的员工编号、名字、工资,并根据工资倒序排序
select emp_no, emp_name, sal from emp
where sal > (select job from emp where emp_name='SCOTT')
or workdate < (select workdate from emp where emp_name='SCOTT')
order by sal desc;

多行子查询

多行子查询会返回多行记录,多行子查询不能直接使用单行子查询的比较运算符

all:查询条件与子查询返回的所有值比较,比较是否全部满足

any:查询条件与子查询返回结果的任意一个值比较,比较是否存在一个满足

in:与where条件的in相同

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
-- 查询工资低于其中一个岗位CLERK的工资的雇员信息
select * from emp
where sal < any(select sal from emp where job='CLERK') and job != 'CLERK';
-- 查询工资比所有的 SALESMAN 都高的员工的信息
select * from emp
where sal > all(select sal from emp where job='SALESMAN');
-- 查询部门20中职务同部门10的员工一样的员工信息
select * from emp
where deptno=20
and job in (select job from emp where deptno=10);
-- 查询员工中那些人员是领导
select * from emp
where emp_no in (
select distinct manager_id from emp
);
-- 找出部门编号为20的所有员工收入最高的职员
select * from emp
where sal >= all(select sal from emp where deptno=20)
and deptno=20;
-- 查询每个部门平均薪水的等级
-- 首先求出每个部门的平均薪水和部门信息
select deptno, avg(sal) as avg_sal from emp group by deptno;
-- 查询结果可以作为临时表使用
select * from (select deptno, avg(sal) as avg_sal from emp group by deptno) t;
-- 将查询结果作为一张临时的平均工资表,连工资等级表进行查询
select * from salgrade s
inner join (select deptno, avg(sal) as avg_sal from emp group by deptno) as t
on t.avg_sal between s.lo_sal and s.hi_sal;

视图

定义

视图是从若干的基本表和其他视图构造出来的一个表,在创建视图时,只是存放视图的定义,即是动态检索数据的查询语句,并不存放视图对应的数据

在用户使用视图时才回去求相应的数据,所以视图也叫作”虚表“

视图可以限制对数据的访问,可有给用户授予表的特定部分的访问权限而不是全表访问权限。

可以使查询变得简单,在视图中定义好查询语句,之后就可以很方便的重用它而不用关注内部的查询细节

视图的简单使用

创建视图

1
2
3
4
-- 语法
create view 视图名 as 查询语句;
-- 将emp创建成视图
create view myview1 as select * from emp;

修改或创建视图

1
2
3
4
-- 语法
create or replace view 视图名称 as 查询语句;
-- 创建myview2
create or replace view myview2 as select emp_no, emp_name, job, deptno from emp where deptno > 10;

向视图中添加或修改数据

1
insert into myview1 values (1000, '张三', null, null, '2022-02-02', 'CLERK', 2000, 0, 20);

删除视图

1
2
-- 语法
drop view 视图名;

统计视图

1
2
3
4
5
6
7
8
9
10
11
create or replace view myview3
as
select
d.dept_no as 部门编号,
d.dept_name as 部门名称,
avg(e.sal) as 平均工资,
max(e.sal) as 最高工资,
count(*) as 员工数
from emp e
inner join dept d on e.deptno=d.dept_no
group by d.dept_no, d.dept_name;

视图还可以基于其他视图

1
2
create or replace view myview4 
as select * from myview3 where 员工数 > 3;

视图总结

  • 视图对应着一个查询语句,是从若干个基本表和其他视图沟造出来的虚表
  • 视图是一个虚表,我们可以像操作表一样进行查询、删除、修改、添加, 其实就是对其背后的基本表进行相应的操作
  • 使用视图可以更加的安全,能够只显示出想让用户查看的数据,敏感数据可以隐藏,并且操作简单

触发器(了解)

为什么需要触发器

当我们想对一张表中数据进行备份时,要求每插入一条新的数据,就需要将这条数据也插入到备份表中,此时每写一条insert语句都要相应的给备份表也写一条insert语句,非常麻烦,此时我们就可以使用触发器。

触发器的概念

当我们执行了insert、delete、update语句的时候,就会产生一个事件,针对这个事件触发相应的行为,触发的行为涉及的一系列的操作就是触发器

语法

1
2
3
4
5
6
7
8
create trigger 触发器名称
before or after -- 触发时间,before表示在SQL语句执行前触发,此时会拿到变更之前的数据,after表示在执行之后触发,会拿到执行之后新的数据
insert or update or delete -- 指定触发的事件
on 表名 -- 给哪张表定义的触发器
for each row -- 触发级别,此时表示行级别的触发器,插入一行、删除一行、更新一行触发一次
begin
-- 自定义触发器规则
end;

使用触发器

需求:为emp表创建一个备份表,当向emp表插入数据时,同时向备份表插入一条相同的数据

创建备份表

1
create table emp_bak like emp;

创建触发器

1
2
3
4
5
6
7
8
9
10
11
delimiter //
create trigger trigger_emp_before_insert
before
insert
on emp
for each row
begin
insert into emp_bak(emp_no, emp_name, manager_name, manager_id, workdate, job, sal, comm, deptno) values
(new.emp_no, new.emp_name, new.manager_name, new.manager_id, new.workdate, new.job, new.sal, new.comm, new.deptno);
end //
delimiter ;

插入数据

1
2
insert into emp(emp_no, emp_name, manager_name, manager_id, workdate, job, sal, comm, deptno) values
(1221, '张三', null, null, '2011-01-01', null, 2000, 0, 20);

创建一个删除触发器

1
2
3
4
5
6
7
8
9
10
delimiter //
create trigger trigger_emp_after_delete
after
delete
on emp
for each row
begin
delete from emp_bak where emp_no=old.emp_no;
end //
delimiter ;

自定义函数

语法

1
2
3
4
5
create function 函数名(形参列表) returns 返回值类型
begin
函数体 -- 可以定义变量,变量的形式 set @x=1
返回值
end

定义一个函数完成加法运算

1
2
3
4
5
6
delimiter //
create function myadd(a int, b int) returns int -- 形参列表为 变量名 数据类型,不要写反了
begin
return a + b;
end //
delimiter ;

查看函数

1
2
show function status like '匹配符';
show create function 函数名;

删除函数

1
drop function 函数名;

自定义函数实现计算1-指定数据的和

使用全局变量

1
2
3
4
5
6
7
8
9
10
11
12
delimiter //
create function mysum1(x int) returns int
begin
set @i=1;
set @sum=0;
while @i <= x do
set @sum = @sum + @i;
set @i = @i + 1;
end while;
return @sum;
end //
delimiter ;

使用局部变量,且5的倍数跳过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
delimiter //
create function mysum2(x int) returns int
begin
declare i int default 1; -- 声明局部变量
declare sum int default 0;
sumwhile:while i <= x do
if i % 5 = 0 then
set i = i + 1;
iterate sumwhile; -- iterate代表结束本次循环,使用时要指定一个标签,结束本次循环后从指定的标签位置开始运行
end if;
set sum = sum + i;
set i = i + 1;
end while;
return sum;
end //
delimiter ;

自定义函数给定生成一定长度的随机字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
delimiter //
create function rand_string(n int) returns varchar(255)
begin
declare chars_str varchar(100) default 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXY';
declare result varchar(255) default '';
declare i int default 0;
while i < n do
set result = concat(result, substring(chars_str,floor(1+rand()*52),1));
set i = i + 1;
end while;
return result;
end //
delimiter ;

存储过程

存储过程是完成某个功能需要的一系列SQL语句集合。使用存储过程编译之后就会将存储结果存储到mysql服务器中的缓存中,后面调用存储过程就不需要重复检索数据文件,直接从缓存冲获取数据,提供查询效率

注意:存储过程是一个过程化语言和SQL语言,可以在存储过程中定义变量、分支结构、循环结构,但是不能有return关键字,所以存储过程不能有返回值

语法

1
2
3
4
5
drop procedure if exists 存储过程名称;
create procedure 存储过程名称(形参列表)
begin
过程体
end

形参编写方式:in 输入参数 out 输出参数 inout 输入输出参数

编写存储过程

案例1:编写存储过程,输入员工id得到员工对应的名称和工资

1
2
3
4
5
6
7
8
9
delimiter //
drop procedure if exists proc_emp;
create procedure proc_emp(in empno int)
begin
select emp_no, emp_name, sal
from emp
where emp_no=empno;
end //
delimiter ;

调用语法

1
call 存储过程名称(参数列表);

综合案例

需求:

公司成立N周年,准备为中工发放年终奖,规则如下,
进入公司小于30年的员工在原有工资基础上多发5个月薪水,
进入公司在30~40年之间的员工在原有工资基础上多发10个月薪水,
进入公司大于40年的员工在原有工资基础上多发20个月薪水。

创建年终奖表

1
2
3
4
5
6
7
8
9
10
11
12
13
-- ----------------------------
-- 年终奖表
-- ----------------------------
DROP TABLE IF EXISTS year_end_bonus ;
CREATE TABLE year_end_bonus (
CURRENT_YEARS int(11) NOT NULL COMMENT '年份',
EMPNO int(11) NOT NULL COMMENT '员工编号',
ENAME varchar(50) NOT NULL COMMENT '员工姓名',
BONUS decimal(7, 2) NOT NULL COMMENT '年终奖',
YEARS int(11) NOT NULL COMMENT '入职总月数',
MONTHS int(11) NOT NULL COMMENT '入职总年数',
DAYS int(11) NOT NULL COMMENT '入职总天数'
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COMMENT = '年终奖表' ;

实现步骤

输入员工id,查询员工对应的入职日期,对入职日期进行区间判断,根据判断结果向年终奖表中insert数据,如果当前已有数据,就删除后再添加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
delimiter //
drop procedure if exists proc_yearmoney;
create procedure proc_yearmoney(in calc_date datetime, in empno int)
begin
-- 定义局部变量存储员工姓名、入职日期、在职年数、在职月数、在职天数
declare temp_name varchar(50) default null;
declare temp_workdate datetime default current_date;
declare temp_year int default 0;
declare temp_month int default 0;
declare temp_day int default 0;
declare temp_bounds decimal(9, 2) default 0.00;
declare temp_sal decimal(9, 2) default 00;
-- 根据员工id获取员工基本信息,存储到局部变量
select emp_name, workdate, sal into temp_name, temp_workdate, temp_sal
from emp where emp_no=empno;
-- 根据计算日期计算员工的在职年数,在职月数,在职天数
select timestampdiff(year, temp_workdate, calc_date),
timestampdiff(month, temp_workdate, calc_date),
timestampdiff(day, temp_workdate, calc_date)
into temp_year, temp_month, temp_day;
-- 对在职年数进行区间判断,计算奖金
case
when temp_year < 30 then
set temp_bounds = temp_sal * 5;
when temp_year >= 30 and temp_year < 40 then
set temp_bounds = temp_sal * 10;
when temp_year >= 40 then
set temp_bounds = temp_sal * 20;
end case;
-- 把计算出来的数据添加到年终奖表中
delete from year_end_bonus where empno=empno and current_years = extract(year from calc_date);
insert into year_end_bonus(current_years, empno, ename, bonus, years, months, days) values (
extract(year from calc_date), empno, temp_name, temp_bounds, temp_year, temp_month, temp_day
);
end //
delimiter ;

最后测试

1
call proc_yearmoney('2023-01-01', 1111);

MySQL存储引擎

InnoDB(5.5以后默认使用)

MySQL5.5以及以后版本中的默认存储引擎,它的有点如下

  1. 灾备恢复性好
  2. 支持事务
  3. 使用行级锁
  4. 支持外键关联
  5. 支持热备份
  6. 对于InnoDB中的表,数据的物理组织形式是簇表,主键索引和数据是在一起的,数据按照主键的物理顺序分布
  7. 实现了缓冲管理,不仅能缓冲索引,也能缓冲数据

MyISAM

特性如下

  1. 不支持事务
  2. 使用表级锁,性能差

缺点

  1. MyISAM容易损坏,灾备差
  2. 只缓存索引

两种存储引擎的大致区别

  1. InnoDB支持事务,MyISAM不支持,如果一系列增删改中只要哪个出错还可以回滚还原,或者不需要保证数据的完整性,那么就可以使用MyISAM,反之就需要使用InnBODB
  2. MyISAM适合以插入为主的引用,以及部分不需要缓存的查询为主的应用,InnoDB适合频繁修改以及涉及到安全性较高,还有频繁查询需要缓存的应用
  3. InnoDB支持外键,MyISAM不支持
  4. 从5.5版本之后,InnoDB是默认引擎
  5. InnoDB不支持fulltext类型的索引
  6. InnoDB不保存表的行数,select count(*) InnBODB需要扫描一遍整个表来计算有多少行,而MyISAM则只需要读出保存好的行数即可。当加上where条件时,MyISAM也需要扫表
  7. 对于自增长的字段,InnoDB必须要包含只有该字段的索引,但是MyISAM可以和其他字段一起建立联合索引
  8. 清空整个表时,InnoDB是一行一行的删除,但MyISAM则会重建表
  9. InnoDB支持行锁,而MyISAM只支持表锁

存储引擎设置方式

1
2
3
-- 在建表时
create table 表名 (
) engine=INNODB;

事务

概念

完成一个事情所需要的一系列的步骤,要么都成功,要么都失败

事务的基本操作

场景:有两个账户:雷哥和稽哥,他们默认都有1000块余额,在这两个账户之间进行转账

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
-- 1 创建account表
create table tb_account(
id int(11) auto_increment,
user_name varchar(30) not null,
account_blance int(11) not null, -- 账户余额
primary key (id)
)ENGINE=INNODB,default charset =UTF8MB4;
-- 2 插入数据
insert into tb_account(user_name,account_blance)
values('雷哥',1000),('稽哥',1000);
-- 3 执行张三向李四转账500
-- ZS账户-500,LS账户+500
-- 下面两个update语句要么同时执行成功要么同时执行失败
-- 执行下列SQL语句会出现问题:张三的钱减少了,李四的钱没有加上。因为此时的两个update语句并没有使用事务来托管
update tb_account set account_blance=account_blance-500 where id=1;
-- 银行转帐异常情况:如机机房停电
update tb_account set account_blance=account_blance+500 where id=2;

为了解决以上问题,就引入了事务机制。

事务的四大特征

原子性:事务每一步都不可再分

一致性:张三和李四账户一共2000块钱,不管转账多少次,总金额都不会变

持久性:当一个事务执行成功,数据会持久化存储到磁盘的数据文件中

隔离性:A事务和B事务同时操作一份数据,相互之间互不影响。

事务的提交方式

  1. 自动提交。默认情况下就是自动提交
  2. 手动提交。
1
2
-- 查看提交方式
select @@autocommit;

事务的开启和提交

开启事务

1
start transaction;

提交事务

1
commit;

回滚事务

1
rollback;

转账操作

1
2
3
4
5
6
-- 开始事务
start transaction;
-- 转账
update tb_account set account_blance=account_blance-500 where id = 1;
-- 转账过程中系统宕机,回滚事务
rollback;

事务隔离级别

并发下的数据问题

脏读

一个事务读到了另一个事务没有提交的数据(破坏了隔离性)

不可重复读问题

同一个事务中多次读取到的数据不一致(破坏了一致性)

幻读

一个事务读取数据,此时另一个事务插入数据,之前的事务读取到了这条数据,就像是产生了幻觉一样

四大事务隔离级别

隔离级别 脏读 不可重复读 幻读
读未提交(read-uncommited)
读已提交(read-commited)
可重复读(repeatable-read)
串行化(serializable)

查询隔离级别

1
select @@tx_isolation;

设置事物的隔离级别

1
set session transaction isolation level read—uncommited

SQL优化之索引

索引是什么

  • 索引的建立对于MySQL的高效运行是很重要的,索引可以大大提高MySQL的检索速度
  • 索引就好比是字典的目录,通过目录去查询一个字或者单词将比直接翻字典更加的快。
  • 索引分为单列索引和组合索引。单列索引一个索引只包含一个列,一个表可以有多个单列索引。组合索引即一个索引包含多个列。
  • 创建索引时,需要确保该索引是应用在SQL查询语句的条件。
  • 索引也是一张表,该表保存了主键与索引字段,并指向实体表的记录。索引也会占用存储空间,每次新增、修改、删除数据时都要维护索引结构,因此在增删改频繁时,索引性能会比较差,索引不能滥用。

准备工作

1
2
3
4
5
6
7
8
9
10
-- 建表
DROP TABLE IF EXISTS person;
CREATE TABLE person (
PID int(11) NOT NULL AUTO_INCREMENT COMMENT '编号',
PNAME varchar(50) NOT NULL COMMENT '姓名',
PSEX varchar(10) NULL DEFAULT NULL COMMENT '性别',
PAGE int(11) NOT NULL COMMENT '年龄',
SAL decimal(7, 2) NULL DEFAULT NULL COMMENT '工资',
PRIMARY KEY (PID)
) ENGINE = InnoDB AUTO_INCREMENT = 7001 CHARACTER SET = utf8mb4 COMMENT = '人员表';

索引

普通索引

创建语法

1
create index 索引名称 on 表名称(字段名称);

给person表的pname字段创建普通索引

1
create index index_person_pname on person(pname);

使用 explain 关键字可以分析SQL是否使用到了索引

1
explain select * from person where pname='张三';

主键也是索引,叫做主键索引

验证模糊查询是否使用到了索引

1
2
3
4
5
6
-- 前后模糊中间精确,不会用到索引
explain select * from person where pname like '%ee%';
-- 前面模糊后面精确,不会用到索引
explain select * from person where pname like '%ee';
-- 前面精确后面模糊,会用到索引
explain select * from person where pname like 'ee%';

索引压力测试

1
2
-- 首先删除索引
alter table person drop index index_person_pname;

插入500w条数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
delimiter //
create procedure insert_person(in max_num int(10))
begin
declare i int default 0;
set autocommit = 0; -- 把autocommit设置成0,这样可以只提交一次,否则。。。。。
repeat
set i = i +1;
insert into person (PID,PNAME,PSEX,PAGE,SAL) values (i,rand_string(5),IF(RAND()>0.5,'男','女'),FLOOR((RAND()*100)+10),FLOOR((RAND()*19000)+1000));
until i = max_num
end repeat;
commit;
end //
delimiter ;

-- 调用
call insert_person(5000000);

测试

1
2
3
4
5
6
7
-- 首先没有索引测试
select * from person where pname='adsa';
-- 创建索引
create index index_person_pname on person(pname);
-- 之后在测试

select * from person where pname='adsa';

问题1:创建索引为什么这么慢

因为索引就像是一个目录,内部采用某种数据结构存储(B+树),创建的过程中会将索引构建成这种数据结构存储到硬盘中,数据量很大时,耗时也会比较久

问题2:没有创建索引和创建索引为什么性能差距这么大

因为索引就像是个目录,有索引的情况下会使用索引匹配数据,之后再通过匹配到的索引去查找数据的位置

唯一索引

创建了唯一索引的列,该列存储的数据是唯一的

1
2
3
4
5
6
-- 语法
create unique index 索引名称 on 表名(列名);
-- 查询
select * from person where mobile = '547454245';
-- 创建唯一索引
create unique index uni_idx_person_mobile on person(mobile);

唯一索引使用explain分析时,如果没有匹配到数据,分析结果会显示没有用到索引,如果匹配到了数据,则显示使用到了唯一索引

组合索引

组合索引是使用一个以上字段一起组成的索引,实际使用中建议创建组合索引

1
2
3
4
-- 测试sql性能
select * from person where pname = 'gafadsdfsa' and psex= '男';
-- 创建page和psex的组合索引
create index idx_person_name_sex on person(pname, psex);

当我们创建了一个(a,b,c)字段构成的组合索引时,实际上相当于创建了(a),(a, b),(a,b,c) 三个索引

全文索引

SQL优化之缓存

MySQL缓存简介

MySQL缓存机制即缓存SQL文本和缓存结果,用键值对形式保存在服务器的内存中,如果运行相同的sql,服务器直接从缓存中获取结果,不再去解析、优化、执行SQL

在表的结构或者数据发生变化时,查询缓存中的数据不再有效,查询缓存之的相关条目将被清空

对于频繁查询的表才需要加缓存,增删改频繁的表不建议加缓存

缓存命中条件

缓存的数据结构是Hash表

以SQL、数据库名、客户端协议等作为key,在判断命中钱,MySQL不会解析SQL,而是使用SQL去查询缓存,SQL上任何字符的不同,比如空格、注释等都会导致缓存不命中。

如果查询有不确定的数据,比如 like now(),此时查询完成后结果都不会被缓存。

工作流程

服务器接收SQL,以SQL和一些其他条件为key查询缓存表

如果缓存命中,直接返回缓存

如果没命中,正常执行SQL

执行完毕SQL之后,将SQL查询结果写入缓存表。

缓存失败

当某个表正在写入数据,则这个表的缓存将会处于失效状态

在InnoDB中,如果某个事务修改了表,则这个表的缓存在事务提交前都会处于失效状态,即在事务提交前,这个表的相关查询都无法被缓存。

缓存内存管理

MySQL缓存机制会在内存中开辟一块内存区来维护缓存数据

MySQL缓存机制将大内存分成小内存块,每个小内存块中存储自身的类型、大小和查询结果数据,还有前后内存块的指针

MySQL缓存机制会在SQL查询开始时就去申请一块内存空间,所以即使缓存数据没有达到这个大小也需要占用申请的内存块空间,如果超出申请的大小,就需要再申请一块,当查询完成发现申请的内存有富余,就会将剩余的内存释放掉,就可能会导致造成内存碎片。

缓存参数配置

查询缓存相关配置

1
show variables like '%query_cache%';

query_cache_type

是否开启缓存

0:关闭

1:打开

2:按需使用查询缓存,只有明确写了SQL_CACHE的查询才会写入缓存

如果不想要有缓存

1
2
select sql_no_cache * from 表名;
select sql_cache * from 表名;

query_cache_size

缓存使用的总内存空间大小,单位是字节,这个值必须是1024整数倍,否则MySQL实际分配的内存可能与这个数值不同

1
set global query_cache_size=1024*1024*10;

query_cache_min_res_unit

分配内存块时的最小单位大小

1
set global query_cache_min_res_unit=8192;

query_cache_limit

MySQL能够缓存的最大结果,如果超出,则增加 Qcache_not_cached的值,并删除查询结果

1
set global query_cache_limit=1048576 * 2;

query_cache_wlock_invalidate

如果某表被锁住了,是否依然能够从缓存中返回数据,默认是OFF,表示的是依然可以返回

session status 中缓存参数

Qcache_free_blocks:缓存池中空闲块的个数

Qcache_free_memory:缓存中空闲内存量

Qcache_hits:缓存命中次数

Qcache_inserts:缓存写入次数

Qcache_lowmen_prunes:因内存不足删除缓存次数

Qcache_not_cached:查询未被缓存次数,例如查询结果超出缓存块大小,查询中包含可变函数等

Qcache_queries_in_cache:当前缓存中缓存的SQL数量

Qcache_total_blocks:缓存总block数

整理碎片

flush query cache;

数据库设计原则

引入三大范式

什么是范式

范式是 复合某一种设计要求的总结,想设计一个结构合理的关系型数据库,必须满足一定的范式

范式的分类

第一范式、第二范式、第三范式、NCNF、第四范式、第五范式

必须保证数据库的合理性

数据库设计关系整个系统的架构,关系到后续的开发效率和运行效率

什么是合理的数据库

结构合理

冗余较小

尽量避免插入删除修改异常

第一范式

最基本的范式,要求数据库表每一列都是不可分割的基本数据项,同一列不能有多个值,简单来说就要保证每列的原子性

第二范式

第二范式需要确保数据表中每一列都与主键相关,而不是与主键的某一部分相关,即在一个表中只能保存一种数据,不可以把多种数据保存到同一张表中。

第三范式

确保数据表中每一列数据都和主键直接相关,而不间接相关

范式的优缺点

优点

结构合理

冗余较小

尽量避免操作异常

缺点

性能降低

避免不了多表查询

应当根据当前情况合理的做出灵活的配置

比如第三范式在有的时候是可以不遵守的,比如设计订单表,就需要冗余进去一些商品基本信息,这是为了能够知道当时买的时候商品叫什么

案例:设计一个商品购物车的数据库

品牌表

用户表

商品表

1
2
3
4
5
6
7
8
9
10
11
create table pro_product (
id int(11) auto_increment,
pro_name varchar(50) not null,
brand_id int(11),
brand_name varchar(30), -- 冗余字段,因为品牌万年不变,不需要考虑修改的情况,即便修改,舍弃这一会儿的性能
pro_pics varchar(512), -- 图片,逗号隔开
pro_price decimal(10, 2) default 0.00, -- 价格,默认0
stock_num int(11) default 0, -- 库存
pro_content mediumtext, -- 商品描述
primary key (id)
);

购物车表

1
2
3
4
5
6
7
8
9
10
create table pro_cart(
id int(11) auto_increment,
product_id int(11), -- 商品id
pro_name varchar(50) not null,
pro_price decimal(10, 2) default 0.00, -- 加入购物车时的价格
cart_num int(11),
create_user int(11) not null,
create_time datetime not null,
primary key(id)
);