MySQL

数据库操作

查询所有数据库

show databases;    --- 固定写法

查询当前数据库

select database();    ---  固定写法

创建数据库

create database [ if not exists ] 数据库名 [ default charset 字符集 ] [ collate 排序规则 ] ;

eg:  创建一个itheima数据库,并且指定字符集
 
create database if not extists itcast default charset utf8mb4;

删除数据库

drop database [ if exists ] 数据库名;

可以加上参数 if exists ,如果数据库存在,再执行删除,否则不执行删除。

切换数据库

use 数据库名;

表操作

查询当前数据库所有表

show tables;        ---- 固定写法

查看指定表结构

desc 表名;

查询指定表的建表语句

show create table 表名;

创建表结构

CREATE TABLE 表名(
字段1 字段1类型 [ COMMENT 字段1注释 ],
......
字段n 字段n类型 [COMMENT 字段n注释 ]
) [ COMMENT 表注释 ] ;

数值类型

image-20230324164336176

字符串类型

image-20230324164524245

日期时间类型

image-20230324164622534

添加字段

ALTER TABLE 表名 ADD 字段名 类型 (长度) [ COMMENT 注释 ] [ 约束 ];

eg:  为emp表增加一个新的字段”昵称”为nickname,类型为varchar(20)

ALTER TABLE emp ADD nickname varchar(20) COMMENT '昵称';

修改数据类型

ALTER TABLE 表名 MODIFY 字段名 新数据类型 (长度);

修改字段名和字段类型

ALTER TABLE 表名 CHANGE 旧字段名 新字段名 类型 (长度) [ COMMENT 注释 ] [ 约束 ];

eg:  将emp表的nickname字段修改为username,类型为varchar(30)

ALTER TABLE emp CHANGE nickname username varchar(30) COMMENT '昵称';

删除字段

ALTER TABLE 表名 DROP 字段名;

eg:  将emp表的字段username删除

ALTER TABLE emp DROP username;

修改表名

ALTER TABLE 表名 RENAME TO 新表名;

eg:   将emp表的表名修改为 employee

ALTER TABLE emp RENAME TO employee;

删除表

DROP TABLE [ IF EXISTS ] 表名;

eg:  如果tb_user表存在,则删除tb_user表

DROP TABLE IF EXISTS tb_user;

删除指定表, 并重新创建表

TRUNCATE TABLE 表名;
注意: 在删除表的时候,表中的全部数据也都会被删除。

数据

给指定字段添加数据

INSERT INTO 表名 (字段名1, 字段名2, ...) VALUES (值1, 值2, ...);c

eg: 给employee表所有的字段添加数据

insert into 
employee(id,workno,name,gender,age,idcard,entrydate)
values(1,'1','Itcast','男',10,'123456789012345678','2000-01-01');

给全部字段添加数据

INSERT INTO 表名 VALUES (值1, 值2, ...);

批量添加数据

INSERT INTO 表名 (字段名1, 字段名2, ...) VALUES (值1, 值2, ...), (值1, 值2, ...), (值1, 值2, ...) ;

修改数据

UPDATE 表名 SET 字段名1 = 值1 , 字段名2 = 值2 , .... [ WHERE 条件 ];

删除数据

DELETE FROM 表名 [ WHERE 条件 ];

DQL 查询语句

SELECT
	字段列表
FROM
	表名列表
WHERE
	条件列表
GROUP BY
	分组字段列表
HAVING
	分组后条件列表
ORDER BY
	排序字段列表
LIMIT
	分页参数

基础查询

查询多个字段
SELECT 字段1, 字段2, 字段3 ... FROM 表名;
SELECT * FROM 表名;
字段设置别名
SELECT 字段1 [ AS 别名1 ] , 字段2 [ AS 别名2 ] ... FROM 表名;
SELECT 字段1 [ 别名1 ] , 字段2 [ 别名2 ] ... FROM 表名;
去除重复记录
SELECT DISTINCT 字段列表 FROM 表名;
distinct只能出现在所有字段的最前面。

eg: 查询公司员工的上班地址有哪些(不要重复)

select distinct workaddress '工作地址' from emp;

# 联合去重
mysql> select distinct deptno,job from emp;
+--------+-----------+
| deptno | job       |
+--------+-----------+
|     20 | CLERK     |
|     30 | SALESMAN  |
|     20 | MANAGER   |
|     30 | MANAGER   |
|     10 | MANAGER   |
|     20 | ANALYST   |
|     10 | PRESIDENT |
|     30 | CLERK     |
|     10 | CLERK     |
+--------+-----------+

统计岗位的数量?
select count(distinct job) from emp;

+---------------------+
| count(distinct job) |
+---------------------+
|                   5 |
+---------------------+

条件查询

条件查询语句
SELECT 字段列表 FROM 表名 WHERE 条件列表;
条件

image-20230324170940284

image-20230324170958978

聚合函数

常见的聚合函数

image-20230324171126222

聚合语法
SELECT 聚合函数(字段列表) FROM 表名;      --   NULL值是不参与所有聚合函数运算的

# 多行处理函数
多行处理函数的特点:输入多行,最终输出的结果是1行。
聚合函数自动忽略NULL。
select sum(comm) from emp where comm is not null; // 不需要额外添加这个过滤条件。sum函数自动忽略NULL。
分组函数也能组合起来用:
		select count(*),sum(sal),avg(sal),max(sal),min(sal) from emp;


# 单行处理函数
重点:所有数据库都是这样规定的:只要有NULL参与的运算结果一定是NULL
ifnull() 空处理函数
ifnull(可能为NULL的数据,被当做什么处理) : 属于单行处理函数。
select ename,ifnull(comm,0) as comm from emp;

分组查询

分组查询语法
SELECT 字段列表 FROM 表名 [ WHERE 条件 ] GROUP BY 分组字段名 [ HAVING 分组后过滤条件 ];
where与having区别

​ 执行时机不同:where是分组之前进行过滤,不满足where条件,不参与分组;而having是分组 之后对结果进行过滤。

​ 判断条件不同:where不能对聚合函数进行判断,而having可以。

​ 分组之后,查询的字段一般为聚合函数和分组字段,查询其他字段无任何意义。

​ 执行顺序: where > 聚合函数 > having 。

​ 支持多字段分组, 具体语法为 : group by columnA,columnB

根据性别分组 , 统计男性员工 和 女性员工的数量

select gender, count(*) from emp group by gender ;

根据性别分组 , 统计男性员工 和 女性员工的平均年龄

select gender, avg(age) from emp group by gender ;

查询年龄小于45的员工 , 并根据工作地址分组 , 获取员工数量大于等于3的工作地址

select workaddress, count(*) address_count from emp where age < 45 group by workaddress having address_count >= 3;

统计各个工作地址上班的男性及女性员工的数量

select workaddress, gender, count(*) '数量' from emp group by gender , workaddress;

找出薪资大于2900
select max(sal),deptno from emp group by deptno having max(sal) > 2900; // 这种方式效率低。
select max(sal),deptno from emp where sal > 2900 group by deptno;  // 效率较高,建议能够使用where过滤的尽量使用where。

where后面不能使用分组函数:
select deptno,avg(sal) from emp where avg(sal) > 2000 group by deptno;	// 错误了,还没有进行分组呢,不能使用分组函数。这种情况只能使用having过滤。

排序查询

排序查询语法
SELECT 字段列表 FROM 表名 ORDER BY 字段1 排序方式1 , 字段2 排序方式2 ;  

​ ASC : 升序(默认值)

​ DESC: 降序

​ 如果是多字段排序,当第一个字段值相同时,才会根据第二个字段进行排序

分页查询

分页查询语法
SELECT 字段列表 FROM 表名 LIMIT 起始索引, 查询记录数 ;

limitMySQL特有的,其他数据库中没有,不通用。(Oracle中有一个相同的机制,叫做rownum

起始索引从0开始,起始索引 = (查询页码 - 1)* 每页显示记录数。

分页查询是数据库的方言,不同的数据库有不同的实现,MySQL中是LIMIT

如果查询的是第一页数据,起始索引可以省略,直接简写为 limit 10

查询第1页员工数据, 每页展示10条记录
select * from emp limit 0,10;
select * from emp limit 10;

查询第2页员工数据, 每页展示10条记录 --------> (页码-1)*页展示记录数
select * from emp limit 10,10;


每页显示3条记录:
第1页:0, 3
第2页:3, 3
第3页:6, 3
第4页:9, 3
第5页:12, 3

每页显示pageSize条记录:
第pageNo页:(pageNo - 1) * pageSize, pageSize

执行顺序image-20230326125438373

函数

字符串函数

image-20230326125725441

数值函数

image-20230326125812525

日期函数

image-20230326125853077

eg:入职天数,就是通过当前日期 - 入职日期,所以需要使用datediff函数来完成。

select name, datediff(curdate(), entrydate) as 'entrydays' from emp order byentrydays desc;

流程函数

image-20230326130109737

约束

在这里插入图片描述

约束是作用于表中字段上的,可以在创建表/修改表的时候添加约束。

非空约束

create table t_user(
	id int,
	username varchar(255) not null,
	password varchar(255)
);
insert into t_user(id,password) values(1,'123');  //编译错误,约束username字段不能为空!

唯一约束

#唯一性约束修饰的字段具有唯一性,不能重复。但可以为null
 
create table t_user(
   id int,
	username varchar(255) unique  //列级约束
);

create table t_user(
   id int,
	username varchar(255)   
	unique(username)			//表级约束
);

#给两个列或者多个列添加unique
create table t_user(
	id int,
	usercode varchar(255),
	username varchar(255),
	unique(usercode,username)  //多个字段联合起来添加一个约束unique 【表级约束】
);


#注意:not null约束只有列级约束,没有表级约束。

主键约束

create table t_user(
  id int primary key,  //列级约束
  username varchar(255),
  email varchar(255)
);

create table t_user(
  id int,
  username varchar(255),
  primary key(id)   //表级约束
);


主键有什么作用?
      根据主键字段的字段数量来划分:
          '单一主键' (推荐的,常用的。)
	  	  '复合主键'(多个字段联合起来添加一个主键约束) (复合主键不建议使用,因为复合主键违背三范式。)
      根据主键性质来划分:
          '自然主键' :主键值最好就是一个和业务没有任何关系的自然数。(这种方式是推荐的)
	      '业务主键' : 主键值和系统的业务挂钩,例如:拿着银行卡的卡号做主键、拿着身份证号做为主键。(不推荐使用) 最好不要拿着和业务挂钩的字段做为主键。因为以后的业务一旦发生改变的时候,主键也可能需要随着发生变化,但有的时候没有办法变化,因为变化可能会导致主键重复。
	      
# 一张表的主键约束只能有1个。(必须记住)


# mysql提供主键值自增:(非常重要)
create table t_user(
    id int primary key auto_increment,  //id字段自动维护一个自增的数字,从1开始,以1递增。
	username varchar(255)
);

外键约束

在这里插入图片描述

添加外键语法(两种)
CREATE TABLE 表名(
	字段名 数据类型,
	...
	[CONSTRAINT] [外键名称] FOREIGN KEY (外键字段名) REFERENCES 主表 (主表列名)
);

ALTER TABLE 表名 ADD CONSTRAINT 外键名称 FOREIGN KEY (外键字段名) REFERENCES 主表 (主表列名) ;


eg: 为emp表的dept_id字段添加外键约束,关联dept表的主键id。

alter table emp add constraint fk_emp_dept_id foreign key (dept_id) references dept(id);
删除外键
ALTER TABLE 表名 DROP FOREIGN KEY 外键名称;

eg: 删除emp表的外键fk_emp_dept_id。

alter table emp drop foreign key fk_emp_dept_id;
删除/更新行为

在这里插入图片描述

删除更新语法
ALTER TABLE 表名 ADD CONSTRAINT 外键名称 FOREIGN KEY (外键字段) REFERENCES 主表名 (主表字段名) ON UPDATE CASCADE ON DELETE CASCADE(这个就是上面的可选参数);

多表查询

一对多

在这里插入图片描述

案例: 部门 与 员工的关系
关系: 一个部门对应多个员工,一个员工对应一个部门
实现: 在多的一方建立外键,指向一的一方的主键

多对多

在这里插入图片描述

一对一

在这里插入图片描述

select * from emp , dept     ----    笛卡尔积

select * from emp , dept where emp.dept_id = dept.id   ---- 消除无效的笛卡尔积

在这里插入图片描述

连接查询

内连接:
	假设A和B表进行连接,使用内连接的话,凡是A表和B表能够匹配上的记录查询出来,这就是内连接。
	AB两张表没有主副之分,两张表是平等的。

外连接:
	假设A和B表进行连接,使用外连接的话,AB两张表中有一张表是主表,一张表是副表,主要查询主表中的数据,捎带着查询副表,当副表中的数据没有和主表中的数据匹配上,副表自动模拟出NULL与之匹配。

内连接

在这里插入图片描述

隐式内连接
SELECT 字段列表 FROM 表1 , 表2 WHERE 条件 ... ;

eg: 查询每一个员工的姓名 , 及关联的部门的名称

select e.name,d.name from emp e , dept d where e.dept_id = d.id;      #SQL92 太老以后不用这个语法
显式内连接
SELECT 字段列表 FROM 表1 [ INNER ] JOIN 表2 ON 连接条件 ... ;
#inner可以省略的,带着inner目的是可读性好一些。
#语法:
	...
		A
	join
		B
	on
		连接条件
	where
		...

#eg: 查询每一个员工的姓名 , 及关联的部门的名称

select e.name, d.name from emp e join dept d on e.dept_id = d.id;     #SQL99 常用语法    

#SQL99语法结构更清晰一些:表的连接条件和后来的where条件分离了。

一旦为表起了别名,就不能再使用表名来指定对应的字段了,此时只能够使用别名来指定字 段。

外连接

左外连接

左外连接相当于查询表1(左表)的所有数据,当然也包含表1和表2交集部分的数据。

SELECT 字段列表 FROM 表1 LEFT [ OUTER ] JOIN 表2 ON 条件 ... ;

eg: 查询emp表的所有数据, 和对应的部门信息

select e.*, d.name from emp e left outer join dept d on e.dept_id = d.id;
select e.*, d.name from emp e left join dept d on e.dept_id = d.id;
右外连接

右外连接相当于查询表2(右表)的所有数据,当然也包含表1和表2交集部分的数据。

SELECT 字段列表 FROM 表1 RIGHT [ OUTER ] JOIN 表2 ON 条件 ... ;

eg :查询dept表的所有数据, 和对应的员工信息

select d.*, e.* from emp e right outer join dept d on e.dept_id = d.id
select d.*, e.* from dept d left outer join emp e on e.dept_id = d.id
左外连接和右外连接是可以相互替换的,只需要调整在连接查询时SQL中,表结构的先后顺
序就可以了。而我们在日常开发使用时,更偏向于左外连接。

自连接

自连接语法

自连接查询,顾名思义,就是自己连接自己,也就是把一张表连接查询多次 ,自连接查询,可以是内连接查询,也可以是外连接查询。

SELECT 字段列表 FROM 表A 别名A JOIN 表A 别名B ON 条件 ... ;

eg:  查询员工 及其 所属领导的名字

select a.name , b.name from emp a , emp b where a.managerid = b.id

eg: 查询所有员工 emp 及其领导的名字 emp , 如果员工没有领导, 也需要查询出来

select a.name '员工', b.name '领导' from emp a left join emp b on a.managerid =b.id;
在自连接查询中,必须要为表起别名,要不然我们不清楚所指定的条件、返回的字段,到底是哪一张表的字段。

联合查询

对于union查询,就是把多次查询的结果合并起来,形成一个新的查询结果集

联合查询语句
SELECT 字段列表 FROM 表A ...
UNION [ ALL ]
SELECT 字段列表 FROM 表B ....;

eg: 将薪资低于 5000 的员工 , 和 年龄大于 50 岁的员工全部查询出来

select * from emp where salary < 5000
union all
select * from emp where age > 50;

union all查询出来的结果,仅仅进行简单的合并,并未去重。
union 联合查询,会对查询出来的结果进行去重处理。

对于联合查询的多张表的列数必须保持一致,字段类型也需要保持一致。

union all 会将全部的数据直接合并在一起,union 会对合并之后的数据去重。

子查询

子查询语句

子查询外部的语句可以是INSERT / UPDATE / DELETE / SELECT 的任何一个。

SELECT * FROM t1 WHERE column1 = ( SELECT column1 FROM t2 )

子查询可以出现在哪里?
		select
			..(select).
		from
			..(select).
		where
			..(select).
标量子查询

子查询返回的结果是单个值(数字、字符串、日期等),常用的操作符:= <> > >= < <=

eg: 根据 "销售部" 部门ID, 查询员工信息

select * from emp where dept_id = (select id from dept where name = '销售部')

eg: 查询在 "方东白" 入职之后的员工信息

select * from emp where entrydate > (select entrydate from emp where name = '方东白');
列子查询

子查询返回的结果是一列(可以是多行),常用的操作符:IN 、NOT IN 、 ANY 、SOME 、 ALL
在这里插入图片描述

eg: 查询 "销售部" 和 "市场部" 的所有员工信息

select * from emp where dept_id in (select id from dept where name = '销售部' orname = '市场部');

eg: 查询比 财务部 所有人工资都高的员工信息

select * from emp where salary > all ( select salary from emp where dept_id =
(select id from dept where name = '财务部') );

eg: 查询比研发部其中任意一人工资高的员工信息

select * from emp where salary > any ( select salary from emp where dept_id =
(select id from dept where name = '研发部') );
行子查询

子查询返回的结果是一行(可以是多列), 常用的操作符:= 、<> 、IN 、NOT IN

eg: 查询与 "张无忌" 的薪资及直属领导相同的员工信息 

select * from emp where (salary,managerid) = (select salary, managerid from emp where name = '张无忌');
表子查询

子查询返回的结果是多行多列,常用的操作符:IN

eg: 查询与 "鹿客" , "宋桥" 的职位和薪资相同的员工信息

select * from emp where (job,salary) in ( select job, salary from emp where name = '鹿客' or name = '宋桥' );

基础内容联系

建表语句

DROP TABLE IF EXISTS EMP;
DROP TABLE IF EXISTS DEPT;
DROP TABLE IF EXISTS SALGRADE;

CREATE TABLE DEPT
       (DEPTNO int(2) not null ,
	DNAME VARCHAR(14) ,
	LOC VARCHAR(13),
	primary key (DEPTNO)
	);
CREATE TABLE EMP
       (EMPNO int(4)  not null ,
	ENAME VARCHAR(10),
	JOB VARCHAR(9),
	MGR INT(4),
	HIREDATE DATE  DEFAULT NULL,
	SAL DOUBLE(7,2),
	COMM DOUBLE(7,2),
	primary key (EMPNO),
	DEPTNO INT(2) 
	)
	;

CREATE TABLE SALGRADE
      ( GRADE INT,
	LOSAL INT,
	HISAL INT );


INSERT INTO DEPT ( DEPTNO, DNAME, LOC ) VALUES ( 
10, 'ACCOUNTING', 'NEW YORK'); 
INSERT INTO DEPT ( DEPTNO, DNAME, LOC ) VALUES ( 
20, 'RESEARCH', 'DALLAS'); 
INSERT INTO DEPT ( DEPTNO, DNAME, LOC ) VALUES ( 
30, 'SALES', 'CHICAGO'); 
INSERT INTO DEPT ( DEPTNO, DNAME, LOC ) VALUES ( 
40, 'OPERATIONS', 'BOSTON'); 
commit;
 
INSERT INTO EMP ( EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM,
DEPTNO ) VALUES ( 
7369, 'SMITH', 'CLERK', 7902,  '1980-12-17'
, 800, NULL, 20); 
INSERT INTO EMP ( EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM,
DEPTNO ) VALUES ( 
7499, 'ALLEN', 'SALESMAN', 7698,  '1981-02-20'
, 1600, 300, 30); 
INSERT INTO EMP ( EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM,
DEPTNO ) VALUES ( 
7521, 'WARD', 'SALESMAN', 7698,  '1981-02-22'
, 1250, 500, 30); 
INSERT INTO EMP ( EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM,
DEPTNO ) VALUES ( 
7566, 'JONES', 'MANAGER', 7839,  '1981-04-02'
, 2975, NULL, 20); 
INSERT INTO EMP ( EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM,
DEPTNO ) VALUES ( 
7654, 'MARTIN', 'SALESMAN', 7698,  '1981-09-28'
, 1250, 1400, 30); 
INSERT INTO EMP ( EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM,
DEPTNO ) VALUES ( 
7698, 'BLAKE', 'MANAGER', 7839,  '1981-05-01'
, 2850, NULL, 30); 
INSERT INTO EMP ( EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM,
DEPTNO ) VALUES ( 
7782, 'CLARK', 'MANAGER', 7839,  '1981-06-09'
, 2450, NULL, 10); 
INSERT INTO EMP ( EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM,
DEPTNO ) VALUES ( 
7788, 'SCOTT', 'ANALYST', 7566,  '1987-04-19'
, 3000, NULL, 20); 
INSERT INTO EMP ( EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM,
DEPTNO ) VALUES ( 
7839, 'KING', 'PRESIDENT', NULL,  '1981-11-17'
, 5000, NULL, 10); 
INSERT INTO EMP ( EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM,
DEPTNO ) VALUES ( 
7844, 'TURNER', 'SALESMAN', 7698,  '1981-09-08'
, 1500, 0, 30); 
INSERT INTO EMP ( EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM,
DEPTNO ) VALUES ( 
7876, 'ADAMS', 'CLERK', 7788,  '1987-05-23'
, 1100, NULL, 20); 
INSERT INTO EMP ( EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM,
DEPTNO ) VALUES ( 
7900, 'JAMES', 'CLERK', 7698,  '1981-12-03'
, 950, NULL, 30); 
INSERT INTO EMP ( EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM,
DEPTNO ) VALUES ( 
7902, 'FORD', 'ANALYST', 7566,  '1981-12-03'
, 3000, NULL, 20); 
INSERT INTO EMP ( EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM,
DEPTNO ) VALUES ( 
7934, 'MILLER', 'CLERK', 7782,  '1982-01-23'
, 1300, NULL, 10); 
commit;
 
INSERT INTO SALGRADE ( GRADE, LOSAL, HISAL ) VALUES ( 
1, 700, 1200); 
INSERT INTO SALGRADE ( GRADE, LOSAL, HISAL ) VALUES ( 
2, 1201, 1400); 
INSERT INTO SALGRADE ( GRADE, LOSAL, HISAL ) VALUES ( 
3, 1401, 2000); 
INSERT INTO SALGRADE ( GRADE, LOSAL, HISAL ) VALUES ( 
4, 2001, 3000); 
INSERT INTO SALGRADE ( GRADE, LOSAL, HISAL ) VALUES ( 
5, 3001, 9999); 
commit;

dept表

image-20231027100418467

emp表

image-20231027100454911

salgrade

image-20231027100513547

#1、取得每个部门最高薪水的人员名称
select e.ename, e.sal , e.deptno from (select  e.deptno, max(e.sal) max from emp e group by e.deptno) t join emp e on t.deptno = e.deptno and t.max = e.sal 

#2、哪些人的薪水在部门的平均薪水之上
select e.ename , e.sal from (select e.deptno, avg(e.sal) avg from emp e group by e.deptno) t join emp e on t.deptno = e.deptno and e.sal > t.avg 
       
#3、取得部门中(所有人的)平均的薪水等级
select e.deptno, avg(s.grade) from emp e left join salgrade s on e.sal between  s.losal and s.hisal group by e.deptno 

#4、不准用函数(Max),取得最高薪水
select sal from emp order by sal desc limit 1;       #降序取第一个

select sal from emp where sal not in (select distinct a.sal from emp a join emp b on a.sal < b.sal)             #先查出不是最大的值,再排除


#5、取得平均薪水最高的部门的部门编号
#第一种
select e.deptno from emp e group by e.deptno order by avg(e.sal) desc limit 1
#第二种
select deptno , avg(sal) avgsal from emp  group by deptno having avgsal = (select max(t.avgsal) from (select e.deptno , avg(e.sal) avgsal from emp e group by e.deptno) t)


#6、取得平均薪水最高的部门的部门名称
select d.dname,avg(e.sal) avgsal from emp e left join dept d on e.deptno = d.deptno group by e.deptno  order by avgsal desc limit 1  

#7、求平均薪水的等级最低的部门的部门名称
select d.dname, avg(s.grade) avgsgrade from emp e left join salgrade s on e.sal between  s.losal and s.hisal left join dept d on e.deptno = d.deptno group by e.deptno order by avgsgrade limit 1 

#8、取得比普通员工(员工代码没有在mgr字段上出现的)的最高薪水还要高的领导人姓名
select ename, sal from emp where ename in (select distinct a.ename from emp a join emp b on a.empno = b.mgr) and sal > (select max(sal) from emp where ename not in (select distinct a.ename from emp a join emp b on a.empno = b.mgr ))

#9、取得薪水最高的前五名员工
select ename, sal from emp order by sal desc limit 5


#10、取得薪水最高的第六到第十名员工
select ename, sal from emp order by sal desc limit 5 , 5

#11、取得最后入职的5名员工
select ename, hiredate from emp order by hiredate desc limit 5

#12、取得每个薪水等级有多少员工
select s.grade, count(*) from salgrade s left join emp e on e.sal between  s.losal and s.hisal group by s.grade

#14、列出所有员工及领导的姓名
select a.ename  ,ifnull(b.ename, '没有上级')  from emp a left join emp b on a.mgr = b.empno

#15、列出受雇日期早于其直接上级的所有员工的编号,姓名,部门名称
-- select a.empno , a.ename from emp a left join emp b on a.mgr = b.empno and a.hiredate > b.hiredate

#16、列出部门名称和这些部门的员工信息,同时列出那些没有员工的部门.
select d.dname, e.* from emp e right join dept d on e.deptno = d.deptno

#17、列出至少有5个员工的所有部门
select dept.dname, count(*) from emp join dept on dept.DEPTNO = emp.DEPTNO group by  emp.deptno  having count(*) >= 5 

#18、列出薪金比"SMITH"多的所有员工信息.
select * from emp where sal > (select sal from emp where ename = 'SMITH')

事务

事务操作

控制事务一
查看/设置事务提交方式
SELECT @@autocommit ;
SET @@autocommit = 0 ;

提交事务
COMMIT;

回滚事务
ROLLBACK;

上述的这种方式,我们是修改了事务的自动提交行为, 把默认的自动提交修改为了手动提交, 此时我们执行的DML语句都不会提交, 需要手动的执行commit进行提交。
控制事务二
开启事务
START TRANSACTION 或 BEGIN ;

提交事务
COMMIT;

回滚事务
ROLLBACK;
-- 如果执行过程中报错, 则回滚事务
-- rollback;

并发事务问题

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

事务隔离级别

在这里插入图片描述

查看事务隔离级别
SELECT @@TRANSACTION_ISOLATION;
设置事务隔离级别
SET [ SESSION | GLOBAL ] TRANSACTION ISOLATION LEVEL { READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE }
注意:事务隔离级别越高,数据越安全,但是性能越低。

存储引擎

建表时指定存储引擎 ---- 默认存储引擎: InnoDB

CREATE TABLE 表名(
	字段1 字段1类型 [ COMMENT 字段1注释 ] ,
	......
	字段n 字段n类型 [COMMENT 字段n注释 ]
) ENGINE = INNODB [ COMMENT 表注释 ] ;


create table my_myisam(
	id int,
	name varchar(10)
) engine = MyISAM ;


create table my_memory(
	id int,
	name varchar(10)
) engine = Memory ;

查询当前数据库支持的存储引擎

show engines;

查询建表语句

show create table account;

InnoDB

InnoDB是一种兼顾高可靠性和高性能的通用存储引擎

特点
	DML操作遵循ACID模型,支持事务;
​	行级锁,提高并发访问性能;
​	支持外键FOREIGN KEY约束,保证数据的完整性和正确性;
文件

xxx.ibd: xxx代表的是表名,innoDB引擎的每张表都会对应这样一个表空间文件,存储该表的表结 构(frm-早期的 、sdi-新版的)、数据和索引。

查看是否开启,参数:innodb_file_per_table

show variables like 'innodb_file_per_table';

如果该参数开启,代表对于InnoDB引擎的表,每一张表都对应一个ibd文件,文件都放在 mysql的Data文件下,这个目录下有很多文件 夹,不同的文件夹代表不同的数据库

mysql提供的一个指令 ibd2sdi ,通过该指令就可以从ibd文件中提取sdi信息,而sdi数据字典信息中就包含该表的表结构。

逻辑存储结构

image-20230530170022050

	'表空间' : InnoDB存储引擎逻辑结构的最高层,ibd文件其实就是表空间文件,在表空间中可以 包含多个Segment段。 

​	'段' : 表空间是由各个段组成的, 常见的段有数据段、索引段、回滚段等。InnoDB中对于段的管 理,都是引擎自身完成,不需要人为对其控制,一个段中包含多个区。 

​	'区' : 区是表空间的单元结构,每个区的大小为1M。 默认情况下, InnoDB存储引擎页大小为 16K, 即一个区中一共有64个连续的页。 

​	'页' : 页是组成区的最小单元,页也是InnoDB 存储引擎磁盘管理的最小单元,每个页的大小默 认为 16KB。为了保证页的连续性,InnoDB 存储引擎每次从磁盘申请 4-5 个区。 

​	'行' : InnoDB 存储引擎是面向行的,也就是说数据是按行进行存放的,在每一行中除了定义表时 所指定的字段以外,还包含两个隐藏字段

MyISAM

MyISAM是MySQL早期的默认存储引擎。

特点
	不支持事务,不支持外键
​	支持表锁,不支持行锁
​	访问速度快
文件
	xxx.sdi:存储表结构信息
​	xxx.MYD: 存储数据
​	xxx.MYI: 存储索引

Memory

Memory引擎的表数据时存储在内存中的,由于受到硬件问题、或断电问题的影响,只能将这些表作为临时表或缓存使用。

特点
	内存存放
​	 hash索引(默认)
文件
xxx.sdi:存储表结构信息

区别及特点

在这里插入图片描述

InnoDB引擎与MyISAM引擎的区别 ?
①. InnoDB引擎, 支持事务, 而MyISAM不支持。
②. InnoDB引擎, 支持行锁和表锁, 而MyISAM仅支持表锁, 不支持行锁。
③. InnoDB引擎, 支持外键, 而MyISAM是不支持的。

存储引擎选择

	InnoDB: 是Mysql的默认存储引擎,支持事务、外键。如果应用对事务的完整性有比较高的要 求,在并发条件下要求数据的一致性,数据操作除了插入和查询之外,还包含很多的更新、删除操 作,那么InnoDB存储引擎是比较合适的选择。 
​	MyISAM : 如果应用是以读操作和插入操作为主,只有很少的更新和删除操作,并且对事务的完 整性、并发性要求不是很高,那么选择这个存储引擎是非常合适的。 MEMORY:将所有数据保存在内存中,访问速度快,通常用于临时表及缓存。
​	MEMORY的缺陷就是 对表的大小有限制,太大的表无法缓存在内存中,而且无法保障数据的安全性。

索引

介绍

优势:
​	提高数据检索的效率,降低数据库的IO成本
​	通过索引列对数据进行排序,降低数据排序的成本,降低CPU的消耗。
劣势:
​	索引列也是要占用空间的。
​	索引大大提高了查询效率,同时却也降低更新表的速度, 如对表进行INSERT、UPDATE、DELETE时,效率降低。

索引结构

MySQL的索引是在存储引擎层实现的,不同的存储引擎有不同的索引结构,主要包含以下几种:

	'B+Tree索引':最常见的索引类型,大部分引擎都支持 B+ 树索引
​	'Hash索引':底层数据结构是用哈希表实现的, 只有精确匹配索引列的查询才有效, 不 支持范围查询
​	'R-tree(空间索引)':空间索引是MyISAM引擎的一个特殊索引类型,主要用于地理空间数据类 型,通常使用较少
​	'Full-text(全文 索引)':是一种通过建立倒排索引,快速匹配文档的方式。类似于 Lucene,Solr,ES

注意: 我们平常所说的索引,如果没有特别指明,都是指B+树结构组织的索引。

image-20230530171633136

B-Tree

B-Tree,B树是一种多叉路衡查找树,相对于二叉树,B树每个节点可以有多个分支,即多叉。

以一颗最大度数(max-degree)为5(5阶)的b-tree为例,那这个B树每个节点最多存储4个key,5 个指针:

image-20230530171750937

树的度数指的是一个节点的子节点个数。

特点:

	5阶的B树,每一个节点最多存储4个key,对应5个指针。
​	一旦节点存储的key数量到达5,就会裂变,中间元素向上分裂。
​	在B树中,非叶子节点和叶子节点都会存放数据。
B+Tree

B+Tree是B-Tree的变种,我们以一颗最大度数(max-degree)为4(4阶)的b+tree为例

image-20230530172034250

我们可以看到,两部分:

	绿色框框起来的部分,是索引部分,仅仅起到索引数据的作用,不存储数据。

​	红色框框起来的部分,是数据存储部分,在其叶子节点中要存储具体的数据。
B+Tree 与 B-Tree区别
所有的数据都会出现在叶子节点。

叶子节点形成一个单向链表。

非叶子节点仅仅起到索引数据作用,具体的数据都是在叶子节点存放的。
MySQL中优化B+Tree

MySQL索引数据结构对经典的B+Tree进行了优化。在原B+Tree的基础上,增加一个指向相邻叶子节点 的链表指针,就形成了带有顺序指针的B+Tree,提高区间访问的性能,利于排序。

image-20230530172445863

Hash

MySQL中除了支持B+Tree索引,还支持一种索引类型—Hash索引。

image-20230530172534711

特点

Hash索引只能用于对等比较(=,in),不支持范围查询(between,>,< ,...)

无法利用索引完成排序操作

查询效率高,通常(不存在hash冲突的情况)只需要一次检索就可以了,效率通常要高于B+tree索引

在MySQL中,支持hash索引的是Memory存储引擎。 而InnoDB中具有自适应hash功能,hash索引是 InnoDB存储引擎根据B+Tree索引在指定条件下自动构建的。

为什么InnoDB存储引擎选择使用B+tree索引结构?

	相对于二叉树,层级更少,搜索效率高;

​	对于B-tree,无论是叶子节点还是非叶子节点,都会保存数据,这样导致一页中存储 的键值减少,指针跟着减少,要同样保存大量数据,只能增加树的高度,导致性能降低;

​	相对Hash索引,B+tree支持范围匹配及排序操作;

索引分类

在MySQL数据库,将索引的具体类型主要分为以下几类:主键索引、唯一索引、常规索引、全文索引。

在这里插入图片描述

#主键索引
1.表中的列设定为主键后,数据库会自动建立主键索引。
2.单独创建和删除主键索引语法:
	创建主键索引语法: alter table 表名 add primary key (字段);
	删除主键索引语法: alter table 表名 drop primary key;
	
#唯一索引
1. 表中的列创建了唯一约束时,数据库会自动建立唯一索引。
2. 单独创建和删除唯一索引语法:
	创建唯一索引语法:alter table 表名 add unique 索引名(字段); 或 create unique index 索引名 on 表名(字段);
	删除唯一索引语法:drop index 索引名 on 表名;
	
#单值索引
即一个索引只包含单个列,一个表可以有多个单值索引。
1. 建表时可随表一起建立单值
2. 单独创建和删除单值索引:
	创建单值索引: alter table 表名 add index 索引名(字段); 或 create index 索引名 on 表名(字段);
	删除单值索引:drop index 索引名 on 表名;
	
#复合索引
即一个索引包含多个列:
1. 建表时可随表一起建立复合索引
2. 单独创建和删除复合索引:
	创建复合索引:create index 索引名 on 表名(字段 1,字段 2); 或 alter table 表名 add index 索引名(字段,字段 2);
 	删除复合索引: drop index 索引名 on 表名; 

聚集索引&二级索引

而在在InnoDB存储引擎中,根据索引的存储形式,又可以分为以下两种:

在这里插入图片描述

聚集索引选取规则:

​	如果存在主键,主键索引就是聚集索引。
​	如果不存在主键,将使用第一个唯一(UNIQUE)索引作为聚集索引。
​	如果表没有主键,或没有合适的唯一索引,则InnoDB会自动生成一个`rowid`作为隐藏的聚集索引。

聚集索引和二级索引的具体结构如下:

image-20230530173603857

聚集索引的叶子节点下挂的是这一行的数据 。

二级索引的叶子节点下挂的是该字段值对应的主键值。

image-20230530173754952

具体过程如下:

​ ①. 由于是根据name字段进行查询,所以先根据name='Arm’到name字段的二级索引中进行匹配查 找。但是在二级索引中只能查找到 Arm 对应的主键值 10。

​ ②. 由于查询返回的数据是*,所以此时,还需要根据主键值10,到聚集索引中查找10对应的记录,最终找到10对应的行row。

​ ③. 最终拿到这一行的数据,直接返回即可。

回表查询: 这种先到二级索引中查找数据,找到主键值,然后再到聚集索引中根据主键值,获取数据的方式,就称之为回表查询。

索引语法

创建索引

CREATE [ UNIQUE | FULLTEXT ] INDEX index_name ON table_name (index_col_name,... ) ;

查看索引

SHOW INDEX FROM table_name ;

删除索引

DROP INDEX index_name ON table_name ;

SQL性能分析

SQL执行频率

MySQL 客户端连接成功后,通过 show [session|global] status 命令可以提供服务器状态信 息。通过如下指令,可以查看当前数据库的INSERT、UPDATE、DELETE、SELECT的访问频次

-- session 是查看当前会话 ;

-- global 是查询全局数据 ;

SHOW GLOBAL STATUS LIKE 'Com_______';

Com_delete: 删除次数
Com_insert: 插入次数
Com_select: 查询次数
Com_update: 更新次数

image-20230530174434545

慢查询日志

慢查询日志记录了所有执行时间超过指定参数(long_query_time,单位:秒,默认10秒)的所有SQL语句的日志。

image-20230530174627229

#如果要开启慢查询日志,需要在MySQL的配置文件(/etc/my.cnf)中配置如下信息

# 开启MySQL慢日志查询开关
slow_query_log=1
# 设置慢日志的时间为2秒,SQL语句执行时间超过2秒,就会视为慢查询,记录慢查询日志
long_query_time=2

#查看时间: 
SHOW VARIABLES LIKE 'long_query_time%';

查看超时的 sql 记录日志:Mysql 的数据文件夹下 5.5\Data\设备名称-slow.log
'注意':非调优场景下,一般不建议启动改参数,慢查询日志支持将日志记录写入文件,开启慢查询日志会或多或少带来一定的性能影响

重新启动MySQL服务器进行测试,查看慢日志文件中记录的信息

systemctl restart mysqld

/var/lib/mysql/localhost-slow.log
profile

show profiles 能够在做SQL优化时帮助我们了解时间都耗费到哪里去了。通过have_profiling 参数,能够看到当前MySQL是否支持profile操作:

SELECT @@have_profiling ;

image-20230530175251257

当前MySQL是支持 profile操作的,但是开关是关闭的。可以通过set语句在 session/global级别开启profiling:

SET profiling = 1;

查看耗时情况

-- 查看每一条SQL的耗时基本情况
show profiles;
-- 查看指定query_id的SQL语句各个阶段的耗时情况
show profile for query query_id;
-- 查看指定query_id的SQL语句CPU的使用情况
show profile cpu for query query_id;
explain

EXPLAIN 或者 DESC命令获取 MySQL 如何执行 SELECT 语句的信息,包括在 SELECT 语句执行 过程中表如何连接和连接的顺序。

-- 直接在select语句之前加上关键字 explain / desc
EXPLAIN SELECT 字段列表 FROM 表名 WHERE 条件 ;

image-20230530175539378

Explain 执行计划中各个字段的含义:

​	id:select查询的序列号,表示查询中执行select子句或者是操作表的顺序 
		id相同,执行顺序从上到下
   		id 不同,如果是子查询,id 的序号会递增,id 值越大优先级越高,则先被执行。
   		id 相同和不同都存在时,id 相同的可以理解为一组,从上往下顺序执行,所有组中,id值越大,优先级越高越先执行。

​	select_type:表示 SELECT 的类型,常见的取值有:       -- #不重要
		SIMPLE(简单的 select 查询,查询中不包含子查询或者 表连接(UNION))、
		PRIMARY(查询中若包含任何复杂的子部分,最外层查询则被标记为 Primary)、 
		UNION(UNION 中的第二个或者后面的查询语句)、 
		DERIVED:在 FROM 列表中包含的子查询被标记为 DERIVED(衍生),MySQL 会递归执行这些子查询, 把结果放在临时表里、
		SUBQUERY(SELECT/WHERE之后包含了子查询) 

​	table
	显示这一行的数据是关于哪张表的。

​	type:表示连接类型,性能由好到差的连接类型为NULL、system、const、 eq_ref、ref、range、 index、all 。  #重要
		System:表只有一行记录(等于系统表),这是 const 类型的特列,平时不会出现,这个也可以忽略不计。
		Const:表示通过索引一次就找到了,根据主键或者唯一索引时会出现这个,const 用于比较 primary key 或者 unique 索引。因为只匹配一行数据,所以很快,如将主键置于 where 列表中,MySQL 就能将该查询转换为一个常量。
		eq_ref:唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见于主键或唯一索引扫描
		Ref:非唯一性索引扫描,返回匹配某个单独值的所有行。本质上也是一种索引访问,它返回所有匹配某个单独值的行,然而,它可能会找到多个符合条件的行,所以他应该属于查找和扫描的混合体。
		Range:只检索给定范围的行,使用一个索引来选择行。key列显示使用了哪个索引一般就是在你的 where 语句中出现了 between、<、>、in 等的查询这种范围扫描索引扫描比全表扫描要好,因为它只需要开始于索引的某一点,而结束语另一点,不用扫描全部索引。
		Index:index 与 ALL 区别为 index 类型只遍历索引树。这通常比ALL 快,因为索引文件通常比数据文件小。也就是说虽然 all 和 Index 都是读全表,但 index是从索引中读取的,而 all 是从硬盘中读的。
		all   --- 没有索引,全表扫描,将遍历全表以找到匹配的行。

​	possible_key:显示可能应用在这张表上的索引,一个或多个。查询涉及到的字段上如果存在索引,则改
索引将会被列出来,但不一定会被查询实际使用上。

​	key:实际使用的索引,如果为NULL,则没有使用索引。

​	key_len:表示索引中使用的字节数, 该值为索引字段最大可能长度,并非实际使用长度,在不损失精确性的前提下,'长度越短越好'

​	ref:显示索引的哪一列被使用了。哪些列或常量被用于查找索引列上的值。

​	rows:MySQL认为必须要执行查询的行数,在innodb引擎的表中,是一个估计值, 可能并不总是准确的。'一般越少越好'。

​	filtered:表示返回结果的行数占需读取行数的百分比, 'filtered 的值越大越好'。

​	Extra:一些常见的重要的额外信息:
		Using filesort:MySQL 无法利用索引完成的排序操作称为“文件排序”。
		Using temporary:Mysql 在对查询结果排序时使用临时表,常见于排序 order by 和分组查询 group by。
		Using where; Using Index:查找使用了索引,但是需要的数据都在索引列中能找到,所以不需要回表查询数据
		Using index:表示索引被用来执行索引键值的查找,避免访问了表的数据行,效率不错。
		Using where:表示使用了 where 过滤。
		Using index condition:查找使用了索引,但是需要回表查询数据

索引使用

最左前缀法则

如果索引了多列(联合索引),要遵守最左前缀法则。最左前缀法则指的是查询从索引的最左列开始, 并且不跳过索引中的列。如果跳跃某一列,索引将会部分失效(后面的字段索引失效)。

在 tb_user 表中,有一个联合索引,这个联合索引涉及到三个字段,顺序分别为:profession, age,status。

image-20230530180456733

对于最左前缀法则指的是,查询时,最左边的列,也就是profession必须存在,否则索引全部失效。 而且中间不能跳过某一列,否则该列后面的字段索引将失效。

注意 : 最左前缀法则中指的最左边的列,是指在查询时,联合索引的最左边的字段(即是 第一个字段)必须存在,与我们编写SQL时,条件编写的先后顺序无关

范围查询

联合索引中,出现范围查询(>,<),范围查询右侧的列索引失效。

但是(>=,<=)并不会导致索引失效

所以,在业务允许的情况下,尽可能的使用类似于 >= 或 <= 这类的范围查询,而避免使用 > 或 < 。

索引失效情况

'最佳左前缀法则':
如果索引了多列,要遵循最左前缀法则,指的是查询从索引的最左前列开始并且不跳过索引中的列。

'索引列运算'
不在索引列上做任何计算、函数操作,会导致索引失效而转向全表扫描。

'存储引擎不能使用索引中范围条件右边的列。'   
    
'字符串不加引号'
字符串类型字段使用时,不加引号,索引将失效。数 据库存在隐式类型转换,索引将失效

'Mysql 在使用不等于时无法使用索引会导致全表扫描'
    
'is null 可以使用索引,但是 is not null 无法使用索引。'
    
'模糊查询(%加在头部会失效)'
如果仅仅是尾部模糊匹配,索引不会失效。如果是头部模糊匹配,索引失效。
在like模糊查询中,在关键字后面加%,索引可以生效。而如果在关键字 前面加了%,索引将会失效。

'or连接条件'
用or分割开的条件, 如果or前的条件中的列有索引,而后面的列中没有索引,那么涉及的索引都不会被用到。

'数据分布影响'
如果MySQL评估使用索引比全表更慢,则不使用索引。
   
因为MySQL在查询时,会评估使用索引的效率与走全表扫描的效率,如果走全表扫描更快,则放弃 索引,走全表扫描。 因为索引是用来索引少量数据的,如果通过索引查询返回大批量的数据,则还不 如走全表扫描来的快,此时索引就会失效。
因此,is null 、is not null是否走索引,得具体情况具体 分析,并不是固定的。
    
'建议'1.对于单值索引,尽量选择针对当前查询字段过滤性更好的索引。
    2.对于组合索引,当前 where 查询中过滤性更好的字段在索引字段顺序中位置越靠前越好。
    3.对于组合索引,尽量选择能够包含在当前查询中 where 子句中更多字段的索引。
    4.尽可能通过分析统计信息和调整 query 的写法来达到选择合适索引的目的。

image-20231027112716327

SQL提示

1). use index : 建议MySQL使用哪一个索引完成此次查询(仅仅是建议,mysql 内部还会再次进行评估)。

explain select * from tb_user use index(idx_user_pro) where profession = '软件工程';

2). ignore index : 忽略指定的索引。

explain select * from tb_user ignore index(idx_user_pro) where profession = '软件工程';

3). force index : 强制使用索引。

explain select * from tb_user force index(idx_user_pro) where profession = '软件工程';
覆盖索引

尽量使用覆盖索引,减少select *。覆盖索引是指 查询使用了索引,并且需要返回的列,在该索引中已经全部能够找到 。

	Using where; Using Index:查找使用了索引,但是需要的数据都在索引列中能找到,所以不需 要回表查询数据
​	Using index condition:查找使用了索引,但是需要回表查询数据

第一

image-20230531114354817

第二

image-20230531114409403

第三

image-20230531114511106

第四

image-20230531114551536

思考题:
一张表, 有四个字段(id, username, password, status), 由于数据量大, 需要对以下SQL语句进行优化, 该如何进行才是最优方案:
select id,username,password from tb_user where username = 'itcast';

答案:
针对于 username, password建立联合索引, sql为: create index idx_user_name_pass on tb_user(username,password);
这样可以避免上述的SQL语句,在查询的过程中,出现回表查询。
前缀索引

当字段类型为字符串(varchar,text,longtext等)时,有时候需要索引很长的字符串,这会让 索引变得很大,查询时,浪费大量的磁盘IO, 影响查询效率。此时可以只将字符串的一部分前缀,建 立索引,这样可以大大节约索引空间,从而提高索引效率。

语法
create index idx_xxxx on table_name(column(n)) ;

image-20230531114916766

前缀长度

可以根据索引的选择性来决定,而选择性是指不重复的索引值(基数)和数据表的记录总数的比值, 索引选择性越高则查询效率越高, 唯一索引的选择性是1,这是最好的索引选择性,性能也是最好的。

select count(distinct email) / count(*) from tb_user ;
select count(distinct substring(email,1,5)) / count(*) from tb_user ;
前缀索引的查询流程

image-20230531115619421

单列索引与联合索引

单列索引:即一个索引只包含单个列。

联合索引:即一个索引包含了多个列。

在业务场景中,如果存在多个查询条件,考虑针对于查询字段建立索引时,建议建立联合索引, 而非单列索引。

image-20230531133119609

索引设计原则

1). 针对于数据量较大,且查询比较频繁的表建立索引。

2). 针对于常作为查询条件(where)、排序(order by)、分组(group by)操作的字段建立索 引。

3). 尽量选择区分度高的列作为索引,尽量建立唯一索引,区分度越高,使用索引的效率越高。

4). 如果是字符串类型的字段,字段的长度较长,可以针对于字段的特点,建立前缀索引。

5). 尽量使用联合索引,减少单列索引,查询时,联合索引很多时候可以覆盖索引,节省存储空间, 避免回表,提高查询效率。

6). 要控制索引的数量,索引并不是多多益善,索引越多,维护索引结构的代价也就越大,会影响增 删改的效率。

7). 如果索引列不能存储NULL值,请在创建表时使用NOT NULL约束它。当优化器知道每列是否包含 NULL值时,它可以更好地确定哪个索引最有效地用于查询。

SQL优化

插入数据

insert

如果我们需要一次性往数据库表中插入多条记录,可以从以下三个方面进行优化。

insert into tb_test values(1,'tom');
insert into tb_test values(2,'cat');
insert into tb_test values(3,'jerry');
.....

1). 优化方案一

批量插入数据

Insert into tb_test values(1,'Tom'),(2,'Cat'),(3,'Jerry');

2). 优化方案二

手动控制事务

start transaction;
insert into tb_test values(1,'Tom'),(2,'Cat'),(3,'Jerry');
insert into tb_test values(4,'Tom'),(5,'Cat'),(6,'Jerry');
insert into tb_test values(7,'Tom'),(8,'Cat'),(9,'Jerry');
commit;

3). 优化方案三

主键顺序插入,性能要高于乱序插入。

主键乱序插入 : 8 1 9 21 88 2 4 15 89 5 7 3
主键顺序插入 : 1 2 3 4 5 7 8 9 15 21 88 89
大批量插入数据

如果一次性需要插入大批量数据(比如: 几百万的记录),使用insert语句插入性能较低,此时可以使 用MySQL数据库提供的load指令进行插入。操作如下:

image-20230531133738164

可以执行如下指令,将数据脚本文件中的数据加载到表结构中:

-- 客户端连接服务端时,加上参数 -–local-infile
mysql –-local-infile -u root -p

-- 设置全局参数local_infile为1,开启从本地加载文件导入数据的开关
set global local_infile = 1;

-- 执行load指令将准备好的数据,加载到表结构中
load data local infile '/root/sql1.log' into table tb_user fields
terminated by ',' lines terminated by '\n' ;

主键顺序插入性能高于乱序插入

主键优化

主键顺序插入的性能是要高于乱序插入的。

1). 数据组织方式

在这里插入图片描述

行数据,都是存储在聚集索引的叶子节点上的。而我们之前也讲解过InnoDB的逻辑结构图:

image-20230531134205710

在InnoDB引擎中,数据行是记录在逻辑结构 page 页中的,而每一个页的大小是固定的,默认16K。 那也就意味着, 一个页中所存储的行也是有限的,如果插入的数据行row在该页存储不小,将会存储 到下一个页中,页与页之间会通过指针连接。

2). 页分裂

页可以为空,也可以填充一半,也可以填充100%。每个页包含了2-N行数据(如果一行数据过大,会行 溢出),根据主键排列。

A. 主键顺序插入效果

①. 从磁盘中申请页, 主键顺序插入

image-20230531134355214

②. 第一个页没有满,继续往第一页插入

image-20230531134411364

③. 当第一个也写满之后,再写入第二个页,页与页之间会通过指针连接

image-20230531134427215

④. 当第二页写满了,再往第三页写入

image-20230531134440837

B. 主键乱序插入效果

①. 加入1#,2#页都已经写满了,存放了如图所示的数据

image-20230531134528146

②. 此时再插入id为50的记录,我们来看看会发生什么现象

image-20230531134557034

不会。因为,索引结构的叶子节点是有顺序的。按照顺序,应该存储在47之后。

image-20230531134621324

但是47所在的1#页,已经写满了,存储不了50对应的数据了。 那么此时会开辟一个新的页 3#。

image-20230531134641527

但是并不会直接将50存入3#页,而是会将1#页后一半的数据,移动到3#页,然后在3#页,插入50。

image-20230531134658997

移动数据,并插入id为50的数据之后,那么此时,这三个页之间的数据顺序是有问题的。 1#的下一个 页,应该是3#, 3#的下一个页是2#。 所以,此时,需要重新设置链表指针。

image-20230531134811279

上述的这种现象,称之为 “页分裂”,是比较耗费性能的操作。

3). 页合并

目前表中已有数据的索引结构(叶子节点)如下:

image-20230531134925145

当我们对已有数据进行删除时,具体的效果如下:

当删除一行记录时,实际上记录并没有被物理删除,只是记录被标记(flaged)为删除并且它的空间 变得允许被其他记录声明使用。

image-20230531135006273

当我们继续删除2#的数据记录

image-20230531135024792

当页中删除的记录达到 MERGE_THRESHOLD(默认为页的50%),InnoDB会开始寻找最靠近的页(前 或后)看看是否可以将两个页合并以优化空间使用。

image-20230531135125044

删除数据,并将页合并之后,再次插入新的数据21,则直接插入3#页

image-20230531135148932

这个里面所发生的合并页的这个现象,就称之为 “页合并”。

知识小贴士: MERGE_THRESHOLD:合并页的阈值,可以自己设置,在创建表或者创建索引时指定。

4). 索引设计原则
	满足业务需求的情况下,尽量降低主键的长度。 
​	插入数据时,尽量选择顺序插入,选择使用AUTO_INCREMENT自增主键
​	尽量不要使用UUID做主键或者是其他自然主键,如身份证号。 
​	业务操作时,避免对主键的修改。

image-20230531135432202

order by优化

MySQL的排序,有两种方式:

	Using filesort : 通过表的索引或全表扫描,读取满足条件的数据行,然后在排序缓冲区sort buffer中完成排序操作,所有不是通过索引直接返回排序结果的排序都叫 FileSort 排序。

​	Using index : 通过有序索引顺序扫描直接返回有序数据,这种情况即为 using index,不需要 额外排序,操作效率高。

对于以上的两种排序方式,Using index的性能高,而Using filesort的性能低,我们在优化排序操作时,尽量要优化为 Using index。

image-20230531135809672

image-20230531135815616

由上述的测试,我们得出order by优化原则:

	A. 根据排序字段建立合适的索引,多字段排序时,也遵循最左前缀法则。 

​	B. 尽量使用覆盖索引。 

​	C. 多字段排序, 一个升序一个降序,此时需要注意联合索引在创建时的规则(ASC/DESC)。 

​	D. 如果不可避免的出现filesort,大数据量排序时,可以适当增大排序缓冲区大小 sort_buffer_size(默认256k)。

group by优化

在分组操作中,我们需要通过以下两点进行优化,以提升性能:

	A. 在分组操作时,可以通过索引来提高效率。 

​	B. 分组操作时,索引的使用也是满足最左前缀法则的。

limit优化

在数据量比较大时,如果进行limit分页查询,在查询时,越往后,分页查询效率越低。image-20230531140106098

优化思路: 一般分页查询时,通过创建覆盖索引能够比较好地提高性能,可以通过覆盖索引加子查询形式进行优化。

explain select * from tb_sku t , (select id from tb_sku order by id limit 2000000,10) a where t.id = a.id;

count优化

select count(*) from tb_user ;

如果数据量很大,在执行count操作时,是非常耗时的。
	MyISAM 引擎把一个表的总行数存在了磁盘上,因此执行 count(*) 的时候会直接返回这个 数,效率很高; 但是如果是带条件的count,MyISAM也慢。

​	InnoDB 引擎就麻烦了,它执行 count(*) 的时候,需要把数据一行一行地从引擎里面读出来,然后累积计数。

如果说要大幅度提升InnoDB表的count效率,主要的优化思路:自己计数(可以借助于redis这样的数据库进行,但是如果是带条件的count又比较麻烦了)。

count用法

count() 是一个聚合函数,对于返回的结果集,一行行地判断,如果 count 函数的参数不是 NULL,累计值就加 1,否则不加,最后返回累计值。

用法:count(*)、count(主键)、count(字段)、count(数字)

	count(主 键):InnoDB 引擎会遍历整张表,把每一行的 主键id 值都取出来,返回给服务层。 服务层拿到主键后,直接按行进行累加(主键不可能为null)

​	count(字 段):没有not null 约束 : InnoDB 引擎会遍历整张表把每一行的字段值都取出 来,返回给服务层,服务层判断是否为null,不为null,计数累加。 有not null 约束:InnoDB 引擎会遍历整张表把每一行的字段值都取出来,返 回给服务层,直接按行进行累加。

​	count(数 字):InnoDB 引擎遍历整张表,但不取值。服务层对于返回的每一行,放一个数字“1” 进去,直接按行进行累加。

​	count(*):InnoDB引擎并不会把全部字段取出来,而是专门做了优化,不取值,服务层直接 按行进行累加。


按照效率排序的话,count(字段) < count(主键 id) < count(1) ≈ count(*),所以尽量使用 count(*)。

update优化

我们主要需要注意一下update语句执行时的注意事项。

update course set name = 'javaEE' where id = 1 ;

当我们在执行删除的SQL语句时,会锁定id为1这一行的数据,然后事务提交之后,行锁释放。

但是当我们在执行如下SQL时。

update course set name = 'SpringBoot' where name = 'PHP' ;

当我们开启多个事务,在执行上述的SQL时,我们发现行锁升级为了表锁。 导致该update语句的性能大大降低。

InnoDB的行锁是针对索引加的锁,不是针对记录加的锁 ,并且该索引不能失效,否则会从行锁升级为表锁 。

视图

​ 视图(View)是一种虚拟存在的表。视图中的数据并不在数据库中实际存在,行和列数据来自定义视 图的查询中使用的表,并且是在使用视图时动态生成的。

​ 通俗的讲,视图只保存了查询的SQL逻辑,不保存查询结果。所以我们在创建视图的时候,主要的工作 就落在创建这条SQL查询语句上。

语法

1). 创建

CREATE [OR REPLACE] VIEW 视图名称[(列名列表)] AS SELECT语句 [ WITH [CASCADED | LOCAL ] CHECK OPTION ]

2). 查询

查看创建视图语句:SHOW CREATE VIEW 视图名称;
查看视图数据:SELECT * FROM 视图名称 ...... ;

3). 修改

方式一:CREATE [OR REPLACE] VIEW 视图名称[(列名列表)] AS SELECT语句 [ WITH[ CASCADED | LOCAL ] CHECK OPTION ]
方式二:ALTER VIEW 视图名称[(列名列表)] AS SELECT语句 [ WITH [ CASCADED | LOCAL ] CHECK OPTION ]

4). 删除

DROP VIEW [IF EXISTS] 视图名称 [,视图名称] ...
eg:

-- 创建视图
create or replace view stu_v_1 as select id,name from student where id <= 10;

-- 查询视图
show create view stu_v_1;
select * from stu_v_1;
select * from stu_v_1 where id < 3;

-- 修改视图
create or replace view stu_v_1 as select id,name,no from student where id <= 10;
alter view stu_v_1 as select id,name from student where id <= 10;

-- 删除视图
drop view if exists stu_v_1;

测试:

create or replace view stu_v_1 as select id,name from student where id <= 10 ;
select * from stu_v_1;
insert into stu_v_1 values(6,'Tom');
insert into stu_v_1 values(17,'Tom22');

执行上述的SQL,我们会发现,id为6和17的数据都是可以成功插入的。 但是我们执行查询,查询出来的数据,却没有id为17的记录。
因为我们在创建视图的时候,指定的条件为 id<=10, id为17的数据,是不符合条件的,所以没有查询出来,但是这条数据确实是已经成功的插入到了基表中。 

检查选项

当使用WITH CHECK OPTION子句创建视图时,MySQL会通过视图检查正在更改的每个行,例如 插 入,更新,删除,以使其符合视图的定义。 MySQL允许基于另一个视图创建视图,它还会检查依赖视 图中的规则以保持一致性。为了确定检查的范围,mysql提供了两个选项: CASCADED 和 LOCAL ,默认值为 CASCADED 。

1). CASCADED,级联。

比如,v2视图是基于v1视图的,如果在v2视图创建的时候指定了检查选项为 cascaded,但是v1视图创建时未指定检查选项。 则在执行检查时,不仅会检查v2,还会级联检查v2的关联视图v1。

image-20230531142044091

2). LOCAL,本地。

比如,v2视图是基于v1视图的,如果在v2视图创建的时候指定了检查选项为 local ,但是v1视图创 建时未指定检查选项。 则在执行检查时,知会检查v2,不会检查v2的关联视图v1。

image-20230531142201890

视图的更新

要使视图可更新,视图中的行与基础表中的行之间必须存在一对一的关系。如果视图包含以下任何一项,则该视图不可更新:

A. 聚合函数或窗口函数(SUM()、 MIN()、 MAX()、 COUNT()等) 

B. DISTINCT 

C. GROUP BY

D. HAVING 

E. UNION 或者 UNION ALL

视图作用

1). 简单: 视图不仅可以简化用户对数据的理解,也可以简化他们的操作。那些被经常使用的查询可以被定义为视 图,从而使得用户不必为以后的操作每次指定全部的条件。

2). 安全: 数据库可以授权,但不能授权到数据库特定行和特定的列上。通过视图用户只能查询和修改他们所能见 到的数据

3). 数据独立: 视图可帮助用户屏蔽真实表结构变化带来的影响。

案例

1). 为了保证数据库表的安全性,开发人员在操作tb_user表时,只能看到的用户的基本字段,屏蔽手机号和邮箱两个字段。

create view tb_user_view as select id,name,profession,age,gender,status,createtime from tb_user;

select * from tb_user_view;

2). 查询每个学生所选修的课程(三张表联查),这个功能在很多的业务中都有使用到,为了简化操作,定义一个视图。

create view tb_stu_course_view as select s.name student_name , s.no student_no ,c.name course_name from student s, student_course sc , course c where s.id = sc.studentid and sc.courseid = c.id;

select * from tb_stu_course_view;

存储过程

存储过程是事先经过编译并存储在数据库中的一段 SQL 语句的集合,调用存储过程可以简化应用开发 人员的很多工作,减少数据在数据库和应用服务器之间的传输,对于提高数据处理的效率是有好处的。

存储过程思想上很简单,就是数据库 SQL 语言层面的代码封装与重用。

image-20230531144346495

特点:

​	封装,复用 -----------------------> 可以把某一业务SQL封装在存储过程中,需要用到 的时候直接调用即可。 

​	可以接收参数,也可以返回数据 --------> 再存储过程中,可以传递参数,也可以接收返回 值。 

​	减少网络交互,效率提升 -------------> 如果涉及到多条SQL,每执行一次都是一次网络传 输。 而如果封装在存储过程中,我们只需要网络交互一次可能就可以了。

基本语法

创建
CREATE PROCEDURE 存储过程名称 ([ 参数列表 ])
BEGIN
	-- SQL语句
END ;
调用
CALL 名称 ([ 参数 ]);
查看
SELECT * FROM INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_SCHEMA = 'xxx'; -- 查询指定数据库的存储过程及状态信息

SHOW CREATE PROCEDURE 存储过程名称 ; -- 查询某个存储过程的定义
删除
DROP PROCEDURE [ IF EXISTS ] 存储过程名称 ;

注意: 在命令行中,执行创建存储过程的SQL时,需要通过关键字 delimiter 指定SQL语句的 结束符。

演示
-- 存储过程基本语法
-- 创建
create procedure p1()
begin
	select count(*) from student;
end;

-- 调用
call p1();

-- 查看
select * from information_schema.ROUTINES where ROUTINE_SCHEMA = 'itcast';
show create procedure p1;

-- 删除
drop procedure if exists p1;

变量

在MySQL中变量分为三种类型: 系统变量、用户定义变量、局部变量。

系统变量

系统变量 是MySQL服务器提供,不是用户定义的,属于服务器层面。分为全局变量(GLOBAL)、会话 变量(SESSION)。

查看系统变量
SHOW [ SESSION | GLOBAL ] VARIABLES ; -- 查看所有系统变量

SHOW [ SESSION | GLOBAL ] VARIABLES LIKE '......'; -- 可以通过LIKE模糊匹配方式查找变量

SELECT @@[SESSION | GLOBAL] 系统变量名; -- 查看指定变量的值
设置系统变量
SET [ SESSION | GLOBAL ] 系统变量名 = 值 ;

SET @@[SESSION | GLOBAL]系统变量名 = 值 ;
注意: 如果没有指定SESSION/GLOBAL,默认是SESSION,会话变量。

	mysql服务重新启动之后,所设置的全局参数会失效,要想不失效,可以在 /etc/my.cnf 中配置。

A. 全局变量(GLOBAL): 全局变量针对于所有的会话。 

B. 会话变量(SESSION): 会话变量针对于单个会话,在另外一个会话窗口就不生效了。
演示
-- 查看系统变量
show session variables ;

show session variables like 'auto%';
show global variables like 'auto%';

select @@global.autocommit;
select @@session.autocommit;

-- 设置系统变量
set session autocommit = 1;

insert into course(id, name) VALUES (6, 'ES');

set global autocommit = 0;

select @@global.autocommit;
用户定义变量

​ 用户定义变量 是用户根据需要自己定义的变量,用户变量不用提前声明,在用的时候直接用 “@变量 名” 使用就可以。其作用域为当前连接。

赋值
方式一:
SET @var_name = expr [, @var_name = expr] ... ;
SET @var_name := expr [, @var_name := expr] ... ;
赋值时,可以使用 = ,也可以使用 := 。

方式二:
SELECT @var_name := expr [, @var_name := expr] ... ;
SELECT 字段名 INTO @var_name FROM 表名;
使用
SELECT @var_name ;
注意: 用户定义的变量无需对其进行声明或初始化,只不过获取到的值为NULL。
演示示例
-- 赋值
set @myname = 'itcast';
set @myage := 10;
set @mygender := '男',@myhobby := 'java';
select @mycolor := 'red';
select count(*) into @mycount from tb_user;

-- 使用
select @myname,@myage,@mygender,@myhobby;
select @mycolor , @mycount;
select @abc;
局部变量

​ 局部变量 是根据需要定义的在局部生效的变量,访问之前,需要DECLARE声明。可用作存储过程内的 局部变量和输入参数,局部变量的范围是在其内声明的BEGIN … END块。

声明
DECLARE 变量名 变量类型 [DEFAULT ... ] ;
变量类型就是数据库字段类型:INT、BIGINT、CHAR、VARCHAR、DATE、TIME等。
赋值
SET 变量名 = 值 ;
SET 变量名 := 值 ;
SELECT 字段名 INTO 变量名 FROM 表名 ... ;
演示
-- 声明局部变量 - declare
-- 赋值
create procedure p2()
begin
	declare stu_count int default 0;
	select count(*) into stu_count from student;
	select stu_count;
end;

call p2();

if

if 用于做条件判断,具体的语法结构为:

IF 条件1 THEN
	.....
ELSEIF 条件2 THEN -- 可选
	.....
ELSE -- 可选
	.....
END IF;

在if条件判断的结构中,ELSE IF 结构可以有多个,也可以没有。 ELSE结构可以有,也可以没有。


根据定义的分数score变量,判定当前分数对应的分数等级。
	score >= 85分,等级为优秀。
	score >= 60分 且 score < 85分,等级为及格。
	score < 60分,等级为不及格。


CREATE PROCEDURE p3 () BEGIN
	DECLARE
		score INT DEFAULT 58;
	DECLARE
		result VARCHAR ( 10 );
	IF
		score >= 85 THEN
			
			SET result := '优秀';
		
		ELSEIF score >= 60 THEN
		
		SET result := '及格';
		ELSE 
			SET result := '不及格';
		
	END IF;
	SELECT
		result;
	
END;
CALL p3 ();

参数

参数的类型,主要分为以下三种:IN、OUT、INOUT。 
	IN(默认): 该类参数作为输入,也就是需要调用时传入值
	OUT:	 该类参数作为输出,也就是该参数可以作为返回值
	INOUT:	 既可以作为输入参数,也可以作为输出参数
	
'用法:'
CREATE PROCEDURE 存储过程名称 ([ IN/OUT/INOUT 参数名 参数类型 ])
BEGIN
	-- SQL语句
END ;	
	
根据传入参数score,判定当前分数对应的分数等级,并返回。
	score >= 85分,等级为优秀。
	score >= 60分 且 score < 85分,等级为及格。
	score < 60分,等级为不及格。

CREATE PROCEDURE p4 (
	IN score INT,
	OUT result VARCHAR ( 10 )) BEGIN
	IF
		score >= 85 THEN
			
			SET result := '优秀';
		
		ELSEIF score >= 60 THEN
		
		SET result := '及格';
		ELSE 
			SET result := '不及格';
		
	END IF;
END;	-- 定义用户变量 @result来接收返回的数据, 用户变量可以不用声明
CALL p4 ( 18, @result );
SELECT
	@result;

case

case结构及作用,和我们在基础篇中所讲解的流程控制函数很类似。有两种语法格式:
'语法1:'
-- 含义: 当case_value的值为 when_value1时,执行statement_list1,当值为 when_value2时,执行statement_list2, 否则就执行 statement_list
CASE case_value
	WHEN when_value1 THEN statement_list1
	[ WHEN when_value2 THEN statement_list2] ...
	[ ELSE statement_list ]
END CASE;

'语法2:'
-- 含义: 当条件search_condition1成立时,执行statement_list1,当条件search_condition2成立时,执行statement_list2, 否则就执行 statement_list
CASE
	WHEN search_condition1 THEN statement_list1
	[WHEN search_condition2 THEN statement_list2] ...
	[ELSE statement_list]
END CASE;

根据传入的月份,判定月份所属的季节(要求采用case结构)。
	1-3月份,为第一季度
	4-6月份,为第二季度
	7-9月份,为第三季度
	10-12月份,为第四季度
	
create procedure p6(in month int)
begin
	declare result varchar(10);
	case
		when month >= 1 and month <= 3 then
			set result := '第一季度';
		when month >= 4 and month <= 6 then
			set result := '第二季度';
		when month >= 7 and month <= 9 then
			set result := '第三季度';
		when month >= 10 and month <= 12 then
			set result := '第四季度';
		else
			set result := '非法参数';
	end case ;
		select concat('您输入的月份为: ',month, ', 所属的季度为: ',result);
end;

call p6(16);

注意:如果判定条件有多个,多个条件之间,可以使用 and 或 or 进行连接。

while

while 循环是有条件的循环控制语句。满足条件后,再执行循环体中的SQL语句。具体语法为:

-- 先判定条件,如果条件为true,则执行逻辑,否则,不执行逻辑
WHILE 条件 DO
	SQL逻辑...
END WHILE;

计算从1累加到n的值,n为传入的参数值。
-- A. 定义局部变量, 记录累加之后的值;
-- B. 每循环一次, 就会对n进行减1 , 如果n减到0, 则退出循环
create procedure p7(in n int)
begin
	declare total int default 0;
	while n>0 do
		set total := total + n;
		set n := n - 1;
	end while;
	select total;
end;

call p7(100);

repeat

repeat是有条件的循环控制语句, 当满足until声明的条件的时候,则退出循环。具体语法为:

-- 先执行一次逻辑,然后判定UNTIL条件是否满足,如果满足,则退出。如果不满足,则继续下一次循环
REPEAT
	SQL逻辑...
	UNTIL 条件
END REPEAT;

计算从1累加到n的值,n为传入的参数值。(使用repeat实现)
-- A. 定义局部变量, 记录累加之后的值;
-- B. 每循环一次, 就会对n进行-1 , 如果n减到0, 则退出循环
create procedure p8(in n int)
begin
	declare total int default 0;
	repeat
		set total := total + n;
		set n := n - 1;
	until n <= 0
	end repeat;
	select total;
end;

call p8(10);
call p8(100);

loop

LOOP 实现简单的循环,如果不在SQL逻辑中增加退出循环的条件,可以用其来实现简单的死循环。
LOOP可以配合一下两个语句使用:
	LEAVE :配合循环使用,退出循环。
	ITERATE:必须用在循环中,作用是跳过当前循环剩下的语句,直接进入下一次循环。
	
[begin_label:] LOOP
	SQL逻辑...
END LOOP [end_label];

LEAVE label; -- 退出指定标记的循环体
ITERATE label; -- 直接进入下一次循环
上述语法中出现的 begin_label,end_label,label 指的都是我们所自定义的标记。

计算从1到n之间的偶数累加的值,n为传入的参数值。
-- A. 定义局部变量, 记录累加之后的值;
-- B. 每循环一次, 就会对n进行-1 , 如果n减到0, 则退出循环 ----> leave xx
-- C. 如果当次累加的数据是奇数, 则直接进入下一次循环. --------> iterate xx
create procedure p10(in n int)
begin
	declare total int default 0;
	sum:loop
		if n<=0 then
			leave sum;
		end if;
		
		if n%2 = 1 then
			set n := n - 1;
			iterate sum;
		end if;
		
		set total := total + n;
		set n := n - 1;
	end loop sum;
	select total;
end;

call p10(100);

游标

游标(CURSOR)是用来存储查询结果集的数据类型 , 在存储过程和函数中可以使用游标对结果集进行循环的处理。游标的使用包括游标的声明、OPEN、FETCH 和 CLOSE,其语法分别如下。

A. 声明游标
DECLARE 游标名称 CURSOR FOR 查询语句 ;
B. 打开游标
OPEN 游标名称 ;
C. 获取游标记录
FETCH 游标名称 INTO 变量 [, 变量 ] ;
D. 关闭游标
CLOSE 游标名称 ;
案例
根据传入的参数uage,来查询用户表tb_user中,所有的用户年龄小于等于uage的用户姓名(name)和专业(profession),并将用户的姓名和专业插入到所创建的一张新表(id,name,profession)中。
-- 逻辑:
-- A. 声明游标, 存储查询结果集
-- B. 准备: 创建表结构
-- C. 开启游标
-- D. 获取游标中的记录
-- E. 插入数据到新表中
-- F. 关闭游标
create procedure p11(in uage int)
begin
	declare uname varchar(100);
	declare upro varchar(100);
	declare u_cursor cursor for select name,profession from tb_user where age <=
uage;

		drop table if exists tb_user_pro;
		create table if not exists tb_user_pro(
		id int primary key auto_increment,
		name varchar(100),
		profession varchar(100)
	);
	open u_cursor;
	while true do
		fetch u_cursor into uname,upro;
		insert into tb_user_pro values (null, uname, upro);
	end while;
	close u_cursor;
end;

call p11(30);

上述的存储过程,最终我们在调用的过程中,会报错,之所以报错是因为上面的while循环中,并没有退出条件。当游标的数据集获取完毕之后,再次获取数据,就会报错,从而终止了程序的执行。

image-20231027133459515

但是此时,tb_user_pro表结构及其数据都已经插入成功了,我们可以直接刷新表结构,检查表结构 中的数据。

image-20231027133637196

上述的功能,虽然我们实现了,但是逻辑并不完善,而且程序执行完毕,获取不到数据,数据库还报 错。 接下来,我们就需要来完成这个存储过程,并且解决这个问题。 要想解决这个问题,就需要通过MySQL中提供的 条件处理程序 Handler 来解决。

条件处理程序

条件处理程序(Handler)可以用来定义在流程控制结构执行过程中遇到问题时相应的处理步骤。具体语法为


DECLARE handler_action HANDLER FOR condition_value [, condition_value]
... statement ;

handler_action 的取值:
	CONTINUE: 继续执行当前程序
	EXIT: 终止执行当前程序

condition_value 的取值:
	SQLSTATE sqlstate_value: 状态码,如 02000

	SQLWARNING: 所有以01开头的SQLSTATE代码的简写
	NOT FOUND: 所有以02开头的SQLSTATE代码的简写
	SQLEXCEPTION: 所有没有被SQLWARNING 或 NOT FOUND捕获的SQLSTATE代码的简写

我们继续来完成在上一小节提出的这个需求,并解决其中的问题。 根据传入的参数uage,来查询用户表tb_user中,所有的用户年龄小于等于uage的用户姓名 (name)和专业(profession),并将用户的姓名和专业插入到所创建的一张新表 (id,name,profession)中。

#A. 通过SQLSTATE指定具体的状态码

-- 逻辑:
-- A. 声明游标, 存储查询结果集
-- B. 准备: 创建表结构
-- C. 开启游标
-- D. 获取游标中的记录
-- E. 插入数据到新表中
-- F. 关闭游标
create procedure p11(in uage int)
begin
	declare uname varchar(100);
	declare upro varchar(100);
	declare u_cursor cursor for select name,profession from tb_user where age <=uage;
	-- 声明条件处理程序 : 当SQL语句执行抛出的状态码为02000时,将关闭游标u_cursor,并退出
	declare exit handler for SQLSTATE '02000' close u_cursor;
	
	drop table if exists tb_user_pro;
		create table if not exists tb_user_pro(
		id int primary key auto_increment,
		name varchar(100),
		profession varchar(100)
	);
	
	open u_cursor;
	while true do
		fetch u_cursor into uname,upro;
		insert into tb_user_pro values (null, uname, upro);
	end while;
	close u_cursor;
	
end;

call p11(30);

#B. 通过SQLSTATE的代码简写方式 NOT FOUND
#02 开头的状态码,代码简写为 NOT FOUND


create procedure p12(in uage int)
begin
	declare uname varchar(100);
	declare upro varchar(100);
	declare u_cursor cursor for select name,profession from tb_user where age <=uage;
	-- 声明条件处理程序 : 当SQL语句执行抛出的状态码为02开头时,将关闭游标u_cursor,并退出
	declare exit handler for not found close u_cursor;
	
	drop table if exists tb_user_pro;
	create table if not exists tb_user_pro(
		id int primary key auto_increment,
		name varchar(100),
		profession varchar(100)
	);
	
	open u_cursor;
	while true do
		fetch u_cursor into uname,upro;
		insert into tb_user_pro values (null, uname, upro);
	end while;
	close u_cursor;
end;

call p12(30);

存储函数

存储函数是有返回值的存储过程,存储函数的参数只能是IN类型的。具体语法如下:

CREATE FUNCTION 存储函数名称 ([ 参数列表 ])
RETURNS type [characteristic ...]
BEGIN
	-- SQL语句
	RETURN ...;
END ;

characteristic说明:
	DETERMINISTIC:相同的输入参数总是产生相同的结果
	NO SQL :不包含 SQL 语句。
	READS SQL DATA:包含读取数据的语句,但不包含写入数据的语句。
	
计算从1累加到n的值,n为传入的参数值。
create function fun1(n int)
returns int deterministic
begin
	declare total int default 0;
	
	while n>0 do
		set total := total + n;
		set n := n - 1;
	end while;
	
	return total;
end;

select fun1(50);

-- 在mysql8.0版本中binlog默认是开启的,一旦开启了,mysql就要求在定义存储过程时,需要指定characteristic特性,否则就会报错

触发器

介绍

​ 触发器是与表有关的数据库对象,指在insert/update/delete之前(BEFORE)或之后(AFTER),触发并执行触发器中定义的SQL语句集合。触发器的这种特性可以协助应用在数据库端确保数据的完整性 , 日志记录 , 数据校验等操作 。

​ 使用别名OLD和NEW来引用触发器中发生变化的记录内容,这与其他的数据库是相似的。现在触发器还 只支持行级触发,不支持语句级触发。

image-20231027134615408

语法

创建
CREATE TRIGGER trigger_name
BEFORE/AFTER INSERT/UPDATE/DELETE
ON tbl_name FOR EACH ROW -- 行级触发器
BEGIN
	trigger_stmt ;
END;
查看
SHOW TRIGGERS ;
删除
DROP TRIGGER [schema_name.]trigger_name ; -- 如果没有指定 schema_name,默认为当前数据库。

案例

通过触发器记录 tb_user 表的数据变更日志,将变更日志插入到日志表user_logs中, 包含增加, 修改 , 删除 ;

-- 准备工作 : 日志表 user_logs
create table user_logs(
id int(11) not null auto_increment,
operation varchar(20) not null comment '操作类型, insert/update/delete',
operate_time datetime not null comment '操作时间',
operate_id int(11) not null comment '操作的ID',
operate_params varchar(500) comment '操作参数',
primary key(`id`)
)engine=innodb default charset=utf8;


-- A. 插入数据触发器
create trigger tb_user_insert_trigger
	after insert on tb_user for each row
begin
	insert into user_logs(id, operation, operate_time, operate_id, operate_params)	VALUES	(null, 'insert', now(), new.id, concat('插入的数据内容为:id=',new.id,',name=',new.name, ', phone=', NEW.phone, ', email=', NEW.email, ',profession=', NEW.profession));
end;

测试:
-- 查看
show triggers ;
-- 插入数据到tb_user
insert into tb_user(id, name, phone, email, profession, age, gender, status,createtime) VALUES (26,'三皇子','18809091212','erhuangzi@163.com','软件工程',23,'1','1',now());


-- B. 修改数据触发器
create trigger tb_user_update_trigger
	after update on tb_user for each row
begin
	insert into user_logs(id, operation, operate_time, operate_id, operate_params)	VALUES	(null, 'update', now(), new.id,concat('更新之前的数据: id=',old.id,',name=',old.name, ', phone=',old.phone, ', email=', old.email, ', profession=', old.profession,' | 更新之后的数据: id=',new.id,',name=',new.name, ', phone=',NEW.phone, ', email=', NEW.email, ', profession=', NEW.profession));
end;

测试
-- 查看
show triggers ;
-- 更新
update tb_user set profession = '会计' where id = 23;
update tb_user set profession = '会计' where id <= 5;

-- C. 删除数据触发器

create trigger tb_user_delete_trigger
	after delete on tb_user for each row
begin
	insert into user_logs(id, operation, operate_time, operate_id, operate_params)	VALUES	(null, 'delete', now(), old.id,concat('删除之前的数据: id=',old.id,',name=',old.name, ', phone=',old.phone, ', email=', old.email, ', profession=', old.profession));
end;

测试:

-- 查看
show triggers ;
-- 删除数据
delete from tb_user where id = 26;

概论

锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中,除传统的计算资源(CPURAMI/O)的争用以外,数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。从这个
角度来说,锁对数据库而言显得尤其重要,也更加复杂。

MySQL中的锁,按照锁的粒度分,分为以下三类:
	'全局锁':锁定数据库中的所有表。
	'表级锁':每次操作锁住整张表。
	'行级锁':每次操作锁住对应的行数据。

全局锁

介绍

​ 全局锁就是对整个数据库实例加锁,加锁后整个实例就处于只读状态,后续的DML的写语句,DDL语句,已经更新操作的事务提交语句都将被阻塞。
​ 其典型的使用场景是做全库的逻辑备份,对所有的表进行锁定,从而获取一致性视图,保证数据的完整性。

​ 为什么全库逻辑备份,就需要加全就锁呢?

A. 我们一起先来分析一下不加全局锁,可能存在的问题。

假设在数据库中存在这样三张表: tb_stock 库存表,tb_order 订单表,tb_orderlog 订单日 志表。

image-20231027135545943

1、在进行数据备份时,先备份了tb_stock库存表。
2、然后接下来,在业务系统中,执行了下单操作,扣减库存,生成订单(更新tb_stock表,插入tb_order表)。
3、然后再执行备份 tb_order表的逻辑。
4、业务中执行插入订单日志操作。
5、最后,又备份了tb_orderlog表。

此时备份出来的数据,是存在问题的。因为备份出来的数据,tb_stock表与tb_order表的数据不一致(有最新操作的订单信息,但是库存数没减)。
那如何来规避这种问题呢? 此时就可以借助于MySQL的全局锁来解决。

B. 再来分析一下加了全局锁后的情况

image-20231027135704282

	对数据库进行进行逻辑备份之前,先对整个数据库加上全局锁,一旦加了全局锁之后,其他的DDL、DML全部都处于阻塞状态,但是可以执行DQL语句,也就是处于只读状态,而数据备份就是查询操作。那么数据在进行逻辑备份的过程中,数据库中的数据就是不会发生变化的,这样就保证了数据的一致性和完整性。
语法
A. 加全局锁
flush tables with read lock ;
B. 数据备份
mysqldump -uroot –p1234 itcast > itcast.sql
数据备份的相关指令, 在后面MySQL管理章节, 还会详细讲解.
C. 释放锁
unlock tables ;
特点
数据库中加全局锁,是一个比较重的操作,存在以下问题:
	如果在主库上备份,那么在备份期间都不能执行更新,业务基本上就得停摆。
	如果在从库上备份,那么在备份期间从库不能执行主库同步过来的二进制日志(binlog),会导致主从延迟。
	
在InnoDB引擎中,我们可以在备份时加上参数 --single-transaction 参数来完成不加锁的一致性数据备份。
mysqldump --single-transaction -uroot –p123456 itcast > itcast.sql

表级锁

介绍
表级锁,每次操作锁住整张表。锁定粒度大,发生锁冲突的概率最高,并发度最低。应用在MyISAMInnoDBBDB等存储引擎中。
对于表级锁,主要分为以下三类:
	'表锁'
	'元数据锁(meta data lock,MDL)'
	'意向锁'
表锁
对于表锁,分为两类:
	'表共享读锁(read lock)'
	'表独占写锁(write lock)'
    
语法:
'加锁':	lock tables 表名... read/write。
'释放锁':	unlock tables / 客户端断开连接 。
读锁

image-20231027140556764

左侧为客户端一,对指定表加了读锁,不会影响右侧客户端二的读,但是会阻塞右侧客户端的写。

测试:

image-20231027140628899

写锁

image-20231027140710129

左侧为客户端一,对指定表加了写锁,会阻塞右侧客户端的读和写。

测试:

image-20231027140733027

结论: 读锁不会阻塞其他客户端的读,但是会阻塞写。写锁既会阻塞其他客户端的读,又会阻塞 其他客户端的写。

元数据锁

meta data lock , 元数据锁,简写MDL。

MDL加锁过程是系统自动控制,无需显式使用,在访问一张表的时候会自动加上。MDL锁主要作用是维 护表元数据的数据一致性,在表上有活动事务的时候,不可以对元数据进行写入操作。为了避免DML与 DDL冲突,保证读写的正确性

这里的元数据,大家可以简单理解为就是一张表的表结构。 也就是说,某一张表涉及到未提交的事务 时,是不能够修改这张表的表结构的。

在MySQL5.5中引入了MDL,当对一张表进行增删改查的时候,加MDL读锁(共享);当对表结构进行变 更操作的时候,加MDL写锁(排他)。

常见的SQL操作时,所添加的元数据锁:

image-20231027141054208

演示

当执行SELECT、INSERT、UPDATE、DELETE等语句时,添加的是元数据共享锁(SHARED_READ / SHARED_WRITE),之间是兼容的。

image-20231027141133282

当执行SELECT语句时,添加的是元数据共享锁(SHARED_READ),会阻塞元数据排他锁 (EXCLUSIVE),之间是互斥的。

image-20231027141201331

我们可以通过下面的SQL,来查看数据库中的元数据锁的情况:

select object_type,object_schema,object_name,lock_type,lock_duration from performance_schema.metadata_locks 

我们在操作过程中,可以通过上述的SQL语句,来查看元数据锁的加锁情况。

image-20231027141244375

意向锁

为了避免DML在执行时,加的行锁与表锁的冲突,在InnoDB中引入了意向锁,使得表锁不用检查每行 数据是否加锁,使用意向锁来减少表锁的检查。

假如没有意向锁,客户端一对表加了行锁后,客户端二如何给表加表锁呢,来通过示意图简单分析一 下: 首先客户端一,开启一个事务,然后执行DML操作,在执行DML语句时,会对涉及到的行加行锁。

image-20231027141337075

当客户端二,想对这张表加表锁时,会检查当前表是否有对应的行锁,如果没有,则添加表锁,此时就 会从第一行数据,检查到最后一行数据,效率较低。

image-20231027141408043

有了意向锁之后 : 客户端一,在执行DML操作时,会对涉及的行加行锁,同时也会对该表加上意向锁。

image-20231027141427342

而其他客户端,在对这张表加表锁的时候,会根据该表上所加的意向锁来判定是否可以成功加表锁,而 不用逐行判断行锁情况了。

image-20231027141657834

分类

​ 意向共享锁(IS): 由语句select … lock in share mode添加 。 与 表锁共享锁(read)兼容,与表锁排他锁(write)互斥。
​ 意向排他锁(IX): 由insert、update、delete、select…for update添加 。与表锁共享锁(read)及排他锁(write)都互斥,意向锁之间不会互斥。

一旦事务提交了,意向共享锁、意向排他锁,都会自动释放。

可以通过以下SQL,查看意向锁及行锁的加锁情况:
select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from performance_schema.data_locks

演示:

A. 意向共享锁与表读锁是兼容的

image-20231027141914151

B. 意向排他锁与表读锁、写锁都是互斥的

image-20231027141936346

行级锁

介绍

行级锁,每次操作锁住对应的行数据。锁定粒度最小,发生锁冲突的概率最低,并发度最高。应用在 InnoDB存储引擎中。

InnoDB的数据是基于索引组织的,行锁是通过对索引上的索引项加锁来实现的,而不是对记录加的锁。对于行级锁,主要分为以下三类:

image-20231027142126855

行锁
InnoDB实现了以下两种类型的行锁:
	'共享锁(S)':允许一个事务去读一行,阻止其他事务获得相同数据集的排它锁。
	'排他锁(X)':允许获取排他锁的事务更新数据,阻止其他事务获得相同数据集的共享锁和排他锁。

image-20231027142711229

常见的SQL语句,在执行时,所加的行锁如下:

image-20231027142735328

InnoDB引擎

逻辑存储结构

InnoDB的逻辑存储结构如下图所示:

image-20231030112043986

表空间
表空间是InnoDB存储引擎逻辑结构的最高层, 如果用户启用了参数 innodb_file_per_table(在8.0版本中默认开启) ,则每张表都会有一个表空间(xxx.ibd),一个mysql实例可以对应多个表空间,用于存储记录、索引等数据。
段,分为数据段(Leaf node segment)、索引段(Non-leaf node segment)、回滚段(Rollback segment),InnoDB是索引组织表,数据段就是B+树的叶子节点, 索引段即为B+树的非叶子节点。段用来管理多个Extent(区)。
区,表空间的单元结构,每个区的大小为1M。 默认情况下, InnoDB存储引擎页大小为16K, 即一个区中一共有64个连续的页。
页,是InnoDB 存储引擎磁盘管理的最小单元,每个页的大小默认为 16KB。为了保证页的连续性,InnoDB 存储引擎每次从磁盘申请 4-5 个区。
行,InnoDB 存储引擎数据是按行进行存放的。
在行中,默认有两个隐藏字段:
	'Trx_id':每次对某条记录进行改动时,都会把对应的事务id赋值给trx_id隐藏列。
	'Roll_pointer':每次对某条引记录进行改动时,都会把旧的版本写入到undo日志中,然后这个隐藏列就相当于一个指针,可以通过它来找到该记录修改前的信息。

架构

概述

MySQL5.5 版本开始,默认使用InnoDB存储引擎,它擅长事务处理,具有崩溃恢复特性,在日常开发 中使用非常广泛。下面是InnoDB架构图,左侧为内存结构,右侧为磁盘结构。

image-20231030112257669

内存结构

在这里插入图片描述

在左侧的内存结构中,主要分为这么四大块儿: Buffer Pool、Change Buffer、Adaptive Hash Index、Log Buffer。 接下来介绍一下这四个部分

1). Buffer Pool
	InnoDB存储引擎基于磁盘文件存储,访问物理硬盘和在内存中进行访问,速度相差很大,为了尽可能弥补这两者之间的I/O效率的差值,就需要把经常使用的数据加载到缓冲池中,避免每次访问都进行磁盘I/O。
	
在InnoDB的缓冲池中不仅缓存了索引页和数据页,还包含了undo页、插入缓存、自适应哈希索引以及InnoDB的锁信息等等。

缓冲池 Buffer Pool,是主内存中的一个区域,里面可以缓存磁盘上经常操作的真实数据,在执行增删改查操作时,先操作缓冲池中的数据(若缓冲池没有数据,则从磁盘加载并缓存),然后再以一定频率刷新到磁盘,从而减少磁盘IO,加快处理速度。

缓冲池以Page页为单位,底层采用链表数据结构管理Page。根据状态,将Page分为三种类型:
	• 'free page':空闲page,未被使用。
	• 'clean page':被使用page,数据没有被修改过。
	• 'dirty page':脏页,被使用page,数据被修改过,也中数据与磁盘的数据产生了不一致。
	
在专用服务器上,通常将多达80%的物理内存分配给缓冲池 。参数设置: show variables like 'innodb_buffer_pool_size';

image-20231030112521174

2). Change Buffer
Change Buffer,更改缓冲区(针对于非唯一二级索引页),在执行DML语句时,如果这些数据Page没有在Buffer Pool中,不会直接操作磁盘,而会将数据变更存在更改缓冲区 Change Buffer中,在未来数据被读取时,再将数据合并恢复到Buffer Pool中,再将合并后的数据刷新到磁盘中。

Change Buffer的意义是什么呢?
先来看一幅图,这个是二级索引的结构图:

image-20231030112609403

与聚集索引不同,二级索引通常是非唯一的,并且以相对随机的顺序插入二级索引。同样,删除和更新可能会影响索引树中不相邻的二级索引页,如果每一次都操作磁盘,会造成大量的磁盘IO。有了ChangeBuffer之后,我们可以在缓冲池中进行合并处理,减少磁盘IO。
3). Adaptive Hash Index
自适应hash索引,用于优化对Buffer Pool数据的查询。MySQL的innoDB引擎中虽然没有直接支持hash索引,但是给我们提供了一个功能就是这个自适应hash索引。因为前面我们讲到过,hash索引在进行等值匹配时,一般性能是要高于B+树的,因为hash索引一般只需要一次IO即可,而B+树,可能需要几次匹配,所以hash索引的效率要高,但是hash索引又不适合做范围查询、模糊匹配等。

InnoDB存储引擎会监控对表上各索引页的查询,如果观察到在特定的条件下hash索引可以提升速度,则建立hash索引,称之为自适应hash索引。

'自适应哈希索引,无需人工干预,是系统根据情况自动完成。''
参数: adaptive_hash_index
4). Log Buffer
Log Buffer:日志缓冲区,用来保存要写入到磁盘中的log日志数据(redo log 、undo log),默认大小为 16MB,日志缓冲区的日志会定期刷新到磁盘中。如果需要更新、插入或删除许多行的事务,增加日志缓冲区的大小可以节省磁盘 I/O。

参数:
'innodb_log_buffer_size':缓冲区大小
'innodb_flush_log_at_trx_commit':日志刷新到磁盘时机,取值主要包含以下三个:
	1: 日志在每次事务提交时写入并刷新到磁盘,默认值。
	0: 每秒将日志写入并刷新到磁盘一次。
	2: 日志在每次事务提交后写入,并每秒刷新到磁盘一次。

image-20231030112722615

磁盘结构

接下来,再来看看InnoDB体系结构的右边部分,也就是磁盘结构:

image-20231030112826423

1). System Tablespace
系统表空间是更改缓冲区的存储区域。如果表是在系统表空间而不是每个表文件或通用表空间中创建 的,它也可能包含表和索引数据。(在MySQL5.x版本中还包含InnoDB数据字典、undolog等)
参数:innodb_data_file_path

image-20231030112906112

系统表空间,默认的文件名叫 ibdata1。

2). File-Per-Table Tablespaces
如果开启了innodb_file_per_table开关 ,则每个表的文件表空间包含单个InnoDB表的数据和索引 ,并存储在文件系统上的单个数据文件中。
开关参数:innodb_file_per_table ,该参数默认开启。

image-20231030112936716

3). General Tablespaces
通用表空间,需要通过 CREATE TABLESPACE 语法创建通用表空间,在创建表时,可以指定该表空间。

A. 创建表空间
CREATE TABLESPACE ts_name ADD DATAFILE 'file_name' ENGINE = engine_name;

B. 创建表时指定表空间
CREATE TABLE xxx ... TABLESPACE ts_name;

image-20231030113106275

image-20231030113114770

4). Undo Tablespaces
撤销表空间,MySQL实例在初始化时会自动创建两个默认的undo表空间(初始大小16M),用于存储undo log日志。
5). Temporary Tablespaces
InnoDB 使用会话临时表空间和全局临时表空间。存储用户创建的临时表等数据。
6). Doublewrite Buffer Files
双写缓冲区,innoDB引擎将数据页从Buffer Pool刷新到磁盘前,先将数据页写入双写缓冲区文件中,便于系统异常时恢复数据。

image-20231030113251402

7). Redo Log
重做日志,是用来实现事务的持久性。该日志文件由两部分组成:重做日志缓冲(redo log buffer)以及重做日志文件(redo log),前者是在内存中,后者在磁盘中。当事务提交之后会把所有修改信息都会存到该日志中, 用于在刷新脏页到磁盘时,发生错误时, 进行数据恢复使用。

以循环方式写入重做日志文件,涉及两个文件:

image-20231030113432307

前面我们介绍了InnoDB的内存结构,以及磁盘结构,那么内存中我们所更新的数据,又是如何到磁盘 中的呢? 此时,就涉及到一组后台线程,接下来,就来介绍一些InnoDB中涉及到的后台线程。

image-20231030113456245

后台线程

在这里插入图片描述

在InnoDB的后台线程中,分为4类,分别是:Master Thread 、IO Thread、Purge Thread、 Page Cleaner Thread。

1). Master Thread
核心后台线程,负责调度其他线程,还负责将缓冲池中的数据异步刷新到磁盘中, 保持数据的一致性,还包括脏页的刷新、合并插入缓存、undo页的回收
2). IO Thread
在InnoDB存储引擎中大量使用了AIO来处理IO请求, 这样可以极大地提高数据库的性能,而IO Thread主要负责这些IO请求的回调。

在这里插入图片描述

我们可以通过以下的这条指令,查看到InnoDB的状态信息,其中就包含IO Thread信息

show engine innodb status \G;

image-20231030113728476

3). Purge Thread
主要用于回收事务已经提交了的undo log,在事务提交之后,undo log可能不用了,就用它来回收。
4). Page Cleaner Thread
协助 Master Thread 刷新脏页到磁盘的线程,它可以减轻 Master Thread 的工作压力,减少阻塞。

事务原理

事务基础
1). 事务
事务 是一组操作的集合,它是一个不可分割的工作单位,事务会把所有的操作作为一个整体一起向系统提交或撤销操作请求,即这些操作要么同时成功,要么同时失败。
2). 特性
'原子性'Atomicity):事务是不可分割的最小操作单元,要么全部成功,要么全部失败。
• '一致性'Consistency):事务完成时,必须使所有的数据都保持一致状态。
• '隔离性'Isolation):数据库系统提供的隔离机制,保证事务在不受外部并发操作影响的独立环境下运行。
• '持久性'Durability):事务一旦提交或回滚,它对数据库中的数据的改变就是永久的。

image-20231030113940611

而对于这四大特性,实际上分为两个部分。 其中的原子性、一致性、持久化,实际上是由InnoDB中的两份日志来保证的,一份是redo log日志,一份是undo log日志。 而持久性是通过数据库的锁,加上MVCC来保证的。

我们在讲解事务原理的时候,主要就是来研究一下redolog,undolog以及MVCC。

image-20231030113955654

redo log
重做日志,记录的是事务提交时数据页的物理修改,是用来实现事务的持久性。

该日志文件由两部分组成:重做日志缓冲(redo log buffer)以及重做日志文件(redo log file),前者是在内存中,后者在磁盘中。当事务提交之后会把所有修改信息都存到该日志文件中, 用于在刷新脏页到磁盘,发生错误时, 进行数据恢复使用。

如果没有redolog,可能会存在什么问题的? 我们一起来分析一下。

我们知道,在InnoDB引擎中的内存结构中,主要的内存区域就是缓冲池,在缓冲池中缓存了很多的数据页。 当我们在一个事务中,执行多个增删改的操作时,InnoDB引擎会先操作缓冲池中的数据,如果缓冲区没有对应的数据,会通过后台线程将磁盘中的数据加载出来,存放在缓冲区中,然后将缓冲池中的数据修改,修改后的数据页我们称为脏页。 而脏页则会在一定的时机,通过后台线程刷新到磁盘中,从而保证缓冲区与磁盘的数据一致。 而缓冲区的脏页数据并不是实时刷新的,而是一段时间之后将缓冲区的数据刷新到磁盘中,假如刷新到磁盘的过程出错了,而提示给用户事务提交成功,而数据却没有持久化下来,这就出现问题了,没有保证事务的持久性。

image-20231030114131878

那么,如何解决上述的问题呢? 在InnoDB中提供了一份日志 redo log,接下来我们再来分析一 下,通过 redolog 如何解决这个问题。

image-20231030114150792

有了redolog之后,当对缓冲区的数据进行增删改之后,会首先将操作的数据页的变化,记录在redo log buffer中。在事务提交时,会将redo log buffer中的数据刷新到redo log磁盘文件中。过一段时间之后,如果刷新缓冲区的脏页到磁盘时,发生错误,此时就可以借助于redo log进行数据,这样就保证了事务的持久性。 而如果脏页成功刷新到磁盘 或 或者涉及到的数据已经落盘,此时redolog就没有作用了,就可以删除了,所以存在的两个redolog文件是循环写的。

那为什么每一次提交事务,要刷新redo log 到磁盘中呢,而不是直接将buffer pool中的脏页刷新到磁盘呢 ?

因为在业务操作中,我们操作数据一般都是随机读写磁盘的,而不是顺序读写磁盘。 而redo log在往磁盘文件中写入数据,由于是日志文件,所以都是顺序写的。顺序写的效率,要远大于随机写。 这种先写日志的方式,称之为 WAL(Write-Ahead Logging)。
undo log
回滚日志,用于记录数据被修改前的信息 , 作用包含两个 : 提供回滚(保证事务的原子性) 和MVCC(多版本并发控制)。

undo log和redo log记录物理日志不一样,它是逻辑日志。可以认为当delete一条记录时,undolog中会记录一条对应的insert记录,反之亦然,当update一条记录时,它记录一条对应相反的update记录。当执行rollback时,就可以从undo log中的逻辑记录读取到相应的内容并进行回滚。

Undo log销毁:undo log在事务执行时产生,事务提交时,并不会立即删除undo log,因为这些日志可能还用于MVCC。

Undo log存储:undo log采用段的方式进行管理和记录,存放在前面介绍的 rollback segment回滚段中,内部包含1024个undo log segment。

MVCC

基本概念
当前读
读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。对于我们日常的操作,如:select ... lock in share mode(共享锁),select ... for update、update、insert、delete(排他锁)都是一种当前读。

测试

image-20231030115341960

在测试中我们可以看到,即使是在默认的RR隔离级别下,事务A中依然可以读取到事务B最新提交的内容,因为在查询语句后面加上了 lock in share mode 共享锁,此时是当前读操作。当然,当我们加排他锁的时候,也是当前读操作。
快照读
简单的select(不加锁)就是快照读,快照读,读取的是记录数据的可见版本,有可能是历史数据,
不加锁,是非阻塞读。
	• 'Read Committed':每次select,都生成一个快照读。
	• 'Repeatable Read':开启事务后第一个select语句才是快照读的地方。
	• 'Serializable':快照读会退化为当前读。

测试

image-20231030131738734

在测试中,我们看到即使事务B提交了数据,事务A中也查询不到。 原因就是因为普通的select是快照读,而在当前默认的RR隔离级别下,开启事务后第一个select语句才是快照读的地方,后面执行相同的select语句都是从快照中获取数据,可能不是当前的最新数据,这样也就保证了可重复读。
MVCC
全称 Multi-Version Concurrency Control,多版本并发控制。指维护一个数据的多个版本,使得读写操作没有冲突,快照读为MySQL实现MVCC提供了一个非阻塞读功能。MVCC的具体实现,还需要依赖于数据库记录中的三个隐式字段、undo log日志、readView。

接下来,我们再来介绍一下InnoDB引擎的表中涉及到的隐藏字段 、undolog 以及 readview,从而来介绍一下MVCC的原理。
隐藏字段
介绍

image-20231030132059291

当我们创建了上面的这张表,我们在查看表结构的时候,就可以显式的看到这三个字段。 实际上除了这三个字段以外,InnoDB还会自动的给我们添加三个隐藏字段及其含义分别是:

image-20231030132117500

而上述的前两个字段是肯定会添加的, 是否添加最后一个字段DB_ROW_ID,得看当前表有没有主键,如果有主键,则不会添加该隐藏字段。
测试

1). 查看有主键的表 stu

进入服务器中的 /var/lib/mysql/itcast/ , 查看stu的表结构信息, 通过如下指令:

ibd2sdi stu.ibd

查看到的表结构信息中,有一栏 columns,在其中我们会看到处理我们建表时指定的字段以外,还有额外的两个字段 分别是:DB_TRX_ID 、 DB_ROLL_PTR ,因为该表有主键,所以没有DB_ROW_ID隐藏字段。

2). 查看没有主键的表 employee

建表语句:
create table employee (id int , name varchar(10));

此时,我们再通过以下指令来查看表结构及其其中的字段信息:

ibd2sdi employee.ibd

查看到的表结构信息中,有一栏 columns,在其中我们会看到处理我们建表时指定的字段以外,还有额外的三个字段 分别是:DB_TRX_ID 、 DB_ROLL_PTR 、DB_ROW_ID,因为employee表是没有指定主键的。
undolog
回滚日志,在insert、update、delete的时候产生的便于数据回滚的日志。
当insert的时候,产生的undo log日志只在回滚时需要,在事务提交后,可被立即删除。
而update、delete的时候,产生的undo log日志不仅在回滚时需要,在快照读时也需要,不会立即被删除。

在这里插入图片描述

DB_TRX_ID : 代表最近修改事务ID,记录插入这条记录或最后一次修改该记录的事务ID,是自增的。
DB_ROLL_PTR : 由于这条数据是才插入的,没有被更新过,所以该字段值为null。

然后,有四个并发事务同时在访问这张表。

A. 第一步

在这里插入图片描述

当事务2执行第一条修改语句时,会记录undo log日志,记录数据变更之前的样子; 然后更新记录,并且记录本次操作的事务ID,回滚指针,回滚指针用来指定如果发生回滚,回滚到哪一个版本。

image-20231030132848307

B.第二步

image-20231030132905758

当事务3执行第一条修改语句时,也会记录undo log日志,记录数据变更之前的样子; 然后更新记录,并且记录本次操作的事务ID,回滚指针,回滚指针用来指定如果发生回滚,回滚到哪一个版本。

image-20231030132930083

C. 第三步

image-20231030132946559

当事务4执行第一条修改语句时,也会记录undo log日志,记录数据变更之前的样子; 然后更新记 录,并且记录本次操作的事务ID,回滚指针,回滚指针用来指定如果发生回滚,回滚到哪一个版本。

image-20231030133005511

最终我们发现,不同事务或相同事务对同一条记录进行修改,会导致该记录的undolog生成一条记录版本链表,链表的头部是最新的旧记录,链表尾部是最早的旧记录。
readview
ReadView(读视图)是 快照读 SQL执行时MVCC提取数据的依据,记录并维护系统当前活跃的事务(未提交的)id。
ReadView中包含了四个核心字段:

在这里插入图片描述

而在readview中就规定了版本链数据的访问规则:trx_id 代表当前undolog版本链对应事务ID。

在这里插入图片描述

不同的隔离级别,生成ReadView的时机不同:
	'READ COMMITTED' :在事务中每一次执行快照读时生成ReadView。
	'REPEATABLE READ':仅在事务中第一次执行快照读时生成ReadView,后续复用该ReadView
原理分析
RC隔离级别
RC隔离级别下,在事务中每一次执行快照读时生成ReadView。
我们就来分析事务5中,两次快照读读取数据,是如何获取数据的?
在事务5中,查询了两次id为30的记录,由于隔离级别为Read Committed,所以每一次进行快照读都会生成一个ReadView,那么两次生成的ReadView如下。

image-20231030133503741

那么这两次快照读在获取数据时,就需要根据所生成的ReadView以及ReadView的版本链访问规则,到undolog版本链中匹配数据,最终决定此次快照读返回的数据。

A. 先来看第一次快照读具体的读取过程:

image-20231030133538355

image-20231030133548083

在进行匹配时,会从undo log的版本链,从上到下进行挨个匹配:

image-20231030133607841

image-20231030133617573

B. 再来看第二次快照读具体的读取过程:

image-20231030133635121

image-20231030133643116

在进行匹配时,会从undo log的版本链,从上到下进行挨个匹配:

image-20231030133700696

RR隔离级别
RR隔离级别下,仅在事务中第一次执行快照读时生成ReadView,后续复用该ReadView。 而RR 是可重复读,在一个事务中,执行两次相同的select语句,查询到的结果是一样的。

MySQL是如何做到可重复读的呢? 我们简单分析一下就知道了

image-20231030133840886

我们看到,在RR隔离级别下,只是在事务中第一次快照读时生成ReadView,后续都是复用该ReadView,那么既然ReadView都一样, ReadView的版本链匹配规则也一样, 那么最终快照读返回的结果也是一样的。

所以呢,MVCC的实现原理就是通过 InnoDB表的隐藏字段、UndoLog 版本链、ReadView来实现的。而MVCC + 锁,则实现了事务的隔离性。 而一致性则是由redolog 与 undolog保证。

image-20231030133919686

MySQL管理

系统数据库

MySQL 数据库安装完成后,自带了一下四个数据库,具体作用如下:

在这里插入图片描述

常用工具

mysql

该mysql不是指mysql服务,而是指mysql的客户端工具。

语法 :
	mysql [options] [database]

选项 :
	-u, --user=name #指定用户名
	-p, --password[=name] #指定密码
	-h, --host=name #指定服务器IP或域名
	-P, --port=port #指定连接端口
	-e, --execute=name #执行SQL语句并退出
	
-e选项可以在Mysql客户端执行SQL语句,而不用连接到MySQL数据库再执行,对于一些批处理脚本,这种方式尤其方便。

示例:
mysql -uroot –p123456 db01 -e "select * from stu";

image-20231030134352300

mysqladmin
mysqladmin 是一个执行管理操作的客户端程序。可以用它来检查服务器的配置和当前状态、创建并 删除数据库等。
通过帮助文档查看选项: mysqladmin --help

语法:
	mysqladmin [options] command ...
选项:
	-u, --user=name #指定用户名
	-p, --password[=name] #指定密码
	-h, --host=name #指定服务器IP或域名
	-P, --port=port #指定连接端口
	
示例:
mysqladmin -uroot –p1234 drop 'test01';
mysqladmin -uroot –p1234 version;

image-20231030134547023

mysqlbinlog
由于服务器生成的二进制日志文件以二进制格式保存,所以如果想要检查这些文本的文本格式,就会使用到mysqlbinlog 日志管理工具。

语法 :
	mysqlbinlog [options] log-files1 log-files2 ...
选项 :
	-d, --database=name 指定数据库名称,只列出指定的数据库相关操作。
	-o, --offset=# 忽略掉日志中的前n行命令。
	-r,--result-file=name 将输出的文本格式日志输出到指定文件。
	-s, --short-form 显示简单格式, 省略掉一些信息。
	--start-datatime=date1 --stop-datetime=date2 指定日期间隔内的所有日志。
	--start-position=pos1 --stop-position=pos2 指定位置间隔内的所有日志。

示例:
A. 查看 binlog.000008这个二进制文件中的数据信息

image-20231030134707227

上述查看到的二进制日志文件数据信息量太多了,不方便查询。 我们可以加上一个参数 -s 来显示简 单格式。

image-20231030134733019

mysqlshow

mysqlshow 客户端对象查找工具,用来很快地查找存在哪些数据库、数据库中的表、表中的列或者索 引。

语法 :
	mysqlshow [options] [db_name [table_name [col_name]]]
选项 :
	--count 显示数据库及表的统计信息(数据库,表 均可以不指定)
	-i 显示指定数据库或者指定表的状态信息
示例:
	#查询test库中每个表中的字段书,及行数
	mysqlshow -uroot -p2143 test --count
	#查询test库中book表的详细情况
	mysqlshow -uroot -p2143 test book --count

示例

A. 查询每个数据库的表的数量及表中记录的数量

mysqlshow -uroot -p1234 --count

image-20231030134847008

B. 查看数据库db01的统计信息

mysqlshow -uroot -p1234 db01 --count

image-20231030134907122

C. 查看数据库db01中的course表的信息

mysqlshow -uroot -p1234 db01 course --count

image-20231030134927226

D. 查看数据库db01中的course表的id字段的信息

mysqlshow -uroot -p1234 db01 course id --count

image-20231030134944025

mysqldump
mysqldump 客户端工具用来备份数据库或在不同数据库之间进行数据迁移。备份内容包含创建表,及插入表的SQL语句。

语法 :
	mysqldump [options] db_name [tables]
	mysqldump [options] --database/-B db1 [db2 db3...]
	mysqldump [options] --all-databases/-A
连接选项 :
	-u, --user=name 指定用户名
	-p, --password[=name] 指定密码
	-h, --host=name 指定服务器ip或域名
	-P, --port=# 指定连接端口
输出选项:
	--add-drop-database 在每个数据库创建语句前加上 drop database 语句
	--add-drop-table 在每个表创建语句前加上 drop table 语句 , 默认开启 ; 不
开启 (--skip-add-drop-table)
	-n, --no-create-db 不包含数据库的创建语句
	-t, --no-create-info 不包含数据表的创建语句
	-d --no-data 不包含数据
	-T, --tab=name 自动生成两个文件:一个.sql文件,创建表结构的语句;一个.txt文件,数据文件

示例:

A. 备份db01数据库

mysqldump -uroot -p1234 db01 > db01.sql

image-20231030135120464

可以直接打开db01.sql,来查看备份出来的数据到底什么样。

image-20231030135138657

备份出来的数据包含:
	删除表的语句
	创建表的语句
	数据插入语句
	
如果我们在数据备份时,不需要创建表,或者不需要备份数据,只需要备份表结构,都可以通过对应的参数来实现。

B. 备份db01数据库中的表数据,不备份表结构(-t)

mysqldump -uroot -p1234 -t db01 > db01.sql

image-20231030135250639

打开 db02.sql ,来查看备份的数据,只有insert语句,没有备份表结构。

image-20231030135304011

C. 将db01数据库的表的表结构与数据分开备份(-T)

mysqldump -uroot -p1234 -T /root db01 score

image-20231030135327382

执行上述指令,会出错,数据不能完成备份,原因是因为我们所指定的数据存放目录/root,MySQL认为是不安全的,需要存储在MySQL信任的目录下。那么,哪个目录才是MySQL信任的目录呢,可以查看一下系统变量 secure_file_priv 。执行结果如下:

image-20231030135348290

上述的两个文件 score.sql 中记录的就是表结构文件,而 score.txt 就是表数据文件,但是需要注意表数据文件,并不是记录一条条的insert语句,而是按照一定的格式记录表结构中的数据。如下:

image-20231030135408222

mysqlimport/source
mysqlimport
mysqlimport 是客户端数据导入工具,用来导入mysqldump 加 -T 参数后导出的文本文件。

语法 :
	mysqlimport [options] db_name textfile1 [textfile2...]
示例 :
	mysqlimport -uroot -p2143 test /tmp/city.txt

image-20231030135508635

source
如果需要导入sql文件,可以使用mysql中的source 指令 :

语法 :source /root/xxxxx.sql

日志

错误日志

错误日志是 MySQL 中最重要的日志之一,它记录了当 mysqld 启动和停止时,以及服务器在运行过 程中发生任何严重错误时的相关信息。当数据库出现任何故障导致无法正常使用时,建议首先查看此日志。

该日志是默认开启的,默认存放目录 /var/log/,默认的日志文件名为 mysqld.log 。查看日志 位置:
show variables like '%log_error%';

image-20231030135729978

二进制日志

介绍
二进制日志(BINLOG)记录了所有的 DDL(数据定义语言)语句和 DML(数据操纵语言)语句,但不包括数据查询(SELECT、SHOW)语句。
作用:
	①. 灾难时的数据恢复;
	②. MySQL的主从复制。在MySQL8版本中,默认二进制日志是开启着的,
	
涉及到的参数如下:show variables like '%log_bin%';

image-20231030140106833

参数说明:
	log_bin_basename:当前数据库服务器的binlog日志的基础名称(前缀),具体的binlog文件名需要再该basename的基础上加上编号(编号从000001开始)。
	log_bin_index:binlog的索引文件,里面记录了当前服务器关联的binlog文件有哪些。
格式

MySQL服务器中提供了多种格式来记录二进制日志,具体格式及特点如下:

image-20231030140331337

show variables like '%binlog_format%';

image-20231030140414599

如果我们需要配置二进制日志的格式,只需要在 /etc/my.cnf 中配置 binlog_format 参数即可。
查看
由于日志是以二进制方式存储的,不能直接读取,需要通过二进制日志查询工具 mysqlbinlog 来查看,具体语法:

mysqlbinlog [ 参数选项 ] logfilename
参数选项:
	-d 指定数据库名称,只列出指定的数据库相关操作。
	-o 忽略掉日志中的前n行命令。
	-v 将行事件(数据变更)重构为SQL语句
	-vv 将行事件(数据变更)重构为SQL语句,并输出注释信息
删除
对于比较繁忙的业务系统,每天生成的binlog数据巨大,如果长时间不清除,将会占用大量磁盘空间。可以通过以下几种方式清理日志:

在这里插入图片描述

也可以在mysql的配置文件中配置二进制日志的过期时间,设置了之后,二进制日志过期会自动删除。
show variables like '%binlog_expire_logs_seconds%'

查询日志

查询日志中记录了客户端的所有操作语句,而二进制日志不包含查询数据的SQL语句。默认情况下,查询日志是未开启的。

image-20231030142238463

如果需要开启查询日志,可以修改MySQL的配置文件 /etc/my.cnf 文件,添加如下内容:

#该选项用来开启查询日志 , 可选值 : 0 或者 1 ; 0 代表关闭, 1 代表开启
general_log=1
#设置日志的文件名 , 如果没有指定, 默认的文件名为 host_name.log
general_log_file=mysql_query.log

开启了查询日志之后,在MySQL的数据存放目录,也就是 /var/lib/mysql/ 目录下就会出现mysql_query.log 文件。之后所有的客户端的增删改查操作都会记录在该日志文件之中,长时间运行后,该日志文件将会非常大。

慢查询日志

慢查询日志记录了所有执行时间超过参数 long_query_time 设置值并且扫描记录数不小于min_examined_row_limit 的所有的SQL语句的日志,默认未开启。long_query_time 默认为10 秒,最小为 0, 精度可以到微秒。
如果需要开启慢查询日志,需要在MySQL的配置文件 /etc/my.cnf 中配置如下参数:
#慢查询日志
slow_query_log=1
#执行时间参数
long_query_time=2

默认情况下,不会记录管理语句,也不会记录不使用索引进行查找的查询。可以使用log_slow_admin_statements和 更改此行为 log_queries_not_using_indexes,如下所述。

#记录执行较慢的管理语句
log_slow_admin_statements =1
#记录执行较慢的未使用索引的语句
log_queries_not_using_indexes = 1
上述所有的参数配置完成之后,都需要重新启动MySQL服务器才可以生效。

主从复制

概述

主从复制是指将主数据库的 DDL 和 DML 操作通过二进制日志传到从库服务器中,然后在从库上对这些日志重新执行(也叫重做),从而使得从库和主库的数据保持同步。
MySQL支持一台主库同时向多台从库进行复制, 从库同时也可以作为其他从服务器的主库,实现链状复制。

image-20231030142646539

MySQL 复制的优点主要包含以下三个方面:
	主库出现问题,可以快速切换到从库提供服务。
	实现读写分离,降低主库的访问压力。
	可以在从库中执行备份,以避免备份期间影响主库服务。

原理

MySQL主从复制的核心就是 二进制日志,具体的过程如下

image-20231030143127372

从上图来看,复制分成三步:
	1. Master 主库在事务提交时,会把数据变更记录在二进制日志文件 Binlog 中。
	2. 从库读取主库的二进制日志文件 Binlog ,写入到从库的中继日志 Relay Log 。
	3. slave重做中继日志中的事件,将改变反映它自己的数据。

搭建

准备

image-20231030143243193

准备好两台服务器之后,在上述的两台服务器中分别安装好MySQL,并完成基础的初始化准备(安装、密码配置等操作)工作。 其中:
	192.168.200.200 作为主服务器master
	192.168.200.201 作为从服务器slave
主库配置
'1.修改配置文件 /etc/my.cnf'

#mysql 服务ID,保证整个集群环境中唯一,取值范围:1 – 232-1,默认为1
server-id=1
#是否只读,1 代表只读, 0 代表读写
read-only=0
#忽略的数据, 指不需要同步的数据库
#binlog-ignore-db=mysql
#指定同步的数据库
#binlog-do-db=db01

'2. 重启MySQL服务器'
systemctl restart mysqld



'3. 登录mysql,创建远程连接的账号,并授予主从复制权限'
#创建itcast用户,并设置密码,该用户可在任意主机连接该MySQL服务
CREATE USER 'itcast'@'%' IDENTIFIED WITH mysql_native_password BY 'Root@123456';
#为 'itcast'@'%' 用户分配主从复制权限
GRANT REPLICATION SLAVE ON *.* TO 'itcast'@'%';

'4. 通过指令,查看二进制日志坐标'
show master status;

image-20231030143457746

字段含义说明:
 	'file' : 从哪个日志文件开始推送日志文件
 	'position' : 从哪个位置开始推送日志
 	'binlog_ignore_db' : 指定不需要同步的数据库
从库配置
1. 修改配置文件 /etc/my.cnf

#mysql 服务ID,保证整个集群环境中唯一,取值范围:1 – 2^32-1,和主库不一样即可
server-id=2
#是否只读,1 代表只读, 0 代表读写
read-only=1

2. 重新启动MySQL服务
systemctl restart mysqld

3. 登录mysql,设置主库配置
CHANGE REPLICATION SOURCE TO SOURCE_HOST='192.168.200.200', SOURCE_USER='itcast',
SOURCE_PASSWORD='Root@123456', SOURCE_LOG_FILE='binlog.000004',SOURCE_LOG_POS=663;

上述是8.0.23中的语法。如果mysql是 8.0.23 之前的版本,执行如下SQL:
CHANGE MASTER TO MASTER_HOST='192.168.200.200', MASTER_USER='itcast',
MASTER_PASSWORD='Root@123456', MASTER_LOG_FILE='binlog.000004',MASTER_LOG_POS=663;

image-20231030145055918

4. 开启同步操作
start replica ; #8.0.22之后
start slave ; #8.0.22之前

5. 查看主从同步状态
show replica status ; #8.0.22之后
show slave status ; #8.0.22之前

image-20231030145142811

测试
1. 在主库 192.168.200.200 上创建数据库、表,并插入数据

create database db01;
use db01;
create table tb_user(
	id int(11) primary key not null auto_increment,
	name varchar(50) not null,
	sex varchar(1)
)engine=innodb default charset=utf8mb4;
insert into tb_user(id,name,sex) values(null,'Tom', '1'),(null,'Trigger','0'),(null,'Dawn','1');

2. 在从库 192.168.200.201 中查询数据,验证主从是否同步

分库分表

介绍

问题分析

image-20231030150206652

随着互联网及移动互联网的发展,应用系统的数据量也是成指数式增长,若采用单数据库进行数据存储,存在以下性能瓶颈:
	1. IO瓶颈:热点数据太多,数据库缓存不足,产生大量磁盘IO,效率较低。 请求数据太多,带宽不够,网络IO瓶颈。
	2. CPU瓶颈:排序、分组、连接查询、聚合统计等SQL会耗费大量的CPU资源,请求数太多,CPU出现瓶颈。
	
	为了解决上述问题,我们需要对数据库进行分库分表处理。

image-20231030150326226

分库分表的中心思想都是将数据分散存储,使得单一数据库/表的数据量变小来缓解单一数据库的性能问题,从而达到提升数据库性能的目的。
拆分策略
分库分表的形式,主要是两种:垂直拆分和水平拆分。而拆分的粒度,一般又分为分库和分表,所以组 成的拆分策略最终如下:

image-20231030150429273

垂直拆分
垂直分库

在这里插入图片描述

垂直分库:以表为依据,根据业务将不同表拆分到不同库中。
特点:
	每个库的表结构都不一样。
	每个库的数据也不一样。
	所有库的并集是全量数据。
垂直分表

在这里插入图片描述

垂直分表:以字段为依据,根据字段属性将不同字段拆分到不同表中。
特点:
	每个表的结构都不一样。
	每个表的数据也不一样,一般通过一列(主键/外键)关联。
	所有表的并集是全量数据。
水平拆分
水平分库

在这里插入图片描述

水平分库:以字段为依据,按照一定策略,将一个库的数据拆分到多个库中。
特点:
	每个库的表结构都一样。
	每个库的数据都不一样。
	所有库的并集是全量数据。
水平分表

在这里插入图片描述

水平分表:以字段为依据,按照一定策略,将一个表的数据拆分到多个表中。
特点:
	每个表的表结构都一样。
	每个表的数据都不一样。
	所有表的并集是全量数据。
'在业务系统中,为了缓解磁盘IOCPU的性能瓶颈,到底是垂直拆分,还是水平拆分;具体是分库,还是分表,都需要根据具体的业务需求具体分析。'
实现技术
'shardingJDBC':基于AOP原理,在应用程序中对本地执行的SQL进行拦截,解析、改写、路由处理。需要自行编码配置实现,只支持java语言,性能较高。
'MyCat':数据库分库分表中间件,不用调整代码即可实现分库分表,支持多种语言,性能不及前者。
    
本次课程,我们选择了是MyCat数据库中间件,通过MyCat中间件来完成分库分表操作

在这里插入图片描述

MyCat概述

介绍
Mycat是开源的、活跃的、基于Java语言编写的MySQL数据库中间件。可以像使用mysql一样来使用mycat,对于开发人员来说根本感觉不到mycat的存在。
开发人员只需要连接MyCat即可,而具体底层用到几台数据库,每一台数据库服务器里面存储了什么数据,都无需关心。 具体的分库分表的策略,只需要在MyCat中配置即可。

在这里插入图片描述

优势:
	性能可靠稳定
	强大的技术团队
	体系完善
	社区活跃
下载

下载地址:http://dl.mycat.org.cn/

在这里插入图片描述

安装
Mycat是采用java语言开发的开源的数据库中间件,支持WindowsLinux运行环境,下面介绍MyCatLinux中的环境搭建。我们需要在准备好的服务器中安装如下软件。
	MySQL
	JDK
	Mycat

在这里插入图片描述

具体的安装步骤: 参考资料中提供的 《MyCat安装文档》即可,里面有详细的安装及配置步骤。

目录介绍

image-20231030151441326

bin : 存放可执行文件,用于启动停止mycat
conf:存放mycat的配置文件
lib:存放mycat的项目依赖包(jar)
logs:存放mycat的日志文件
概念介绍
在MyCat的整体结构中,分为两个部分:上面的逻辑结构、下面的物理结构。
在MyCat的逻辑结构主要负责逻辑库、逻辑表、分片规则、分片节点等逻辑结构的处理,而具体的数据存储还是在物理结构,也就是数据库服务器中存储的。
在后面讲解MyCat入门以及MyCat分片时,还会讲到上面所提到的概念。

image-20231030151519988

MyCat入门

需求
由于 tb_order 表中数据量很大,磁盘IO及容量都到达了瓶颈,现在需要对 tb_order 表进行数据分片,分为三个数据节点,每一个节点主机位于不同的服务器上, 具体的结构,参考下图:

在这里插入图片描述

环境准备
准备3台服务器:
	192.168.200.210:MyCat中间件服务器,同时也是第一个分片服务器。
	192.168.200.213:第二个分片服务器。
	192.168.200.214:第三个分片服务器。

在这里插入图片描述

并且在上述3台数据库中创建数据库 db01 。

配置
'1). schema.xml'
在schema.xml中配置逻辑库、逻辑表、数据节点、节点主机等相关信息。具体的配置如下:

<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
	<mycat:schema xmlns:mycat="http://io.mycat/">
		<schema name="DB01" checkSQLschema="true" sqlMaxLimit="100">
			<table name="TB_ORDER" dataNode="dn1,dn2,dn3" rule="auto-sharding-long"/>
		</schema>
	<dataNode name="dn1" dataHost="dhost1" database="db01" />
	<dataNode name="dn2" dataHost="dhost2" database="db01" />
	<dataNode name="dn3" dataHost="dhost3" database="db01" />
	<dataHost name="dhost1" maxCon="1000" minCon="10" balance="0" writeType="0" dbType="mysql" dbDriver="jdbc" switchType="1" slaveThreshold="100">
	<heartbeat>select user()</heartbeat>
	
	<writeHost host="master" url="jdbc:mysql://192.168.200.210:3306?
useSSL=false&amp;serverTimezone=Asia/Shanghai&amp;characterEncoding=utf8"user="root" password="1234" />
	</dataHost>
	
	<dataHost name="dhost2" maxCon="1000" minCon="10" balance="0" writeType="0" dbType="mysql" dbDriver="jdbc" switchType="1" slaveThreshold="100">
	<heartbeat>select user()</heartbeat>

	<writeHost host="master" url="jdbc:mysql://192.168.200.213:3306?
useSSL=false&amp;serverTimezone=Asia/Shanghai&amp;characterEncoding=utf8" user="root" password="1234" />
	</dataHost>

	<dataHost name="dhost3" maxCon="1000" minCon="10" balance="0"
writeType="0" dbType="mysql" dbDriver="jdbc" switchType="1" slaveThreshold="100">
	<heartbeat>select user()</heartbeat>

<writeHost host="master" url="jdbc:mysql://192.168.200.214:3306?
useSSL=false&amp;serverTimezone=Asia/Shanghai&amp;characterEncoding=utf8" user="root" password="1234" />
	</dataHost>
</mycat:schema>

'2). server.xml'
需要在server.xml中配置用户名、密码,以及用户的访问权限信息,具体的配置如下:

<user name="root" defaultAccount="true">
	<property name="password">123456</property>
	<property name="schemas">DB01</property>

	<!-- 表级 DML 权限设置 -->
	<!--
	<privileges check="true">
		<schema name="DB01" dml="0110" >
			<table name="TB_ORDER" dml="1110"></table>
		</schema>
	</privileges>
	-->
</user>
<user name="user">
	<property name="password">123456</property>
	<property name="schemas">DB01</property>
	<property name="readOnly">true</property>
</user>

上述的配置表示,定义了两个用户 root 和 user ,这两个用户都可以访问 DB01 这个逻辑库,访问密码都是123456,但是root用户访问DB01逻辑库,既可以读,又可以写,但是 user用户访问DB01逻辑库是只读的。
测试
启动
配置完毕后,先启动涉及到的3台分片服务器,然后启动MyCat服务器。切换到Mycat的安装目录,执行如下指令,启动Mycat:

#启动
bin/mycat start
#停止
bin/mycat stop

Mycat启动之后,占用端口号 8066。
启动完毕之后,可以查看logs目录下的启动日志,查看Mycat是否启动完成。

在这里插入图片描述

测试
'1). 连接MyCat'
通过如下指令,就可以连接并登陆MyCat。
mysql -h 192.168.200.210 -P 8066 -uroot -p123456
我们看到我们是通过MySQL的指令来连接的MyCat,因为MyCat在底层实际上是模拟了MySQL的协议。

'2). 数据测试'
然后就可以在MyCat中来创建表,并往表结构中插入数据,查看数据在MySQL中的分布情况。
CREATE TABLE TB_ORDER (
	id BIGINT(20) NOT NULL,
	title VARCHAR(100) NOT NULL ,
	PRIMARY KEY (id)
) ENGINE=INNODB DEFAULT CHARSET=utf8 ;

INSERT INTO TB_ORDER(id,title) VALUES(1,'goods1');
INSERT INTO TB_ORDER(id,title) VALUES(2,'goods2');
INSERT INTO TB_ORDER(id,title) VALUES(3,'goods3');

INSERT INTO TB_ORDER(id,title) VALUES(1,'goods1');
INSERT INTO TB_ORDER(id,title) VALUES(2,'goods2');
INSERT INTO TB_ORDER(id,title) VALUES(3,'goods3');
INSERT INTO TB_ORDER(id,title) VALUES(5000000,'goods5000000');
INSERT INTO TB_ORDER(id,title) VALUES(10000000,'goods10000000');
INSERT INTO TB_ORDER(id,title) VALUES(10000001,'goods10000001');
INSERT INTO TB_ORDER(id,title) VALUES(15000000,'goods15000000');
INSERT INTO TB_ORDER(id,title) VALUES(15000001,'goods15000001');

经过测试,我们发现,在往 TB_ORDER 表中插入数据时:
	如果id的值在1-500w之间,数据将会存储在第一个分片数据库中。
	如果id的值在500w-1000w之间,数据将会存储在第二个分片数据库中。
	如果id的值在1000w-1500w之间,数据将会存储在第三个分片数据库中。
	如果id的值超出1500w,在插入数据时,将会报错。
为什么会出现这种现象,数据到底落在哪一个分片服务器到底是如何决定的呢? 这是由逻辑表配置时的一个参数 rule 决定的,而这个参数配置的就是分片规则,关于分片规则的配置,在后面的课程中会详细讲解。

MyCat配置

schema.xml
schema.xml 作为MyCat中最重要的配置文件之一 , 涵盖了MyCat的逻辑库 、 逻辑表 、 分片规则、分片节点及数据源的配置。

image-20231030160615119

主要包含以下三组标签:
	schema标签
	datanode标签
	datahost标签
schema标签

1). schema 定义逻辑库

image-20231030161315799

schema 标签用于定义 MyCat实例中的逻辑库 , 一个MyCat实例中, 可以有多个逻辑库 , 可以通过 schema 标签来划分不同的逻辑库。MyCat中的逻辑库的概念,等同于MySQL中的database概念, 需要操作某个逻辑库下的表时, 也需要切换逻辑库(use xxx)。

核心属性:
'name':指定自定义的逻辑库库名
'checkSQLschema':在SQL语句操作时指定了数据库名称,执行时是否自动去除;true:自动去除,false:不自动去除
'sqlMaxLimit':如果未指定limit进行查询,列表查询模式查询多少条记录

2). schema 中的table定义逻辑表

image-20231030161427592

table 标签定义了MyCat中逻辑库schema下的逻辑表 , 所有需要拆分的表都需要在table标签中定 义 。

核心属性:
	name:定义逻辑表表名,在该逻辑库下唯一
	dataNode:定义逻辑表所属的dataNode,该属性需要与dataNode标签中name对应;多个
	dataNode逗号分隔
	rule:分片规则的名字,分片规则名字是在rule.xml中定义的
	primaryKey:逻辑表对应真实表的主键
	type:逻辑表的类型,目前逻辑表只有全局表和普通表,如果未配置,就是普通表;全局表,配置为 global
datanode标签

image-20231030161610487

核心属性:
	name:定义数据节点名称
	dataHost:数据库实例主机名称,引用自 dataHost 标签中name属性
	database:定义分片所属数据库
datahost标签

image-20231030161727489

该标签在MyCat逻辑库中作为底层标签存在, 直接定义了具体的数据库实例、读写分离、心跳语句。

核心属性:
	name:唯一标识,供上层标签使用
	maxCon/minCon:最大连接数/最小连接数
	balance:负载均衡策略,取值 0,1,2,3
	writeType:写操作分发方式(0:写操作转发到第一个writeHost,第一个挂了,切换到第二个;1:写操作随机分发到配置的writeHost)
	dbDriver:数据库驱动,支持 native、jdbc
rule.xml
rule.xml中定义所有拆分表的规则, 在使用过程中可以灵活的使用分片算法, 或者对同一个分片算法 使用不同的参数, 它让分片过程可配置化。主要包含两类标签:tableRule、Function。

在这里插入图片描述

server.xml
server.xml配置文件包含了MyCat的系统配置信息,主要有两个重要的标签:
	system、
	user。
1). system标签

image-20231030163223243

主要配置MyCat中的系统配置信息,对应的系统配置项及其含义,如下

属性
取值
含义

charset
utf8
设置Mycat的字符集, 字符集需要与MySQL的字符集保持一致

nonePasswordLogin
0,1
0为需要密码登陆、1为不需要密码登陆 ,默认为0,设置为1则需要指定默认账户

useHandshakeV10
0,1
使用该选项主要的目的是为了能够兼容高版本的jdbc驱动, 是否采用
HandshakeV10Packet来与client进行通信, 1:是, 0:否

useSqlStat
0,1
开启SQL实时统计, 1 为开启 , 0 为关闭 ;
开启之后, MyCat会自动统计SQL语句的执行情况 ; mysql -h 127.0.0.1 -P 9066 -u root -p 查看MyCat执行的SQL, 执行
效率比较低的SQL , SQL的整体执行情况、读写比例等 ; show @@sql ; show @@sql.slow ; show @@sql.sum ;

useGlobleTableCheck
0,1
是否开启全局表的一致性检测。1为开启 ,0为关闭 。

sqlExecuteTimeout
1000
SQL语句执行的超时时间 , 单位为 s ;

sequnceHandlerType
0,1,2
用来指定Mycat全局序列类型,0 为本地文件,1 为数据库方式,2 为时间戳列方式,默认使用本地文件方式,文件方式主要用于测试

sequnceHandlerPattern
正则表达式
必须带有MYCATSEQ或者 mycatseq进入序列匹配流程 注意MYCATSEQ_有空格的情况

subqueryRelationshipCheck
true,false
子查询中存在关联查询的情况下,检查关联字段中是否有分片字段 .默认 false

useCompression
0,1
开启mysql压缩协议 , 0 : 关闭, 1 : 开启

fakeMySQLVersion
5.5,5.6
设置模拟的MySQL版本号

defaultSqlParser
由于MyCat的最初版本使用了FoundationDB的SQL解析器, 在MyCat1.3后增加了Druid解析器, 所以要设置defaultSqlParser属性来指定默认的解析器; 解析器有两个 :druidparser 和 fdbparser, 在MyCat1.4之后,默认是druidparser,fdbparser已经废除了

processors
1,2....
指定系统可用的线程数量, 默认值为CPU核心x 每个核心运行线程数量; processors 会影响processorBufferPool,processorBufferLocalPercent,processorExecutor属性, 所有, 在性能调优时, 可以适当地修改processors值

processorBufferChunk
指定每次分配Socket Direct Buffer默认值为4096字节, 也会影响BufferPool长度,如果一次性获取字节过多而导致buffer不够用, 则会出现警告, 可以调大该值

processorExecutor
指定NIOProcessor上共享businessExecutor固定线程池的大小;MyCat把异步任务交给 businessExecutor线程池中, 在新版本的MyCat中这个连接池使用频次不高, 可以适当地把该值调小


packetHeaderSize
指定MySQL协议中的报文头长度, 默认4个字节

maxPacketSize
指定MySQL协议可以携带的数据最大大小, 默认值为16M

idleTimeout
30
指定连接的空闲时间的超时长度;如果超时,将关闭资源并回收, 默认30分钟。

txIsolation
1,2,3,4
初始化前端连接的事务隔离级别,默认为REPEATED_READ , 对应数字为3 READ_UNCOMMITED=1;READ_COMMITTED=2; REPEATED_READ=3;SERIALIZABLE=4;

sqlExecuteTimeout
300
执行SQL的超时时间, 如果SQL语句执行超时,将关闭连接; 默认300秒;

serverPort
8066
定义MyCat的使用端口, 默认8066

managerPort
9066
定义MyCat的管理端口, 默认9066
2). user标签
配置MyCat中的用户、访问密码,以及用户针对于逻辑库、逻辑表的权限信息,具体的权限描述方式及配置说明如下:

image-20231030164453525

在测试权限操作时,我们只需要将 privileges 标签的注释放开。 在 privileges 下的schema标签中配置的dml属性配置的是逻辑库的权限。 在privileges的schema下的table标签的dml属性中配置逻辑表的权限。

MyCat分片

垂直拆分
场景
在业务系统中, 涉及以下表结构 ,但是由于用户与订单每天都会产生大量的数据, 单台服务器的数据存储及处理能力是有限的, 可以对数据库表进行拆分, 原有的数据库表如下。

image-20231030164723484

现在考虑将其进行垂直分库操作,将商品相关的表拆分到一个数据库服务器,订单表拆分的一个数据库服务器,用户及省市区表拆分到一个服务器。最终结构如下:

在这里插入图片描述

准备

准备三台服务器,IP地址如图所示:

image-20231030164849811

并且在192.168.200.210,192.168.200.213, 192.168.200.214上面创建数据库shopping。
配置
‘1). schema.xml’

<schema name="SHOPPING" checkSQLschema="true" sqlMaxLimit="100">
	<table name="tb_goods_base" dataNode="dn1" primaryKey="id" />
	<table name="tb_goods_brand" dataNode="dn1" primaryKey="id" />
	<table name="tb_goods_cat" dataNode="dn1" primaryKey="id" />
	<table name="tb_goods_desc" dataNode="dn1" primaryKey="goods_id" />
	<table name="tb_goods_item" dataNode="dn1" primaryKey="id" />
	<table name="tb_order_item" dataNode="dn2" primaryKey="id" />
	<table name="tb_order_master" dataNode="dn2" primaryKey="order_id" />
	<table name="tb_order_pay_log" dataNode="dn2" primaryKey="out_trade_no" />
	<table name="tb_user" dataNode="dn3" primaryKey="id" />
	<table name="tb_user_address" dataNode="dn3" primaryKey="id" />
	<table name="tb_areas_provinces" dataNode="dn3" primaryKey="id"/>
	<table name="tb_areas_city" dataNode="dn3" primaryKey="id"/>
	<table name="tb_areas_region" dataNode="dn3" primaryKey="id"/>
</schema>
<dataNode name="dn1" dataHost="dhost1" database="shopping" />
<dataNode name="dn2" dataHost="dhost2" database="shopping" />
<dataNode name="dn3" dataHost="dhost3" database="shopping" />

<dataHost name="dhost1" maxCon="1000" minCon="10" balance="0"
writeType="0" dbType="mysql" dbDriver="jdbc" switchType="1"
slaveThreshold="100">
	<heartbeat>select user()</heartbeat>
	<writeHost host="master" url="jdbc:mysql://192.168.200.210:3306?
useSSL=false&amp;serverTimezone=Asia/Shanghai&amp;characterEncoding=utf8"
user="root" password="1234" />
</dataHost>

	<dataHost name="dhost2" maxCon="1000" minCon="10" balance="0"
writeType="0" dbType="mysql" dbDriver="jdbc" switchType="1"
slaveThreshold="100">

	<heartbeat>select user()</heartbeat>
	<writeHost host="master" url="jdbc:mysql://192.168.200.213:3306?
useSSL=false&amp;serverTimezone=Asia/Shanghai&amp;characterEncoding=utf8"
user="root" password="1234" />
</dataHost>
<dataHost name="dhost3" maxCon="1000" minCon="10" balance="0"
writeType="0" dbType="mysql" dbDriver="jdbc" switchType="1"
slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<writeHost host="master" url="jdbc:mysql://192.168.200.214:3306?
useSSL=false&amp;serverTimezone=Asia/Shanghai&amp;characterEncoding=utf8"
user="root" password="1234" />
</dataHost>


'2). server.xml'
<user name="root" defaultAccount="true">
	<property name="password">123456</property>
	<property name="schemas">SHOPPING</property>
	<!-- 表级 DML 权限设置 -->
	<!--
	<privileges check="true">
	<schema name="DB01" dml="0110" >
		<table name="TB_ORDER" dml="1110"></table>
			</schema>
		</privileges>
	-->
	</user>
<user name="user">
	<property name="password">123456</property>
	<property name="schemas">SHOPPING</property>
	<property name="readOnly">true</property>
</user>
测试

1). 上传测试SQL脚本到服务器的/root目录

image-20231030165424074

2). 执行指令导入测试数据

重新启动MyCat后,在mycat的命令行中,通过source指令导入表结构,以及对应的数据,查看数据分布情况。

source /root/shopping-table.sql
source /root/shopping-insert.sql

将表结构及对应的测试数据导入之后,可以检查一下各个数据库服务器中的表结构分布情况。 检查是否和我们准备工作中规划的服务器一致。

image-20231030165458121

3). 查询用户的收件人及收件人地址信息(包含省、市、区)。

在MyCat的命令行中,当我们执行以下多表联查的SQL语句时,可以正常查询出数据。

select ua.user_id, ua.contact, p.province, c.city, r.area , ua.address from tb_user_address ua ,tb_areas_city c , tb_areas_provinces p ,tb_areas_region r where ua.province_id = p.provinceid and ua.city_id = c.cityid and ua.town_id = r.areaid ;

image-20231030165538756

4). 查询每一笔订单及订单的收件地址信息(包含省、市、区)。

实现该需求对应的SQL语句如下:

SELECT order_id , payment ,receiver, province , city , area FROM tb_order_master o, tb_areas_provinces p , tb_areas_city c , tb_areas_region r WHERE o.receiver_province = p.provinceid AND o.receiver_city = c.cityid AND o.receiver_region = r.areaid ;

但是现在存在一个问题,订单相关的表结构是在 192.168.200.213 数据库服务器中,而省市区的数
据库表是在 192.168.200.214 数据库服务器中。那么在MyCat中执行是否可以成功呢?

image-20231030170136538

经过测试,我们看到,SQL语句执行报错。原因就是因为MyCat在执行该SQL语句时,需要往具体的数据库服务器中路由,而当前没有一个数据库服务器完全包含了订单以及省市区的表结构,造成SQL语句失败,报错。
对于上述的这种现象,我们如何来解决呢? 下面我们介绍的全局表,就可以轻松解决这个问题
全局表
	对于省、市、区/县表tb_areas_provinces , tb_areas_city , tb_areas_region,是属于数据字典表,在多个业务模块中都可能会遇到,可以将其设置为全局表,利于业务操作。
	修改schema.xml中的逻辑表的配置,修改 tb_areas_provinces、tb_areas_city、tb_areas_region 三个逻辑表,增加 type 属性,配置为global,就代表该表是全局表,就会在所涉及到的dataNode中创建给表。对于当前配置来说,也就意味着所有的节点中都有该表了。
	
<table name="tb_areas_provinces" dataNode="dn1,dn2,dn3" primaryKey="id" type="global"/>
<table name="tb_areas_city" dataNode="dn1,dn2,dn3" primaryKey="id" type="global"/>
<table name="tb_areas_region" dataNode="dn1,dn2,dn3" primaryKey="id" type="global"/>

image-20231030170311443

配置完毕后,重新启动MyCat。

1). 删除原来每一个数据库服务器中的所有表结构
2). 通过source指令,导入表及数据
	source /root/shopping-table.sql
	source /root/shopping-insert.sql
3). 检查每一个数据库服务器中的表及数据分布,看到三个节点中都有这三张全局表
4). 然后再次执行上面的多表联查的SQL语句
	SELECT order_id , payment ,receiver, province , city , area FROM tb_order_master o , tb_areas_provinces p , tb_areas_city c , tb_areas_region r WHERE o.receiver_province = p.provinceid AND o.receiver_city = c.cityid AND o.receiver_region = r.areaid ;
	是可以正常执行成功的。
5). 当在MyCat中更新全局表的时候,我们可以看到,所有分片节点中的数据都发生了变化,每个节点的全局表数据时刻保持一致。

image-20231030170404085

水平拆分
场景
在业务系统中, 有一张表(日志表), 业务系统每天都会产生大量的日志数据 , 单台服务器的数据存储及处理能力是有限的, 可以对数据库表进行拆分。

image-20231030170502249

准备

在这里插入图片描述

配置
1). schema.xml

<schema name="ITCAST" checkSQLschema="true" sqlMaxLimit="100">
<table name="tb_log" dataNode="dn4,dn5,dn6" primaryKey="id" rule="mod-long" /></schema>
<dataNode name="dn4" dataHost="dhost1" database="itcast" />
<dataNode name="dn5" dataHost="dhost2" database="itcast" />
<dataNode name="dn6" dataHost="dhost3" database="itcast" />

tb_log表最终落在3个节点中,分别是 dn4、dn5、dn6 ,而具体的数据分别存储在 dhost1、dhost2、dhost3的itcast数据库中。

2). server.xml
配置root用户既可以访问 SHOPPING 逻辑库,又可以访问ITCAST逻辑库。

<user name="root" defaultAccount="true">
	<property name="password">123456</property>
	<property name="schemas">SHOPPING,ITCAST</property>

	<!-- 表级 DML 权限设置 -->
	<!--
	<privileges check="true">
		<schema name="DB01" dml="0110" >
			<table name="TB_ORDER" dml="1110"></table>
		</schema>
	</privileges>
	-->
</user>
测试
配置完毕后,重新启动MyCat,然后在mycat的命令行中,执行如下SQL创建表、并插入数据,查看数据分布情况。

CREATE TABLE tb_log (
	id bigint(20) NOT NULL COMMENT 'ID',
	model_name varchar(200) DEFAULT NULL COMMENT '模块名',
	model_value varchar(200) DEFAULT NULL COMMENT '模块值',
	return_value varchar(200) DEFAULT NULL COMMENT '返回值',
	return_class varchar(200) DEFAULT NULL COMMENT '返回值类型',
	operate_user varchar(20) DEFAULT NULL COMMENT '操作用户',
	operate_time varchar(20) DEFAULT NULL COMMENT '操作时间',
	param_and_value varchar(500) DEFAULT NULL COMMENT '请求参数名及参数值',
	operate_class varchar(200) DEFAULT NULL COMMENT '操作类',
	operate_method varchar(200) DEFAULT NULL COMMENT '操作方法',
	cost_time bigint(20) DEFAULT NULL COMMENT '执行方法耗时, 单位 ms',
	source int(1) DEFAULT NULL COMMENT '来源 : 1 PC , 2 Android , 3 IOS',
	PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

INSERT INTO tb_log (id, model_name, model_value, return_value, return_class,
operate_user, operate_time, param_and_value, operate_class, operate_method,
cost_time,source)
VALUES('1','user','insert','success','java.lang.String','10001','2022-01-06
18:12:28','{\"age\":\"20\",\"name\":\"Tom\",\"gender\":\"1\"}','cn.itcast.contro
ller.UserController','insert','10',1);
INSERT INTO tb_log (id, model_name, model_value, return_value, return_class,
operate_user, operate_time, param_and_value, operate_class, operate_method,
cost_time,source)
VALUES('2','user','insert','success','java.lang.String','10001','2022-01-06
18:12:27','{\"age\":\"20\",\"name\":\"Tom\",\"gender\":\"1\"}','cn.itcast.contro
ller.UserController','insert','23',1);
INSERT INTO tb_log (id, model_name, model_value, return_value, return_class,
operate_user, operate_time, param_and_value, operate_class, operate_method,
cost_time,source)
VALUES('3','user','update','success','java.lang.String','10001','2022-01-06
18:16:45','{\"age\":\"20\",\"name\":\"Tom\",\"gender\":\"1\"}','cn.itcast.contro
ller.UserController','update','34',1);
INSERT INTO tb_log (id, model_name, model_value, return_value, return_class,
operate_user, operate_time, param_and_value, operate_class, operate_method,
cost_time,source)
VALUES('4','user','update','success','java.lang.String','10001','2022-01-06
18:16:45','{\"age\":\"20\",\"name\":\"Tom\",\"gender\":\"1\"}','cn.itcast.contro
ller.UserController','update','13',2);
INSERT INTO tb_log (id, model_name, model_value, return_value, return_class,
operate_user, operate_time, param_and_value, operate_class, operate_method,
cost_time,source)
VALUES('5','user','insert','success','java.lang.String','10001','2022-01-06
18:30:31','{\"age\":\"200\",\"name\":\"TomCat\",\"gender\":\"0\"}','cn.itcast.co
ntroller.UserController','insert','29',3);
INSERT INTO tb_log (id, model_name, model_value, return_value, return_class,
operate_user, operate_time, param_and_value, operate_class, operate_method,
cost_time,source)
VALUES('6','user','find','success','java.lang.String','10001','2022-01-06
18:30:31','{\"age\":\"200\",\"name\":\"TomCat\",\"gender\":\"0\"}','cn.itcast.co
ntroller.UserController','find','29',2);
分片规则
范围分片

1). 介绍

根据指定的字段及其配置的范围与数据节点的对应情况, 来决定该数据属于哪一个分片。

在这里插入图片描述

2). 配置

schema.xml逻辑表配置:

<table name="TB_ORDER" dataNode="dn1,dn2,dn3" rule="auto-sharding-long" />

schema.xml数据节点配置:
<dataNode name="dn1" dataHost="dhost1" database="db01" />
<dataNode name="dn2" dataHost="dhost2" database="db01" />
<dataNode name="dn3" dataHost="dhost3" database="db01" />

rule.xml分片规则配置:
<tableRule name="auto-sharding-long">
	<rule>
		<columns>id</columns>
		<algorithm>rang-long</algorithm>
	</rule>
</tableRule>
<function name="rang-long" class="io.mycat.route.function.AutoPartitionByLong">
	<property name="mapFile">autopartition-long.txt</property>
	<property name="defaultNode">0</property>
</function>

分片规则配置属性含义:

在这里插入图片描述

在rule.xml中配置分片规则时,关联了一个映射配置文件 autopartition-long.txt,该配置文件的配置如下:

# range start-end ,data node index
# K=1000,M=10000.
0-500M=0
500M-1000M=1
1000M-1500M=2

含义:0-500万之间的值,存储在0号数据节点(数据节点的索引从0开始) ; 500万-1000万之间的数据存储在1号数据节点 ; 1000万-1500万的数据节点存储在2号节点 ;

该分片规则,主要是针对于数字类型的字段适用。 在MyCat的入门程序中,我们使用的就是该分片规则。
取模分片

1). 介绍

根据指定的字段值与节点数量进行求模运算,根据运算结果, 来决定该数据属于哪一个分片。

在这里插入图片描述

2). 配置

schema.xml逻辑表配置:
<table name="tb_log" dataNode="dn4,dn5,dn6" primaryKey="id" rule="mod-long" />

schema.xml数据节点配置:
<dataNode name="dn4" dataHost="dhost1" database="itcast" />
<dataNode name="dn5" dataHost="dhost2" database="itcast" />
<dataNode name="dn6" dataHost="dhost3" database="itcast" />

rule.xml分片规则配置:
<tableRule name="mod-long">
	<rule>
		<columns>id</columns>
		<algorithm>mod-long</algorithm>
	</rule>
</tableRule>
	<function name="mod-long" class="io.mycat.route.function.PartitionByMod">
	<property name="count">3</property>
</function>

分片规则属性说明如下:

在这里插入图片描述

该分片规则,主要是针对于数字类型的字段适用。 在前面水平拆分的演示中,我们选择的就是取模分片。

3). 测试

配置完毕后,重新启动MyCat,然后在mycat的命令行中,执行如下SQL创建表、并插入数据,查看数 据分布情况。

一致性hash分片

1). 介绍

所谓一致性哈希,相同的哈希因子计算值总是被划分到相同的分区表中,不会因为分区节点的增加而改变原来数据的分区位置,有效的解决了分布式数据的拓容问题。

在这里插入图片描述

2). 配置

schema.xml中逻辑表配置:
<!-- 一致性hash -->
<table name="tb_order" dataNode="dn4,dn5,dn6" rule="sharding-by-murmur" />

schema.xml中数据节点配置:
<dataNode name="dn4" dataHost="dhost1" database="itcast" />
<dataNode name="dn5" dataHost="dhost2" database="itcast" />
<dataNode name="dn6" dataHost="dhost3" database="itcast" />

rule.xml中分片规则配置:
<tableRule name="sharding-by-murmur">
	<rule>
		<columns>id</columns>
		<algorithm>murmur</algorithm>
	</rule>
</tableRule>
<function name="murmur" class="io.mycat.route.function.PartitionByMurmurHash">
	<property name="seed">0</property><!-- 默认是0 -->
	<property name="count">3</property>
	<property name="virtualBucketTimes">160</property>
</function>

分片规则属性含义:

在这里插入图片描述

3). 测试

配置完毕后,重新启动MyCat,然后在mycat的命令行中,执行如下SQL创建表、并插入数据,查看数据分布情况。

create table tb_order(
id varchar(100) not null primary key,
money int null,
content varchar(200) null
);
INSERT INTO tb_order (id, money, content) VALUES ('b92fdaaf-6fc4-11ec-b831-
482ae33c4a2d', 10, 'b92fdaf8-6fc4-11ec-b831-482ae33c4a2d');
INSERT INTO tb_order (id, money, content) VALUES ('b93482b6-6fc4-11ec-b831-
482ae33c4a2d', 20, 'b93482d5-6fc4-11ec-b831-482ae33c4a2d');
INSERT INTO tb_order (id, money, content) VALUES ('b937e246-6fc4-11ec-b831-
482ae33c4a2d', 50, 'b937e25d-6fc4-11ec-b831-482ae33c4a2d');
INSERT INTO tb_order (id, money, content) VALUES ('b93be2dd-6fc4-11ec-b831-
482ae33c4a2d', 100, 'b93be2f9-6fc4-11ec-b831-482ae33c4a2d');
INSERT INTO tb_order (id, money, content) VALUES ('b93f2d68-6fc4-11ec-b831-
482ae33c4a2d', 130, 'b93f2d7d-6fc4-11ec-b831-482ae33c4a2d');
INSERT INTO tb_order (id, money, content) VALUES ('b9451b98-6fc4-11ec-b831-
482ae33c4a2d', 30, 'b9451bcc-6fc4-11ec-b831-482ae33c4a2d');
INSERT INTO tb_order (id, money, content) VALUES ('b9488ec1-6fc4-11ec-b831-
482ae33c4a2d', 560, 'b9488edb-6fc4-11ec-b831-482ae33c4a2d');
INSERT INTO tb_order (id, money, content) VALUES ('b94be6e6-6fc4-11ec-b831-
482ae33c4a2d', 10, 'b94be6ff-6fc4-11ec-b831-482ae33c4a2d');
INSERT INTO tb_order (id, money, content) VALUES ('b94ee10d-6fc4-11ec-b831-
482ae33c4a2d', 123, 'b94ee12c-6fc4-11ec-b831-482ae33c4a2d');
INSERT INTO tb_order (id, money, content) VALUES ('b952492a-6fc4-11ec-b831-
482ae33c4a2d', 145, 'b9524945-6fc4-11ec-b831-482ae33c4a2d');
INSERT INTO tb_order (id, money, content) VALUES ('b95553ac-6fc4-11ec-b831-
482ae33c4a2d', 543, 'b95553c8-6fc4-11ec-b831-482ae33c4a2d');
INSERT INTO tb_order (id, money, content) VALUES ('b9581cdd-6fc4-11ec-b831-
482ae33c4a2d', 17, 'b9581cfa-6fc4-11ec-b831-482ae33c4a2d');
INSERT INTO tb_order (id, money, content) VALUES ('b95afc0f-6fc4-11ec-b831-
482ae33c4a2d', 18, 'b95afc2a-6fc4-11ec-b831-482ae33c4a2d');
INSERT INTO tb_order (id, money, content) VALUES ('b95daa99-6fc4-11ec-b831-
482ae33c4a2d', 134, 'b95daab2-6fc4-11ec-b831-482ae33c4a2d');
INSERT INTO tb_order (id, money, content) VALUES ('b9667e3c-6fc4-11ec-b831-
482ae33c4a2d', 156, 'b9667e60-6fc4-11ec-b831-482ae33c4a2d');
INSERT INTO tb_order (id, money, content) VALUES ('b96ab489-6fc4-11ec-b831-
482ae33c4a2d', 175, 'b96ab4a5-6fc4-11ec-b831-482ae33c4a2d');
INSERT INTO tb_order (id, money, content) VALUES ('b96e2942-6fc4-11ec-b831-
482ae33c4a2d', 180, 'b96e295b-6fc4-11ec-b831-482ae33c4a2d');
INSERT INTO tb_order (id, money, content) VALUES ('b97092ec-6fc4-11ec-b831-
482ae33c4a2d', 123, 'b9709306-6fc4-11ec-b831-482ae33c4a2d');
INSERT INTO tb_order (id, money, content) VALUES ('b973727a-6fc4-11ec-b831-
482ae33c4a2d', 230, 'b9737293-6fc4-11ec-b831-482ae33c4a2d');
INSERT INTO tb_order (id, money, content) VALUES ('b978840f-6fc4-11ec-b831-
482ae33c4a2d', 560, 'b978843c-6fc4-11ec-b831-482ae33c4a2d');
枚举分片

1). 介绍

通过在配置文件中配置可能的枚举值, 指定数据分布到不同数据节点上, 本规则适用于按照省份、性 别、状态拆分数据等业务 。

在这里插入图片描述

2). 配置

schema.xml中逻辑表配置:
<!-- 枚举 -->
<table name="tb_user" dataNode="dn4,dn5,dn6" rule="sharding-by-intfile-enumstatus"/>

schema.xml中数据节点配置:
<dataNode name="dn4" dataHost="dhost1" database="itcast" />
<dataNode name="dn5" dataHost="dhost2" database="itcast" />
<dataNode name="dn6" dataHost="dhost3" database="itcast" />

rule.xml中分片规则配置:
<tableRule name="sharding-by-intfile">
	<rule>
		<columns>sharding_id</columns>
		<algorithm>hash-int</algorithm>
	</rule>
</tableRule>

<!-- 自己增加 tableRule -->
<tableRule name="sharding-by-intfile-enumstatus">
	<rule>
		<columns>status</columns>
		<algorithm>hash-int</algorithm>
	</rule>
</tableRule>

<function name="hash-int" class="io.mycat.route.function.PartitionByFileMap">
	<property name="defaultNode">2</property>
	<property name="mapFile">partition-hash-int.txt</property>
</function>

partition-hash-int.txt ,内容如下 :
1=0
2=1
3=2

分片规则属性含义:

在这里插入图片描述

3). 测试

配置完毕后,重新启动MyCat,然后在mycat的命令行中,执行如下SQL创建表、并插入数据,查看数据分布情况。

CREATE TABLE tb_user (
	id bigint(20) NOT NULL COMMENT 'ID',
	username varchar(200) DEFAULT NULL COMMENT '姓名',
	status int(2) DEFAULT '1' COMMENT '1: 未启用, 2: 已启用, 3: 已关闭',
	PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
insert into tb_user (id,username ,status) values(1,'Tom',1);
insert into tb_user (id,username ,status) values(2,'Cat',2);
insert into tb_user (id,username ,status) values(3,'Rose',3);
insert into tb_user (id,username ,status) values(4,'Coco',2);
insert into tb_user (id,username ,status) values(5,'Lily',1);
insert into tb_user (id,username ,status) values(6,'Tom',1);
insert into tb_user (id,username ,status) values(7,'Cat',2);
insert into tb_user (id,username ,status) values(8,'Rose',3);
insert into tb_user (id,username ,status) values(9,'Coco',2);
insert into tb_user (id,username ,status) values(10,'Lily',1);
应用指定算法

1). 介绍

运行阶段由应用自主决定路由到那个分片 , 直接根据字符子串(必须是数字)计算分片号。

在这里插入图片描述

2). 配置

schema.xml中逻辑表配置:
<!-- 应用指定算法 -->
<table name="tb_app" dataNode="dn4,dn5,dn6" rule="sharding-by-substring" />

schema.xml中数据节点配置:
<dataNode name="dn4" dataHost="dhost1" database="itcast" />
<dataNode name="dn5" dataHost="dhost2" database="itcast" />
<dataNode name="dn6" dataHost="dhost3" database="itcast" />

rule.xml中分片规则配置:
<tableRule name="sharding-by-substring">
	<rule>
		<columns>id</columns>
		<algorithm>sharding-by-substring</algorithm>
	</rule>
</tableRule>
<function name="sharding-by-substring" class="io.mycat.route.function.PartitionDirectBySubString">
	<property name="startIndex">0</property> <!-- zero-based -->
	<property name="size">2</property>
	<property name="partitionCount">3</property>
	<property name="defaultPartition">0</property>
</function>

分片规则属性含义:

在这里插入图片描述

示例说明 :
id=05-100000002 , 在此配置中代表根据id中从 startIndex=0,开始,截取siz=2位数字即 05,05就是获取的分区,如果没找到对应的分片则默认分配到defaultPartition 。

3). 测试

配置完毕后,重新启动MyCat,然后在mycat的命令行中,执行如下SQL创建表、并插入数据,查看数据分布情况。

CREATE TABLE tb_app (
	id varchar(10) NOT NULL COMMENT 'ID',
	name varchar(200) DEFAULT NULL COMMENT '名称',
	PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
insert into tb_app (id,name) values('0000001','Testx00001');
insert into tb_app (id,name) values('0100001','Test100001');
insert into tb_app (id,name) values('0100002','Test200001');
insert into tb_app (id,name) values('0200001','Test300001');
insert into tb_app (id,name) values('0200002','TesT400001');
固定分片hash算法

1). 介绍

该算法类似于十进制的求模运算,但是为二进制的操作,例如,取 id 的二进制低 10 位 与 1111111111 进行位 & 运算,位与运算最小值为 0000000000,最大值为1111111111,转换为十进制,也就是位于0-1023之间。

在这里插入图片描述

特点:
	如果是求模,连续的值,分别分配到各个不同的分片;但是此算法会将连续的值可能分配到相同的分片,降低事务处理的难度。
	可以均匀分配,也可以非均匀分配。
	分片字段必须为数字类型。

2). 配置

schema.xml中逻辑表配置:
<!-- 固定分片hash算法 -->
<table name="tb_longhash" dataNode="dn4,dn5,dn6" rule="sharding-by-long-hash" />

schema.xml中数据节点配置:
<dataNode name="dn4" dataHost="dhost1" database="itcast" />
<dataNode name="dn5" dataHost="dhost2" database="itcast" />
<dataNode name="dn6" dataHost="dhost3" database="itcast" />

rule.xml中分片规则配置:
<tableRule name="sharding-by-long-hash">
	<rule>
		<columns>id</columns>
		<algorithm>sharding-by-long-hash</algorithm>
	</rule>
</tableRule>
<!-- 分片总长度为1024,count与length数组长度必须一致; -->
<function name="sharding-by-long-hash" class="io.mycat.route.function.PartitionByLong">
	<property name="partitionCount">2,1</property>
	<property name="partitionLength">256,512</property>
</function>

分片规则属性含义:

在这里插入图片描述

约束 :
1). 分片长度 : 默认最大2^10 , 为 1024 ;
2). count, length的数组长度必须是一致的 ;
以上分为三个分区:0-255,256-511,512-1023

在这里插入图片描述

3). 测试

配置完毕后,重新启动MyCat,然后在mycat的命令行中,执行如下SQL创建表、并插入数据,查看数据分布情况。

CREATE TABLE tb_longhash (
	id int(11) NOT NULL COMMENT 'ID',
	name varchar(200) DEFAULT NULL COMMENT '名称',
	firstChar char(1) COMMENT '首字母',
	PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
insert into tb_longhash (id,name,firstChar) values(1,'七匹狼','Q');
insert into tb_longhash (id,name,firstChar) values(2,'八匹狼','B');
insert into tb_longhash (id,name,firstChar) values(3,'九匹狼','J');
insert into tb_longhash (id,name,firstChar) values(4,'十匹狼','S');
insert into tb_longhash (id,name,firstChar) values(5,'六匹狼','L');
insert into tb_longhash (id,name,firstChar) values(6,'五匹狼','W');
insert into tb_longhash (id,name,firstChar) values(7,'四匹狼','S');
insert into tb_longhash (id,name,firstChar) values(8,'三匹狼','S');
insert into tb_longhash (id,name,firstChar) values(9,'两匹狼','L');
字符串hash解析算法

1). 介绍

截取字符串中的指定位置的子字符串, 进行hash算法, 算出分片。

image-20231030184156842

2). 配置

schema.xml中逻辑表配置:
<!-- 字符串hash解析算法 -->
<table name="tb_strhash" dataNode="dn4,dn5" rule="sharding-by-stringhash" />

schema.xml中数据节点配置:
<dataNode name="dn4" dataHost="dhost1" database="itcast" />
<dataNode name="dn5" dataHost="dhost2" database="itcast" />

rule.xml中分片规则配置:
<tableRule name="sharding-by-stringhash">
	<rule>
		<columns>name</columns>
		<algorithm>sharding-by-stringhash</algorithm>
	</rule>
</tableRule>
<function name="sharding-by-stringhash"
class="io.mycat.route.function.PartitionByString">
	<property name="partitionLength">512</property> <!-- zero-based -->
	<property name="partitionCount">2</property>
	<property name="hashSlice">0:2</property>
</function>

分片规则属性含义:

在这里插入图片描述

image-20231030184308388

3). 测试

配置完毕后,重新启动MyCat,然后在mycat的命令行中,执行如下SQL创建表、并插入数据,查看数据分布情况。

create table tb_strhash(
	name varchar(20) primary key,
	content varchar(100)
)engine=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO tb_strhash (name,content) VALUES('T1001', UUID());
INSERT INTO tb_strhash (name,content) VALUES('ROSE', UUID());
INSERT INTO tb_strhash (name,content) VALUES('JERRY', UUID());
INSERT INTO tb_strhash (name,content) VALUES('CRISTINA', UUID());
INSERT INTO tb_strhash (name,content) VALUES('TOMCAT', UUID());
按天分片算法

1). 介绍

按照日期及对应的时间周期来分片。

在这里插入图片描述

2). 配置

schema.xml中逻辑表配置:
<!-- 按天分片 -->
<table name="tb_datepart" dataNode="dn4,dn5,dn6" rule="sharding-by-date" />

schema.xml中数据节点配置:
<dataNode name="dn4" dataHost="dhost1" database="itcast" />
<dataNode name="dn5" dataHost="dhost2" database="itcast" />
<dataNode name="dn6" dataHost="dhost3" database="itcast" />

rule.xml中分片规则配置:
<tableRule name="sharding-by-date">
	<rule>
		<columns>create_time</columns>
		<algorithm>sharding-by-date</algorithm>
	</rule>
</tableRule>

<function name="sharding-by-date"
class="io.mycat.route.function.PartitionByDate">
	<property name="dateFormat">yyyy-MM-dd</property>
	<property name="sBeginDate">2022-01-01</property>
	<property name="sEndDate">2022-01-30</property>
	<property name="sPartionDay">10</property>
</function>
<!--
	从开始时间开始,每10天为一个分片,到达结束时间之后,会重复开始分片插入配置表的 dataNode 的分片,必须和分片规则数量一致,例如 2022-01-01 到 2022-12-31 ,每10天一个分片,一共需要37个分片。
-->

分片规则属性含义:

在这里插入图片描述

3). 测试

配置完毕后,重新启动MyCat,然后在mycat的命令行中,执行如下SQL创建表、并插入数据,查看数据分布情况。

create table tb_datepart(
	id bigint not null comment 'ID' primary key,
	name varchar(100) null comment '姓名',
	create_time date null
);
insert into tb_datepart(id,name ,create_time) values(1,'Tom','2022-01-01');
insert into tb_datepart(id,name ,create_time) values(2,'Cat','2022-01-10');
insert into tb_datepart(id,name ,create_time) values(3,'Rose','2022-01-11');
insert into tb_datepart(id,name ,create_time) values(4,'Coco','2022-01-20');
insert into tb_datepart(id,name ,create_time) values(5,'Rose2','2022-01-21');
insert into tb_datepart(id,name ,create_time) values(6,'Coco2','2022-01-30');
insert into tb_datepart(id,name ,create_time) values(7,'Coco3','2022-01-31');
自然月分片

1). 介绍

使用场景为按照月份来分片, 每个自然月为一个分片。

在这里插入图片描述

2). 配置

schema.xml中逻辑表配置:
<!-- 按自然月分片 -->
<table name="tb_monthpart" dataNode="dn4,dn5,dn6" rule="sharding-by-month" />

schema.xml中数据节点配置:
<dataNode name="dn4" dataHost="dhost1" database="itcast" />
<dataNode name="dn5" dataHost="dhost2" database="itcast" />
<dataNode name="dn6" dataHost="dhost3" database="itcast" />

rule.xml中分片规则配置:
<tableRule name="sharding-by-month">
	<rule>
		<columns>create_time</columns>
		<algorithm>partbymonth</algorithm>
	</rule>
</tableRule>
<function name="partbymonth" class="io.mycat.route.function.PartitionByMonth">
	<property name="dateFormat">yyyy-MM-dd</property>
	<property name="sBeginDate">2022-01-01</property>
	<property name="sEndDate">2022-03-31</property>
</function>
<!--
	从开始时间开始,一个月为一个分片,到达结束时间之后,会重复开始分片插入配置表的 dataNode 的分片,必须和分片规则数量一致,例如 2022-01-01 到 2022-12-31 ,一共需要12个分片。
-->

分片规则属性含义:

在这里插入图片描述

3). 测试

配置完毕后,重新启动MyCat,然后在mycat的命令行中,执行如下SQL创建表、并插入数据,查看数据分布情况。

create table tb_monthpart(
	id bigint not null comment 'ID' primary key,
	name varchar(100) null comment '姓名',
	create_time date null
);
insert into tb_monthpart(id,name ,create_time) values(1,'Tom','2022-01-01');
insert into tb_monthpart(id,name ,create_time) values(2,'Cat','2022-01-10');
insert into tb_monthpart(id,name ,create_time) values(3,'Rose','2022-01-31');
insert into tb_monthpart(id,name ,create_time) values(4,'Coco','2022-02-20');
insert into tb_monthpart(id,name ,create_time) values(5,'Rose2','2022-02-25');
insert into tb_monthpart(id,name ,create_time) values(6,'Coco2','2022-03-10');
insert into tb_monthpart(id,name ,create_time) values(7,'Coco3','2022-03-31');
insert into tb_monthpart(id,name ,create_time) values(8,'Coco4','2022-04-10');
insert into tb_monthpart(id,name ,create_time) values(9,'Coco5','2022-04-30');

MyCat管理及监控

MyCat原理

在这里插入图片描述

在MyCat中,当执行一条SQL语句时,MyCat需要进行SQL解析、分片分析、路由分析、读写分离分析等操作,最终经过一系列的分析决定将当前的SQL语句到底路由到那几个(或哪一个)节点数据库,数据库将数据执行完毕后,如果有返回的结果,则将结果返回给MyCat,最终还需要在MyCat中进行结果合并、聚合处理、排序处理、分页处理等操作,最终再将结果返回给客户端。

而在MyCat的使用过程中,MyCat官方也提供了一个管理监控平台MyCat-Web(MyCat-eye)。Mycat-web 是 Mycat 可视化运维的管理和监控平台,弥补了 Mycat 在监控上的空白。帮 Mycat分担统计任务和配置管理任务。Mycat-web 引入了 ZooKeeper 作为配置中心,可以管理多个节
点。Mycat-web 主要管理和监控 Mycat 的流量、连接、活动线程和内存等,具备 IP 白名单、邮件告警等模块,还可以统计 SQL 并分析慢 SQL 和高频 SQL 等。为优化 SQL 提供依据。
MyCat管理
Mycat默认开通2个端口,可以在server.xml中进行修改。
	8066 数据访问端口,即进行 DML 和 DDL 操作。
	9066 数据库管理端口,即 mycat 服务管理控制功能,用于管理mycat的整个集群状态
	
连接MyCat的管理控制台:
mysql -h 192.168.200.210 -p 9066 -uroot -p123456

在这里插入图片描述

MyCat-eye
介绍
Mycat-web(Mycat-eye)是对mycat-server提供监控服务,功能不局限于对mycat-server使用。他通过JDBC连接对Mycat、Mysql监控,监控远程服务器(目前仅限于linux系统)的cpu、内存、网络、磁盘。
Mycat-eye运行过程中需要依赖zookeeper,因此需要先安装zookeeper。
安装
1). zookeeper安装
2). Mycat-web安装
访问

image-20231030185135412

配置

1). 开启MyCat的实时统计功能(server.xml)

<property name="useSqlStat">1</property> <!-- 1为开启实时统计、0为关闭 -->

2). 在MyCat监控界面配置服务地址

image-20231030185250057

测试
配置好了之后,我们可以通过MyCat执行一系列的增删改查的测试,然后过一段时间之后,打开mycat-eye的管理界面,查看mycat-eye监控到的数据信息。

A. 性能监控

在这里插入图片描述

B. 物理节点

image-20231030185342800

C. SQL统计

image-20231030185357353

D. SQL表分析

image-20231030185409222

E. SQL监控

image-20231030185421490

F. 高频SQL

image-20231030185441556

读写分离

介绍

读写分离,简单地说是把对数据库的读和写操作分开,以对应不同的数据库服务器。主数据库提供写操作,从数据库提供读操作,这样能有效地减轻单台数据库的压力。
通过MyCat即可轻易实现上述功能,不仅可以支持MySQL,也可以支持Oracle和SQL Server。

image-20231030151906050

一主一从

原理
MySQL的主从复制,是基于二进制日志(binlog)实现的。

image-20231030152015904

准备

image-20231030152044858

备注:主从复制的搭建,可以参考前面课程中 主从复制 章节讲解的步骤操作。

一主一从读写分离

MyCat控制后台数据库的读写分离和负载均衡由schema.xml文件datahost标签的balance属性控制。
schema.xml配置
<!-- 配置逻辑库 -->
<schema name="ITCAST_RW" checkSQLschema="true" sqlMaxLimit="100" dataNode="dn7"></schema>
<dataNode name="dn7" dataHost="dhost7" database="itcast" />

<dataHost name="dhost7" maxCon="1000" minCon="10" balance="1" writeType="0" dbType="mysql" dbDriver="jdbc" switchType="1" slaveThreshold="100">

	<heartbeat>select user()</heartbeat>
	<writeHost host="master1" url="jdbc:mysql://192.168.200.211:3306?
useSSL=false&amp;serverTimezone=Asia/Shanghai&amp;characterEncoding=utf8"
user="root" password="1234" >
	<readHost host="slave1" url="jdbc:mysql://192.168.200.212:3306?
useSSL=false&amp;serverTimezone=Asia/Shanghai&amp;characterEncoding=utf8"
user="root" password="1234" />
	</writeHost>
</dataHost>

上述配置的具体关联对应情况如下:

image-20231030152305894

writeHost代表的是写操作对应的数据库,readHost代表的是读操作对应的数据库。 所以我们要想实现读写分离,就得配置writeHost关联的是主库,readHost关联的是从库。
而仅仅配置好了writeHost以及readHost还不能完成读写分离,还需要配置一个非常重要的负责均衡的参数 balance,取值有4种,具体含义如下:

在这里插入图片描述

所以,在一主一从模式的读写分离中,balance配置1或3都是可以完成读写分离的。
server.xml配置

配置root用户可以访问SHOPPING、ITCAST 以及 ITCAST_RW逻辑库。

<user name="root" defaultAccount="true">
	<property name="password">123456</property>
	<property name="schemas">SHOPPING,ITCAST,ITCAST_RW</property>
	
	<!-- 表级 DML 权限设置 -->
	<!--
	<privileges check="true">
		<schema name="DB01" dml="0110" >
			<table name="TB_ORDER" dml="1110"></table>
		</schema>
	</privileges>
	-->
</user>
测试
配置完毕MyCat后,重新启动MyCat。

bin/mycat stop
bin/mycat start

然后观察,在执行增删改操作时,对应的主库及从库的数据变化。 在执行查询操作时,检查主库及从库对应的数据变化。
在测试中,我们可以发现当主节点Master宕机之后,业务系统就只能够读,而不能写入数据了。

在这里插入图片描述

那如何解决这个问题呢?这个时候我们就得通过另外一种主从复制结构来解决了,也就是我们接下来讲解的双主双从。

双主双从

介绍
一个主机 Master1 用于处理所有写请求,它的从机 Slave1 和另一台主机 Master2 还有它的从机 Slave2 负责所有读请求。当 Master1 主机宕机后,Master2 主机负责写请求,Master1 、Master2 互为备机。架构图如下:

在这里插入图片描述

准备

image-20231030152637177

关闭以上所有服务器的防火墙:
	systemctl stop firewalld
	systemctl disable firewalld
搭建
主库配置

1)Master1(192.168.200.211)

image-20231030152830989

'A. 修改配置文件 /etc/my.cnf'

#mysql 服务ID,保证整个集群环境中唯一,取值范围:1 – 2^32-1,默认为1
server-id=1
#指定同步的数据库
binlog-do-db=db01
binlog-do-db=db02
binlog-do-db=db03
# 在作为从数据库的时候,有写入操作也要更新二进制日志文件
log-slave-updates

'B. 重启MySQL服务器'
systemctl restart mysqld


'C. 创建账户并授权'
#创建itcast用户,并设置密码,该用户可在任意主机连接该MySQL服务
CREATE USER 'itcast'@'%' IDENTIFIED WITH mysql_native_password BY 'Root@123456';
#为 'itcast'@'%' 用户分配主从复制权限
GRANT REPLICATION SLAVE ON *.* TO 'itcast'@'%';

通过指令,查看两台主库的二进制日志坐标
show master status;

image-20231030152957665

2)Master2(192.168.200.213)

在这里插入图片描述

A. 修改配置文件 /etc/my.cnf

#mysql 服务ID,保证整个集群环境中唯一,取值范围:1 – 2^32-1,默认为1
server-id=3
#指定同步的数据库
binlog-do-db=db01
binlog-do-db=db02
binlog-do-db=db03
# 在作为从数据库的时候,有写入操作也要更新二进制日志文件
log-slave-updates

B. 重启MySQL服务器
systemctl restart mysqld

C. 创建账户并授权
#创建itcast用户,并设置密码,该用户可在任意主机连接该MySQL服务
CREATE USER 'itcast'@'%' IDENTIFIED WITH mysql_native_password BY 'Root@123456';
#为 'itcast'@'%' 用户分配主从复制权限
GRANT REPLICATION SLAVE ON *.* TO 'itcast'@'%';

通过指令,查看两台主库的二进制日志坐标
show master status ;

image-20231030154230072

从库配置

1). Slave1(192.168.200.212)

在这里插入图片描述

A. 修改配置文件 /etc/my.cnf
#mysql 服务ID,保证整个集群环境中唯一,取值范围:1 – 232-1,默认为1
server-id=2

B. 重新启动MySQL服务器
systemctl restart mysqld

2). Slave2(192.168.200.214)

在这里插入图片描述

A. 修改配置文件 /etc/my.cnf
#mysql 服务ID,保证整个集群环境中唯一,取值范围:1 – 232-1,默认为1
server-id=4

B. 重新启动MySQL服务器
systemctl restart mysqld
从库关联主库

1). 两台从库配置关联的主库

在这里插入图片描述

需要注意slave1对应的是master1,slave2对应的是master2。

A. 在 slave1(192.168.200.212)上执行
CHANGE MASTER TO MASTER_HOST='192.168.200.211', MASTER_USER='itcast',
MASTER_PASSWORD='Root@123456', MASTER_LOG_FILE='binlog.000002',
MASTER_LOG_POS=663;

B. 在 slave2(192.168.200.214)上执行
CHANGE MASTER TO MASTER_HOST='192.168.200.213', MASTER_USER='itcast',
MASTER_PASSWORD='Root@123456', MASTER_LOG_FILE='binlog.000002',
MASTER_LOG_POS=663;

C. 启动两台从库主从复制,查看从库状态
start slave;
show slave status \G;

image-20231030154736239

2). 两台主库相互复制

在这里插入图片描述

Master2 复制 Master1,Master1 复制 Master2。

A. 在 Master1(192.168.200.211)上执行
CHANGE MASTER TO MASTER_HOST='192.168.200.213', MASTER_USER='itcast',
MASTER_PASSWORD='Root@123456', MASTER_LOG_FILE='binlog.000002',
MASTER_LOG_POS=663;

B. 在 Master2(192.168.200.213)上执行
CHANGE MASTER TO MASTER_HOST='192.168.200.211', MASTER_USER='itcast',
MASTER_PASSWORD='Root@123456', MASTER_LOG_FILE='binlog.000002',
MASTER_LOG_POS=663;

C. 启动两台从库主从复制,查看从库状态
start slave;
show slave status \G;

image-20231030154850476

经过上述的三步配置之后,双主双从的复制结构就已经搭建完成了。 接下来,我们可以来测试验证一下。
测试
分别在两台主库Master1、Master2上执行DDL、DML语句,查看涉及到的数据库服务器的数据同步情况。

create database db01;
use db01;
create table tb_user(
	id int(11) not null primary key ,
	name varchar(50) not null,
	sex varchar(1)
)engine=innodb default charset=utf8mb4;

insert into tb_user(id,name,sex) values(1,'Tom','1');
insert into tb_user(id,name,sex) values(2,'Trigger','0');
insert into tb_user(id,name,sex) values(3,'Dawn','1');
insert into tb_user(id,name,sex) values(4,'Jack Ma','1');
insert into tb_user(id,name,sex) values(5,'Coco','0');
insert into tb_user(id,name,sex) values(6,'Jerry','1');

在Master1中执行DML、DDL操作,看看数据是否可以同步到另外的三台数据库中。
在Master2中执行DML、DDL操作,看看数据是否可以同步到另外的三台数据库中。
完成了上述双主双从的结构搭建之后,接下来,我们再来看看如何完成这种双主双从的读写分离。

双主双从读写分离

配置

MyCat控制后台数据库的读写分离和负载均衡由schema.xml文件datahost标签的balance属性控制,通过writeType及switchType来完成失败自动切换的。

1). schema.xml

配置逻辑库:
<schema name="ITCAST_RW2" checkSQLschema="true" sqlMaxLimit="100" dataNode="dn7"></schema>

配置数据节点:
<dataNode name="dn7" dataHost="dhost7" database="db01" />

配置节点主机:
<dataHost name="dhost7" maxCon="1000" minCon="10" balance="1" writeType="0"
dbType="mysql" dbDriver="jdbc" switchType="1" slaveThreshold="100">
	<heartbeat>select user()</heartbeat>
	
	<writeHost host="master1" url="jdbc:mysql://192.168.200.211:3306?
useSSL=false&amp;serverTimezone=Asia/Shanghai&amp;characterEncoding=utf8"
user="root" password="1234" >
	<readHost host="slave1" url="jdbc:mysql://192.168.200.212:3306?
useSSL=false&amp;serverTimezone=Asia/Shanghai&amp;characterEncoding=utf8"
user="root" password="1234" />
	</writeHost>
	
	<writeHost host="master2" url="jdbc:mysql://192.168.200.213:3306?
useSSL=false&amp;serverTimezone=Asia/Shanghai&amp;characterEncoding=utf8"
user="root" password="1234" >
	<readHost host="slave2" url="jdbc:mysql://192.168.200.214:3306?
useSSL=false&amp;serverTimezone=Asia/Shanghai&amp;characterEncoding=utf8"
user="root" password="1234" />
	</writeHost>
</dataHost>

具体的对应情况如下:

image-20231030155222441

属性说明:

balance="1"
	代表全部的 readHost 与 stand by writeHost 参与 select 语句的负载均衡,简单的说,当双主双从模式(M1->S1,M2->S2,并且 M1 与 M2 互为主备),正常情况下,M2,S1,S2 都参与 select 语句的负载均衡 ;
	
	writeType
		0 : 写操作都转发到第1台writeHost, writeHost1挂了, 会切换到writeHost2上;
		1 : 所有的写操作都随机地发送到配置的writeHost上 ;
	switchType
		-1 : 不自动切换
		1 : 自动切换

2). user.xml

配置root用户也可以访问到逻辑库 ITCAST_RW2。

<user name="root" defaultAccount="true">
	<property name="password">123456</property>
	<property name="schemas">SHOPPING,ITCAST,ITCAST_RW2</property>

	<!-- 表级 DML 权限设置 -->
	<!--
	<privileges check="true">
		<schema name="DB01" dml="0110" >
			<table name="TB_ORDER" dml="1110"></table>
		</schema>
	</privileges>
	-->
</user>
测试
登录MyCat,测试查询及更新操作,判定是否能够进行读写分离,以及读写分离的策略是否正确。
当主库挂掉一个之后,是否能够自动切换。
'A. 修改配置文件 /etc/my.cnf'

#mysql 服务ID,保证整个集群环境中唯一,取值范围:1 – 2^32-1,默认为1
server-id=1
#指定同步的数据库
binlog-do-db=db01
binlog-do-db=db02
binlog-do-db=db03
# 在作为从数据库的时候,有写入操作也要更新二进制日志文件
log-slave-updates

'B. 重启MySQL服务器'
systemctl restart mysqld


'C. 创建账户并授权'
#创建itcast用户,并设置密码,该用户可在任意主机连接该MySQL服务
CREATE USER 'itcast'@'%' IDENTIFIED WITH mysql_native_password BY 'Root@123456';
#为 'itcast'@'%' 用户分配主从复制权限
GRANT REPLICATION SLAVE ON *.* TO 'itcast'@'%';

通过指令,查看两台主库的二进制日志坐标
show master status;

在这里插入图片描述

2)Master2(192.168.200.213)

在这里插入图片描述

A. 修改配置文件 /etc/my.cnf

#mysql 服务ID,保证整个集群环境中唯一,取值范围:1 – 2^32-1,默认为1
server-id=3
#指定同步的数据库
binlog-do-db=db01
binlog-do-db=db02
binlog-do-db=db03
# 在作为从数据库的时候,有写入操作也要更新二进制日志文件
log-slave-updates

B. 重启MySQL服务器
systemctl restart mysqld

C. 创建账户并授权
#创建itcast用户,并设置密码,该用户可在任意主机连接该MySQL服务
CREATE USER 'itcast'@'%' IDENTIFIED WITH mysql_native_password BY 'Root@123456';
#为 'itcast'@'%' 用户分配主从复制权限
GRANT REPLICATION SLAVE ON *.* TO 'itcast'@'%';

通过指令,查看两台主库的二进制日志坐标
show master status ;

在这里插入图片描述

从库配置

1). Slave1(192.168.200.212)

在这里插入图片描述

A. 修改配置文件 /etc/my.cnf
#mysql 服务ID,保证整个集群环境中唯一,取值范围:1 – 232-1,默认为1
server-id=2

B. 重新启动MySQL服务器
systemctl restart mysqld

2). Slave2(192.168.200.214)

在这里插入图片描述

A. 修改配置文件 /etc/my.cnf
#mysql 服务ID,保证整个集群环境中唯一,取值范围:1 – 232-1,默认为1
server-id=4

B. 重新启动MySQL服务器
systemctl restart mysqld
从库关联主库

1). 两台从库配置关联的主库

在这里插入图片描述

需要注意slave1对应的是master1,slave2对应的是master2。

A. 在 slave1(192.168.200.212)上执行
CHANGE MASTER TO MASTER_HOST='192.168.200.211', MASTER_USER='itcast',
MASTER_PASSWORD='Root@123456', MASTER_LOG_FILE='binlog.000002',
MASTER_LOG_POS=663;

B. 在 slave2(192.168.200.214)上执行
CHANGE MASTER TO MASTER_HOST='192.168.200.213', MASTER_USER='itcast',
MASTER_PASSWORD='Root@123456', MASTER_LOG_FILE='binlog.000002',
MASTER_LOG_POS=663;

C. 启动两台从库主从复制,查看从库状态
start slave;
show slave status \G;

在这里插入图片描述

2). 两台主库相互复制

在这里插入图片描述

Master2 复制 Master1,Master1 复制 Master2。

A. 在 Master1(192.168.200.211)上执行
CHANGE MASTER TO MASTER_HOST='192.168.200.213', MASTER_USER='itcast',
MASTER_PASSWORD='Root@123456', MASTER_LOG_FILE='binlog.000002',
MASTER_LOG_POS=663;

B. 在 Master2(192.168.200.213)上执行
CHANGE MASTER TO MASTER_HOST='192.168.200.211', MASTER_USER='itcast',
MASTER_PASSWORD='Root@123456', MASTER_LOG_FILE='binlog.000002',
MASTER_LOG_POS=663;

C. 启动两台从库主从复制,查看从库状态
start slave;
show slave status \G;

在这里插入图片描述

经过上述的三步配置之后,双主双从的复制结构就已经搭建完成了。 接下来,我们可以来测试验证一下。
测试
分别在两台主库Master1、Master2上执行DDL、DML语句,查看涉及到的数据库服务器的数据同步情况。

create database db01;
use db01;
create table tb_user(
	id int(11) not null primary key ,
	name varchar(50) not null,
	sex varchar(1)
)engine=innodb default charset=utf8mb4;

insert into tb_user(id,name,sex) values(1,'Tom','1');
insert into tb_user(id,name,sex) values(2,'Trigger','0');
insert into tb_user(id,name,sex) values(3,'Dawn','1');
insert into tb_user(id,name,sex) values(4,'Jack Ma','1');
insert into tb_user(id,name,sex) values(5,'Coco','0');
insert into tb_user(id,name,sex) values(6,'Jerry','1');

在Master1中执行DML、DDL操作,看看数据是否可以同步到另外的三台数据库中。
在Master2中执行DML、DDL操作,看看数据是否可以同步到另外的三台数据库中。
完成了上述双主双从的结构搭建之后,接下来,我们再来看看如何完成这种双主双从的读写分离。

双主双从读写分离

配置

MyCat控制后台数据库的读写分离和负载均衡由schema.xml文件datahost标签的balance属性控制,通过writeType及switchType来完成失败自动切换的。

1). schema.xml

配置逻辑库:
<schema name="ITCAST_RW2" checkSQLschema="true" sqlMaxLimit="100" dataNode="dn7"></schema>

配置数据节点:
<dataNode name="dn7" dataHost="dhost7" database="db01" />

配置节点主机:
<dataHost name="dhost7" maxCon="1000" minCon="10" balance="1" writeType="0"
dbType="mysql" dbDriver="jdbc" switchType="1" slaveThreshold="100">
	<heartbeat>select user()</heartbeat>
	
	<writeHost host="master1" url="jdbc:mysql://192.168.200.211:3306?
useSSL=false&amp;serverTimezone=Asia/Shanghai&amp;characterEncoding=utf8"
user="root" password="1234" >
	<readHost host="slave1" url="jdbc:mysql://192.168.200.212:3306?
useSSL=false&amp;serverTimezone=Asia/Shanghai&amp;characterEncoding=utf8"
user="root" password="1234" />
	</writeHost>
	
	<writeHost host="master2" url="jdbc:mysql://192.168.200.213:3306?
useSSL=false&amp;serverTimezone=Asia/Shanghai&amp;characterEncoding=utf8"
user="root" password="1234" >
	<readHost host="slave2" url="jdbc:mysql://192.168.200.214:3306?
useSSL=false&amp;serverTimezone=Asia/Shanghai&amp;characterEncoding=utf8"
user="root" password="1234" />
	</writeHost>
</dataHost>

具体的对应情况如下:

在这里插入图片描述

属性说明:

balance="1"
	代表全部的 readHost 与 stand by writeHost 参与 select 语句的负载均衡,简单的说,当双主双从模式(M1->S1,M2->S2,并且 M1 与 M2 互为主备),正常情况下,M2,S1,S2 都参与 select 语句的负载均衡 ;
	
	writeType
		0 : 写操作都转发到第1台writeHost, writeHost1挂了, 会切换到writeHost2上;
		1 : 所有的写操作都随机地发送到配置的writeHost上 ;
	switchType
		-1 : 不自动切换
		1 : 自动切换

2). user.xml

配置root用户也可以访问到逻辑库 ITCAST_RW2。

<user name="root" defaultAccount="true">
	<property name="password">123456</property>
	<property name="schemas">SHOPPING,ITCAST,ITCAST_RW2</property>

	<!-- 表级 DML 权限设置 -->
	<!--
	<privileges check="true">
		<schema name="DB01" dml="0110" >
			<table name="TB_ORDER" dml="1110"></table>
		</schema>
	</privileges>
	-->
</user>
测试
登录MyCat,测试查询及更新操作,判定是否能够进行读写分离,以及读写分离的策略是否正确。
当主库挂掉一个之后,是否能够自动切换。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值