MySQL笔记

基础

用命令行窗口连接MySQL数据库

1
2
3
4
mysql  -h 主机名  -p 端 -u 用户名 -p 密码
# 登陆前保证服务启动
net stop mysql
net start mysql

使用指令创建数据库

1
2
3
4
create database 数据库名 #使用指令创建数据库
drop database 数据库名 #使用指令删除数据库
#创建一个使用utf-8字符集的数据库
create database 数据库名 character set utf-8

数据库备份恢复操作

1
2
3
4
5
6
7
8
9
10
#显示数据库语句
show databases;
#显示接口创建语句
show create database 数据库名
#备份数据库(在DOS执行)
mysql -u用户名 -p 密码 -b 数据库1,数据库2>文件名.sql
#恢复数据库
source 文件名.sql
#备份库的表
mysqldump -u 用户名 -p 密码 数据库 表1,表2 > d://文件名.sql

数据类型

char(n) 长度为 $\mathrm{n}$ 的字符型

VARCHAR(n) 最大长度为 $\mathrm{n}$ 的变长字符型

NUMBER(n) 长度为 $\mathrm{n}$ 的数字型

INT 长整型 (4B)

SMALLINT 短整型 (2B)

BIGINT 大整型 (8B)

FLOAT(n) 精度至少为 n 位数字的浮点数

DATE 日期,格式为 YYYY-MM-DD

TIME 时间,格式为 HH:MM:SS

表操作

1
2
3
4
5
6
7
8
#创建表
create table 表名(
id int
character set 字符集
# 添加数据:
insert into 表名 values()
# 查询表
select * from 表名
1
2
3
4
5
6
7
8
9
10
11
12
# 添加列
alter table 表名 add(xxx xxx,xxxx xxx);
# 修改列
alter table 表名 modify (xx xxx,xx xx);
# 删除列
alter table 表名 drop ();

查看表的结构:desc 表名

修改表名:rename table 表名 to 新表名

修改表字符集 alter table 表名 character set 字符集
  1. insert 语句

    1
    insert into 表名 values ();
  2. insert into 表名 values (),()…形式添加多条记录

  3. 如果是给表中的所有字段添加数据,可以不写前面的字段名称

  4. 默认值的使用,当不给某个字段值时,如果有默认值就会添加默认值

  5. update 语句

1
2
update 表名 set 列名 = xxx where 条件;

  • update语法可以用新值更新原有表行中的各列

  • set 子句 指示要修改哪些行,和要给予哪些值。

  • where子句指定更新哪些行,如果没有 where 子句,则更新所有的行。

  • 如果需要修改多个字段,可以通过set字段名 = 值1,字段名 = 值2 where xxx;

  1. delete语句
1
delete from 表名 where xxx;
  • 如果不适用where子句,将删除表中所有数据。

  • delete语句不能删除某一列的值,可以使用update 设为null或者‘’

  • 使用delete语句仅删除记录不删除表本身,如要删除表,使用drop table 语句,drop table 表名。

  1. select语句
1
2
3
4
5
6
7
8
9
10
11
-- 一般格式

SELECT DISTINCT/ALL 目标列表达式 -- 要显示的属性列

FROM 表名/视图名 -- 查询的对象

WHERE 条件表达式 -- 查询条件

GROUP BY 列名 HAVING 条件表达式 -- 查询结果分组

ORDER BY 列名 次序; -- 最终查询结果排序
1
select * (表达式) from 表名 
  • select语句中可以使用as语句

  • select 列名 as别名 from 表名

在where子句中经常使用的运算符

(1)比较运算符

  • <, > ,>=…..

  • between … and..显示在某一区间的值

  • in(set)显示在in列表中的值

  • like ‘ ’,not like‘ ’模糊查询 %表示0到多个任意字符,_表示单个任意字符

  • is null 判断是否为空

(2)逻辑运算符

  1. and 多个条件同时成立

  2. or多个条件任一成立

  3. not 不成立

(3)使用order by子句排序查询结果。

1
select * from 表名 order by 列名
  • order by指定排序的列,排序的列既可以是表中的列名,也可以是select 语句后指定列名(别名)

  • asc(升序)默认,desc降序

  • order by 子句应位于select 语句的结尾。

(4) Group By

  1. GROUP BY子句用于将结果集按照一个或多个列进行分组。它通常和聚合函数(如SUM, COUNT, AVG等)一起使用,以便对每个组应用聚合函数。
1
2
3
SELECT department, COUNT(*) as employee_count
FROM employees
GROUP BY department;

根据department列对employees表进行分组,并计算每个部门的员工数量。

  1. Having

HAVING子句用于过滤分组后的结果。它类似于WHERE子句,但是可以在分组后应用 v条件。

1
2
3
4
SELECT department, COUNT(*) as employee_count
FROM employees
GROUP BY department
HAVING COUNT(*) > 5;

在9的例子中,我们只选择具有超过5名员工的部门。

  1. Top

在SQL中,没有TOP关键字,不过在一些数据库系统中(如SQL Server),可以使用TOP来限制返回的行数。

1
SELECT TOP 5 * FROM products;

这条SQL语句将返回products表中的前5行记录。

字符串数据类型

  1. 字符串的基本使用

char(size) 固定长度字符串最大255个字符

vachar(size) 0 - 65535

可变长度字符串最大65532字节(utf - 8编码最大21844字符,1-3个字节用于记录大小)

  1. 如果数据是定长,使用char,比如密码,邮编,手机号

  2. 如果数据(一个字段)是不确定,使用varchar 比如文章。

  3. 查询速度:char > varchar。

  1. charset(str)返回字符串字符集

  2. concat(str2 […])连接字符串,将多个列拼接成一列

  3. instr(string ,substring)返回substring 在string 中出现的位置(没有返回0)

  4. ucase(string)转换成大写

  5. lcase(string)转换成小写

  6. left(string,length)从string中的左边起取length个字符

  7. right与⑥相反

  8. length(string)string长度(按照字节计算)

  9. replace(str,str1,str2)在str中str2替换成str1

  10. strcmp(string1,string2)逐字符比较两个字符串大小。

  11. substring(str,position,length)从str的position开始取length个字符

  12. ltrim(string)rtrim(string)去除前端或者后端空格 trim (string)去除左右两端空格。

时间日期相关函数

  1. current_date()当前日期

  2. current_time()当前时间

  3. current_timestamp()当前时间戳

  4. date(datetime)返回datetime的日期部分

  5. now()当前时间

数学相关函数

  1. abs(num)绝对值

  2. bin(num)十进制转换成二进制

  3. ceiling(num)向上取整,得到比num大的最小整数。

  4. conv(num,进制,进制)直接转换

  5. floor(num)向下取整,得到比num小的最大整数

  6. format(num1,保留小数位)保留小数位(四舍五入)

  7. least(num1,num2,num3)求最小值

  8. mod(num,num1)求余

  9. rand()/(seed)返回随机值

加密和系统函数

  1. user()查询用户

  2. datebase()查询当前使用的数据库名称

  3. md5(str)为字符串算出一个md5,32的字符串,常用于(用户密码)加密

  4. password()加密函数

流程控制函数

  1. if(exper1,expr2,expr3)如果expr1为True,则返回expr3,否则返回expr3

  2. ifnull (expr1,expr2)如果expr1不为空null,则返回expr1,否则返回expr2.

  3. select case when expr1 then expr2 when expr3 then expr4 else expr5 end(类似多重分支)如果expr1为True,则返回expr2,如果expr3为True返回expr4,否则返回expr5.

分页查询

1
select .... limit start ,rows;

表示start + 1行开始取,取出rows行,start从0开始计算(每页显示记录数)*(第几页 -1),每页显示记录数

连接

自连接

  1. 把同一张表当做两张表使用。

  2. 需要给表取别名,表名 表别名

  3. 列名不明确,可以指定列的别名 列名 as新列名

左外连接

  1. 左侧的表完全显示。
1
select ... from1 left join2 on 条件

右外连接

  1. 右侧的表完全显示。
1
select ...from1 right join2 on 条件

约束

==primary key(主键)==

  1. 用于唯一标识表行的数据,当定义主键约束后,该列的值不能重复。

  2. 细节:

  • primary key 不能重复而且不能为null

  • 一张表最多只能有一个主键,但可以是复合主键。

  • 直接在字段名后指定:字段名 primary key

  • 在表定义最后写primary key(列名)

==not null==

==unique(唯一)==

  1. 定义:当定义了唯一约束后,该列值不能重复

  2. 语法格式:字段名 字段类型 unique

  3. 细节:

    ①如果没有指定not null,则unique字段可以有多个null

    ②一张表可以有多个unique

==foreign key(外键)==

  1. 用于定义主表和从表之间的关系:外键约束要定义在从表上,主表则必须具有主键约束或者是unique约束,当定义外键约束后,要求外键列数据必须在主表的主键列存在或者是为null。
1
foreign key (本表字段名) references 主表名(主键名或者是unique字段名)
  1. 外键指向的表的字段,要求是primary key 或者是 unique

  2. 表的类型是innodb,这样的表才支持外键

  3. 外键的字段的类型要求和主键字段的类型一致(长度可以不同)

  4. 外键的字段值,必须在主键字段中出现过,工作为null(前提是外键字段允许为null)

  5. 一旦建立外键关系,数据不能随意删除

==自增长==

1
字段名 整形 primary key auto_increment;
  1. 一般来说自增长是和primary key 配合使用的

  2. 自增长也可以单独使用(但需要配合一个unique)

  3. 自增长修饰的字段为整数型的

  4. 自增长默认从1开始,可用通过命令修改

1
alter table 表名 auto_increment = 新值;
  1. 如果添加数据是给自增长字段指定值时,以指定值为准。

事务

(1)定义:事务用于保证数据的一致性,它有一组相关的dml语句组成,该组dml语句要么全部成功,要么全部失败。

(2)事务和锁

当执行事务操作,mysql会在表上加锁。防止其他用户修改表的数据

(3)mysql数据库控制台事务的几个重要操作

1
2
3
4
5
6
7
8
9
start transaction #开始一个事务
# sql server 中为begin transaction;
savepoint 保存点名 #设置一个保存点
# sql server 中为save transaction 保存点名
rollback to 保存点名 #回退事务

rollback #回退全部事务

commit #提交事务,所有的操作生效,不能回退

(4)提交事务

使用commit语句可以提交事务,当执行了commit语句后,会确认事务的变化,结束事务,删除保存点,释放锁,数据生效。当使用commit语句后结束无所谓,其他会话将可以查看到事务变化后的新数据。

(5)细节

  1. 如果不开始事务,默认情况下,dml操作是自动提交的,不能回滚。

  2. 如果开始一个事务,但没有创建保存点,可以执行rollback默认回退到开始的状态。

  3. 可以在事务没有提交前,选择回退哪个保存点

  4. innodb存储引擎支持事务,myisam不支持、

  5. 开始一个事务,set autocommit= off;

(6)事务隔离级别

事务的四大特性(ACID)

原子性(Atomicity):事务是不可分割的最小操作单元,要么全部成功,要么全部失败。

一致性(Consistency):事务完成时,必须使所有的数据都保存一致状态。

隔离性(Isolation):数据库系统的隔离机制,保证事务在不受外部并发操作影响的独立环境下运行。

持久性(Durability):事务一旦提交或回滚,它对数据库中的数据的改变是永久的。

image-20240713221639325

并发事务问题

脏读:一个事务读到另外一个事务还没有提交的数据。

image-20240713222814778

事务B读取到事务A还没提交的事务,会产生数据的不一致。

不可重复读:一个事务先后读取同一条记录,但两次读取的数据不同,称为不可重复读。

image-20240713223139289

事务A的第一次读取和第二次读取的数据不一致,因为在两次执行的间隙中事务B执行完成修改了数据。

幻读:一个事务按照条件查询数据时,没有对应的数据行,但是在插入数据时,有发现这行数据已经存在,好像出现了”幻影”。

image-20240713223808245

当事务A查询id=1时没有查询到,因为数据库不存在该数据。此时,事务B插入id=1的数据,并提交事务B;接着事务A也想插入id=1的数据,但是不能插入因为id为主键,不能重复。这时事务A想查询id=1的数据,不能查询因为是可重复读了。需要事务A也提交事务才能查询。

事务隔离级别

image-20240713223944511

1
2
3
4
-- 查询事务隔离级别
select @@transaction_isolation;
-- 设置事务管理级别
set [session|global] transaction isolation level{read uncommitted|read committed|repeatable read| serializable}

serializable:一次只能执行一个事务,当事务A提交后才能执行事务B。

注意:事务隔离级别越高,数据越安全,但是性能越低。

存储引擎

MySQL体系结构

image-20240713231906147

简介

存储引擎就是存储数据,建立索引,更新/查询数据等技术的实现方式。存储引擎是基于表的,而不是基于库的,所以存储引擎也可被称为表类型。存储引擎没有好坏之分,在适当场景下,使用不同的存储引擎,各个存储引擎各有优劣势。

查看所有引擎: show engines

1
2
3
4
create table 表名(
字段1 字段类型,
字段2 字段类型,
)engine = "存储引擎名";

image-20240715215833930

存储引擎特点

image-20240715222459666

InnoDB

介绍:InnoDB是一种兼顾高可靠性和高性能的通用存储引擎,在MySQL5.5之后,InnoDB是默认的MySQL存储引擎。

特点:

  • DML(数据的增删改)操作遵循ACID模型,支持事务;
  • 行级锁,提高并发访问性能;
  • 支持外键foreign key约束,保证数据的完整性和正确性。

文件:

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

image-20240715221021679

逻辑存储结构:

image-20240715221518555

MyISAM

介绍:MyISAM是MySQL早期使用的存储引擎。

特点:

  1. 添加速度快

  2. 不支持外键和事务

  3. 支持表级锁,不支持行锁

文件:

xxx.sdi:存储表结构信息

xxx.MYD:存储数据

xxx.MYI:存储索引

image-20240715221932522

memory

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

特点:

  1. 数据存储在内存中

  2. 执行速度快,没有io读写

  3. 默认支持索引(hash表)

文件:

xxx.sdi:存储表结构信息

存储引擎选择

在选择存储引擎时,应该根据应用系统的特点选择合适的存储引擎。对于复杂的应用系统还可以根据实际情况选择多种存储引擎进行组合。绝大数业务场景使用innoDB存储

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

(1)myisam存储引擎

  1. 添加速度快

  2. 不支持外键和事务

  3. 支持表级锁

(2)Innodb存储引擎:

  1. 支持事务和外键

  2. 支持行级锁

(3)memory存储引擎

  1. 数据存储在内存中

  2. 执行速度快,没有io读写

  3. 默认支持索引(hash表)

(4)如何选择表的存储引擎

  1. 如果应用不需要事务,处理的知识基本的crud操作,那么myisam是不二选择,速度快。

  2. 如果需要支持事务,选择innodb

修改存储引擎

alter table 表名 engine = xxx

索引

索引概述

介绍:索引(index)是帮助MySQL高效获取数据数据结构(有序)。在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用指向数据,这样就可以在这些数据结构实现国际查找算法,这种数据结构就是索引。

演示:

image-20240715230006578

优势:

  • 提高数据检索的效率,降低数据库的IO成本
  • 通过索引列对数据进行排序,降低数据排序的成本,降低CPU的消耗

劣势:

  • 索引列需要占用空间(可以忽略不计)
  • 索引订单提高了查询效率,同时也降低更新表的速度,如对表继续insert,update,delete,效率降低。但是在一个业务系统增删改的操作较少,可以忽略。

索引结构

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

image-20240715230646901

image-20240715230711791

如果没有特别指定,都是指B+树结构组织的索引。

B-Tree(多路平衡查找树)

以一棵最大度数为5的b-tree为例(每个节点最多存储四个key,5个指针):

image-20240715231620389

B+Tree

以一棵最大度数为4的b-tree为例(4阶)的b+tree为例:

image-20240715232457520

image-20240715232745032

相对于B-Tree区别:

  1. 所有的数据都会出现在叶子节点。
  2. 叶子节点形成一个双向链表

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

image-20240715233200803

Hash

哈希索引就是采用一点的Hash算法,将键值换算成新的Hash值,映射到对应的槽位上,然后存储在Hash表中。

如果两个(或多个)键值,映射到一个相同的槽位上,它们就产生了Hash冲突(也称为Hash碰撞),可以通过链表来解决

image-20240715233618480

特点:

  1. Hash索引只能用于对等比较(=,in),不支持范围查询(between,>,<…)
  2. 无法利用索引完成排序操作
  3. 查询效率高,通常只需要一次索引(不产生Hash冲突)就可以了,效率通常要高于B+Tree索引

思考:为什么innoDB存储引擎选择使用B+Tree索引结构?

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

  1. 对于B-Tree,无论是叶子节点还是非叶子节点,都会保存数据,这样导致一页中存储的键值减少,指针跟着减少,要同样保存大量数据,只能增加数的高度,导致性能降低。
  2. 相对Hash索引,B+Tree支持范围匹配及排序操作(双向循环链表)。

索引分类

image-20240716205009598

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

image-20240716205228271

聚集索引选取规则:

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

image-20240716205824487

select * from user where name = 'Arm'的执行流程:

image-20240716210258180

思考:1. 以下SQL语句,哪个执行效率高?为什么?

image-20240716210455378

答:根据id查询的效率更高。根据id查询:直接根据聚集索引查询即可;

根据name查询:需要使用的二级索引查询到id值,再回表查询走聚集索引查询。

  1. InnoDB主键索引的B+Tree高度为多高?

image-20240716211231761

假设:一行数据大小为1K,一页中可以存储16行这样的数据。InnoDB的指针占用6个字节的空间,主键即使为bigint,占用字节数为8。(每页大小为16K)

高度为2:

n * 8 + (n + 1) * 6 = 16 * 1024,得出n约为1170;

1170 * 16 = 18736(条数据)

高度为3:

1171 * 1171 * 16 = 21939856(条数据)

索引语法

创建索引:

1
create [unique|fulltext] index 索引名 on 表名(字段名, ...)

查看索引:

1
show index from 表名;

删除索引:

1
drop index 索引名 on 表名

SQL性能分析

SQL执行频率:

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

1
show global status like 'com______' #七个"_"

image-20240716213533579

慢查询日志:

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

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

1
2
-- 查询慢查询日志
show variables like 'slow_query_log';

image-20240716213954937

配置完毕之后,重启MySQL服务器,查看慢查询日志的记录信息“var/lib/mysql/localhost-slow.log”.

profile详情

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

1
select @@have_profiling;

image-20240716214740478

默认profiling是关闭的,可以同set语句在session/global级别开启profiling;

1
set profiling = 1;

执行一系列的业务SQL的操作,然后通过如下指令查看指令的执行耗时:

image-20240716215245089

image-20240716215307924

image-20240716215408940

explain执行计划

explain或desc命令获取MySQL如何执行select语句的信息,包括select语句执行过程中表如何连接和连接的顺序。

语法:

1
explain/desc select * from 表名 where 条件;

image-20240716215928365

explain执行聚合个字段含义:

  • id:select查询的序列号,表示查询中执行select子句或者是操作表的顺序(id相同,执行顺序从上到下;id不同,值越大,执行越优先)

image-20240716221004060

  • select_type:表示selec的类型,创建的取值有simple(简单表)、primary(主程序,即外层的查询)/union(union中第二个或者后面的查询语句)、subquery(select/where之后包含了子查询)等。
  • **type**:表示连接类型,性能由好到差的连接类型为null,system、const。eq_ref、ref、range、index、all(全表扫描,性能低).
  • **possible_key:**显示可能应用在这张表上的索引,一个或多个。
  • key:实际使用的索引,如果为null,则没有使用索引。
  • **key_len**:表示索引中使用字节数,该值为索引字段最大可能长度,并非实际使用长度,在不损失精确性的前提下,长度越短越好。
  • rows:MySQL认为必须要执行的行数,在InnoDB引擎的表中,是一个预估值,可能并不总是准确。
  • filtered:表示返回结果的行数占需要读取的行数的百分比,filtered的值越大越好。

索引使用原则

最左前缀法则:

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

image-20240717214120473

范围查询:

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

image-20240717214728919

索引列运算

不可在索引列上进行运算操作,索引将失效。

image-20240717215156202

字符串不加引号

字符串类型字段使用时,不加引号,索引将失效。字符和日期类型数据应包含在单引号中image-20240717215511025

模糊查询

如果解决是尾部模糊匹配,索引不会失效,如果是头部模糊匹配,索引失效。

image-20240717215845007

or连接的条件

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

image-20240717220315120

由于age没有索引,所以即使id,Phone有索引,索引也会失效。所以需要针对于age也要建立索引。

数据分布影响

如果MySQL评估使用索引比全表扫描更慢,则不使用索引。

image-20240717220939408

SQL提示

SQL提示,是优化数据库的一个主要手段,简单来说,就是在SQL语句中加入一些人为的提示来达到优化操作的目的。

  • use index:

image-20240717221619570

  • ignore index:

image-20240717221640024

  • force index:

image-20240717221659442

覆盖索引

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

using index condition:查找使用率索引,但是需要回表查询数据;

`using where,using index:查找使用了索引,但是需要的数据都在索引列中能找到,所以不需要回表查询数据。

image-20240717222620764

前缀索引

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

1
create index idx_xxx on table_name(column(n)); -- n表示截取的长度

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

选择性求法:

image-20240719153641145

image-20240719154422047

单列索引与联合索引

单列索引:即一个索引值包含单个字段

联合索引:即一个索引值包含多个字段

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

多条件联合查询时,MySQL优化器会评估哪个字段的索引效率更高,会选择该索引完成本次查询。

联合索引情况:

image-20240719155652065

索引设计原则

  1. 针对于数据量较大,且查询比较频繁的表建立索引(十万级别的数据)。
  2. 针对于常作为查询条件(where),排序(order by)。分组(group by)操作的字段建立索引。
  3. 尽量选择区分度高的列作为索引,尽量建立唯一索引,区分度越高,使用索引的效率越高。
  4. 如果是字符串的字段,字段的长度较长,可以针对于字段的特点,建立前缀索引。
  5. 尽量使用联合索引,减少单列索引,查询是,联合索引很多时候一覆盖索引,节省存储空间,避免回表查询,提高查询效率。需要遵循最左前缀法。
  6. 要控制索引的数量,索引并不是多多益善,索引越多,维护索引结构的代价越大,会影响增删改的效率。
  7. 如果索引列不能存储null值,需要在创建表时使用not null约束他,当优化器知道每列是否包含null值时,它可以更好地确定哪个索引最有效地用于查询。
1
create index 索引名 on 表名(字段名)

(1)索引类型:

  1. 外键索引,主键自动为主索引 (primary key)

  2. 唯一索引(unique)

  3. 普通索引(index)

  4. 全文索引(fulltext)

(2)索引增删改查

  1. 查询索引
1
show index from 表名;
  1. 删除索引
1
drop index 索引名 on 表名
  1. 修改索引

先删除,再添加新的索引

(3)小结

  1. 比较频繁的作为查询条件的字段应创建索引

  2. 更新非常频繁的字段不适合创建索引

  3. 编号出现在where子句中的字段不创建索引

SQL优化

插入数据优化

insert优化

批量插入:一次插入500到1000条比较合适,如果数据大则分多条插入。

1
insert into tb_name value(1, 'tom'),(2,'cat'),(3,'ha');

手动提交事务

image-20240719162204673

主键顺序插入

image-20240719162226764

大批量插入数据

如果一次性需要插入大批量数据,使用insert语句插入性能较低,此时可以使用MySQL数据库提供的load指令进行插入

image-20240719162645535

1
2
3
4
5
6
7
-- 客户端连接服务端是,加上参数 --local-infile
mysql --local-infile -u root -p;
-- 设置全局程序local_infile为1,开启从本地加载文件导入数据的开关
set global local_infile = 1;
-- 执行load指令将准备好的数据,加载到表结构中
load data local infile '/root/sql.log' into table '表名' file terminated by ',' lines terminated by '\n';
-- 解析:字段将以 ','分割,每行数据以 '\n'分隔

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

主键优化

数据组织方式:在InnoDB存储引擎中,表数据都是根据主键顺序组织存放的,这种存储方式的表成为索引组织表(index organized table IOT);

image-20240719165114714

image-20240719165246043

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

image-20240719165507103

image-20240719165737906

主键乱序插入会产生页分裂的情况。

页合并:当删除一行记录是,实际上记录并没有被物理输出,只是记录被标记(flaged)为删除并且它的空间变得允许被其他记录声明使用。当页中删除的记录达到Merge_threshold(合并页的阈值,默认为页的50%),InnoDB会开始寻找最靠近的页(前或后)看看是否可以将两个页合并以优化空间使用。

image-20240719170144485

image-20240719170210059

主键的设计原则

  1. 满足因为需求的情况下,尽量降低主键的长度。
  2. 插入数据时,尽量选择顺序插入,选择使用使用auto_increment自增主键。
  3. 尽量不要使用UUID做主键或是其他自然主键,如身份证号。
  4. 因为操作是,避免对主键的修改。

image-20240719170500308

order by 优化

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

image-20240719171229439

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

image-20240719172318198

image-20240719172710099

image-20240719173149021

总结

  • 根据排序字段建立合适的索引,多字段排序时,页遵循最左前缀否则
  • 尽量使用覆盖索引
  • 多字段排序,一个升序一个降序,此时需要注意联合索引在创建时的规则(asc,desc)
  • 如果不可避免的出现filesort,大数据量排序是,可以适当增大排序缓冲区大小sort_buffer_size(默认256k),如果空间满了,则会在磁盘文件进行排序,性能低。

group by优化

image-20240719180635870

  • 在分组操作是,可以使用索引来提高效率
  • 分组操作是,索引的使用也是要满足最左前缀法则的。

limit 优化

一个常见问题就是limit 20000000,10,此时需要mysql排序前20000010条记录,仅仅返回20000000——20000010的记录,其他记录丢弃,查询排序的代价非常大。

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

如下:

1
select * from tb_sku t, (select id from tb_sku order by id limit 20000010) a where t.id = a.id;

通过覆盖索引的得到的一个仅有id的表,再通过子查询的形式查询。

count 优化

  • MyISAM引擎把一个表的的总行数存在了磁盘上,因此count(*)的时候会直接返回这个数,效率很高。
  • InnoDB引擎在执行count(*)的时候,需要把数据一行一行地从引擎中读取出来,如果累积计数。

优化思路::自己计数,(数据的插入和删除时分别计数),比较繁琐。

count的几种用法:

  • count()是一个聚合函数,对于返回的结果集,一行行地判断,如果count函数的参数不是null,累积值加一,否则不加,最后返回累计值。
  • 用法:count(*),count(主键),count(字段),count(1).

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

count(字段)

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

有not null约束:InnoDB引擎会变量整张表把每一行字段都取出来,返回给服务层,直接进行累加。

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

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

按照效率排序的话,count(字段)<count(主键id)<count(1) =<count(*),索引尽量使用count( * );

update优化

image-20240719185002307

InnoDB的行锁是针对索引加的锁,不是针对记录加的锁,并且该索引不能失效,否则会从行锁升级为表锁。如果name不是索引,则InnoDB引擎会将整张表锁住,其他事务不能对该表的数据进行修改。

视图

介绍

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

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

创建视图

1
create [or replace] view 视图名称(列名列表) as select语句 [with [cascaded|local] check option];

查询创建视图语句

1
show create view 视图名称;

修改

1
2
3
4
-- 方式一
create [or replace] view 视图名称(列名列表) as select语句 [with [cascaded|local] check option];
-- 方式二
alter view 视图名 as select语句;

删除

1
drop view if exists 视图名;

检查选项

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

cascaded:

image-20240719204641752

local:

image-20240719205629100

local只将自身进行捆绑,检查自身数据,不会强制依赖的视图进行检查(若依赖的视图有检查选项就检查,反之不检查)。

视图的更新

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

  • 聚合函数或窗口函数(sum(),min(),max(),count())等;
  • distinct
  • group by
  • having
  • union或者union all

视图的作用

操作简单

视图不仅可以简化用户对数据的理解,也可以简化它们的操作。那些被经常使用的程序可以被定义为视图,从而使得用户不必为以后的操作每次指定全部的条件。

安全

数据库可以授权,但不能授权到数据库特定行和特点的列上。通过视图用户只能查询和修改它们所能见到的数据。

数据独立

视图可帮助用户屏蔽真实表结构变化带来的影响。

  1. 定义:视图是一个虚拟表,其内容头查询定义, 同真实的表一样,视图包含列,其时间来自对应的真实表。

  2. 对视图的总结

    1. 视图是根据基表来创建的,视图是虚拟的表。

    2. 视图也有列,数据来自基表。

    3. 通过视图可以修改基表的数据。

    4. 基表的改变也会影响视图的数据。

  3. 视图的基本使用

    • create view 视图名 as select 语句 from 表名

    • alter view 视图名 as select 语句 from 表名

    • show create view 视图名

    • drop view 视图1,视图2

  4. 注意

视图中可以再 使用视图。

存储过程

介绍

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

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

image-20240719211749307

特点

  • 封装,复用
  • 可以接收参数,也可以返回数据
  • 减少网络交互,效率提升

基本语法

创建

1
2
3
4
5
create procedure 存储过程名称 (参数列表)
begin
-- SQL语句
end
-- 在命令行中,执行创建存储过程的SQL是,需要通过关键字delimiter指定SQL语句的结束符。

调用

1
call 存储过程名 (参数)

查看

1
2
3
4
-- 查询指定数据库的存储过程及状态信息
select * from information_schema.poutines where routine_schema = 'xxx(数据库名称)'
-- 查询某个存储过程的定义
show create procedure 存储过程名称;

删除

1
drop procedure [if exists] 存储过程名称;

变量

系统变量

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

查看系统变量:

1
2
3
show [session|global] variables; -- 查看所有系统变量
show [session | global] variables like 'xxx'-- 通过like模糊匹配方式查找变量
select @@[session | global] 系统变量名 -- 查看指定变量的值

设置系统变量:

1
2
set [session | global]系统变量名 = 值;
set @@[session | global]系统变量名 = 值;

注意:

  • 如果没有指定session/global,默认是session,会话变量。
  • mysql访问重新启动之后,所设置的全局参数会失效,要想不失效,可以在/etc/my.cnf中配置。

自定义变量

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

赋值:

1
2
set @var_name := expr, @var_name := expr;
select 字段名 into @var_name from 表名;

使用:

1
select @var_name;

注意:用户定义的变量无需对其进行声明或初始化,只不过获取到的值为null;

局部变量

局部变量是根据需要定义的在局部生效的变量,访问之前,需要declare声明。可用做存储过程内的局部变量和输入参数,局部变量的范围是在其内声明的begin...end块;

声明:

1
declare 变量名 变量类型[default...]

赋值:

1
2
3
set 变量名 = 值;
set 变量名 := 值;
select 字段名 into 变量名 from 表名;

流程控制语句

if语句

语法:

1
2
3
4
5
6
7
if 条件1 then
……
elseif 条件2 then
...
else
...
end if;

参数

image-20240720135132650

语法:

1
2
3
4
create procedure 存储过程名称([in/out/inout 参数名 参数类型])
begin
-- sql语句
end
1
2
3
4
5
6
7
8
9
10
11
12
create procedure p_score(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;
call p_score(85, @result);
select @result;
1
2
3
4
5
6
7
create procedure p1_score(inout score double)
begin
set score = score * 0.5;
end;
set @score = 180;
call p1_score(@score);
select @score;

case

语法1:

1
2
3
4
5
case case_value
when when_value1 then statement_list1
when when_value2 then statement_list2
else statement_list
end case;

语法2:

1
2
3
4
5
case 
when 查询条件1 then statement_list1
when 查询条件2 then statement_list2
else statement_list
end case;

image-20240720141900327

1
2
3
4
5
6
7
8
9
10
11
12
create procedure judgment_month(in month int, out result varchar(10))
begin
case
when month between 1 and 3 then set result := '第一季度';
when month between 4 and 6 then set result := '第二季度';
when month between 7 and 9 then set result := '第三季度';
when month between 10 and 12 then set result := '第四季度';
end case;
end;

call judgment_month(9, @result);
select @result;

while

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

1
2
3
while 条件 do
SQL逻辑
end while;

累积1到n:

1
2
3
4
5
6
7
8
9
10
11
create procedure get_sum(in n int)
begin
declare total int default 0;
while n >= 1 do
set total = total + n;
set n = n - 1;
end while;
select total;
end;

call get_sum(10);

repeat

repeat 是有条件的循环控制语句,当满足条件的时候退出循环

1
2
3
4
repeat 
SQL逻辑
until 条件
end repeat;
1
2
3
4
5
6
7
8
9
10
11
12
create procedure get_sum1(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 get_sum1(10);

loop

loop实现简单的循环,如果不在SQL逻辑中添加退出循环的条件,可以用其来实现简单的死循环。loop可以配合以下两个语句使用

  • leave:配合循环使用过,退出循环
  • iterate:必须用在循环中,其作用是跳过当前循环剩下的语句,直接进入下一次循环。
1
2
3
[begin_label:] loop
sql 逻辑
end loop [end_lable];
1
2
3
4
5
6
7
8
9
10
11
12
13
14
create procedure get_sum2(in n int)
begin
declare total int default 0;
flag: loop
set total = total + n;
set n = n - 1;
if n = 0 then
leave flag ;
end if;
end loop flag;
select total;
end;

call get_sum2(100);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
-- 累加1-n之间的偶数
drop procedure get_evenNumber;
create procedure get_evenNumber(in n int)
begin
declare total int default 0;
flag: loop
if n % 2 != 0 then
set n = n - 1;
iterate flag ;
end if;
set total = total + n;
set n = n - 1;
if n <= 1 then
leave flag;
end if;
end loop flag;
select total;
end;
call get_evenNumber(100);

游标

游标(cursor)是用来存储存储程序结果集的数据类型,在存储过程和函数中可以使用游标对结果集进行循环的处理。游标的使用包括游标的声明。open,fetch,和close。

声明游标:

1
declare 游标名称 cursor for 查询语句;

打开游标:

1
open 游标名称;

获取游标记录:

1
fetch 游标名称 into 变量[,变量];

关闭游标:

1
close 游标名称;

条件处理程序

条件处理程序可以用来定义在流程控制结构执行过程中遇到问题是相应的处理步骤。

1
2
3
4
5
6
-- 当游标中的数据为空时,关闭游标并退出
declare
exit
handler
for sqlstate '02000'
close cursor名称;

存储函数

存储函数是有返回值的存储过程,存储函数的参数只能是in类型。

image-20240720183336043

计算从1累加到n的值,n为传入的参数值

1
2
3
4
5
6
7
8
9
10
11
12
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(10);

触发器

介绍

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

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

image-20240720184513085

语法

创建

1
2
3
4
5
6
create trigger trigger_name
before/after insert/update/delete
on table_name for each row -- 行级触发器
begin
SQL逻辑;
end

查看

1
show triggers;

删除

1
2
drop trigger [schema_name] trigger_name;
-- 如果没有指定schema_name,默认为当前数据库。

添加数据的触发器:

image-20240720185955911

修改数据的触发器:

image-20240720190506003

删除数据的触发器:

image-20240720190902436

介绍

锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中,除传统的计算资源(cpu,ram,IO)的争用以外,数据也是一种供许多用户共享的资源。然后保证数据并发访问的一致性,有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。

分类

MySQL中的锁,按照锁的粒度分,分为三类:

  • 全局锁:锁定数据库中的所有表。
  • 表级锁:每次操作锁住整张表
  • 行级锁:每次操作锁住对应的行数据。

全局锁

全局锁就是对整个数据库实例加锁,加锁后整个实例处于只读状态,后续的DML的写语句,DDL语句,已经更新操作的事务提交语句都将被阻塞。

其典型的使用场景就是做全库的逻辑备份,对所有的表进行锁定,从而获取一致性视图,保证数据的完整性。

加锁前:

image-20240720213824672

加锁后:

image-20240720214113460

添加全局锁:

1
flush tables with read lock;

备份数据:

1
mysqldump -u root - p123 数据库名>xxx.sql

解除锁:

1
unlock tables;

特点

数据库中加全局锁,是一个比较重的操作,存在以下问题:

  1. 如果在主库上备份,那么在备份期间都不能执行更新,业务基本上就得停摆;
  2. 如果在从库上备份,那么在备份期间从库不能执行主库同步管理的二进制日志(binlog),会导致主从延迟。

在InnoDB引擎中,我们可以在备份时加上参数--sigle-transaction参数来完成不加锁的一致性数据备份。

1
mysqldump --single-transaction -uroot -pxxx 数据库名> xxx.sql;

表级锁

表级锁,每次操作锁住整张表。锁定粒度大,发生锁冲突的概率最高,并发度最低。应用在MyISAM。InnoDB,BDB等存储引擎中。

对于表级锁,主要分为以下三类:

  • 表锁
  • 元数据锁(meta data lock)
  • 意向锁

表锁

  • 表共享读锁(read lock)
  • 表独享锁(write lock)

语法:

加锁:

1
lock tables 表名... read/write

释放锁:

1
unlock tables/客户端断开连接

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

image-20240720223148884

元数据锁(meta data lock,MDL)

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

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

image-20240720224042939

查看元数据锁:

1
select object_type,object_schema,lock_duration from performance_schema.metadata_locks;

意向锁

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

image-20240720230104550

意向共享锁(IS):

  • 由语句select...lock in share mode;添加
  • 与表锁共享锁(read)兼容,与表锁排他锁(write)互斥

意向排他锁(IX):

  • insert,update,delete,select...from update添加
  • 与表锁共享锁(read)及排它锁(write)都互斥。意向锁之间不会互斥。

查看意向锁机行锁的加锁情况

1
select object_type,object_schema,lock_duration from performance_schema.data_locks;

行级锁

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

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

  • 行锁(Record Lock):锁定单个行记录的锁,防止其他事务对此进行update和delete。在RC,RR隔离级别下都支持。

image-20240720232645588

  • 间隙锁(Gap Lock):锁定索引记录间隙(不含该记录),确保索引记录间隙不变,赋值其他事务在这个间隙进行insert,产生幻读。在RR隔离级别下都支持。

image-20240720232624888

  • 临键锁(next-key Lock):行锁和间隙锁组合,同时锁住数据,并锁住数据前面的间隙gap。在RR隔离级别下支持。

image-20240720232557999

行锁

InnoDB实现了两种类型的行锁:

  • 共享锁(S):允许一个事务去读一行,阻止其他事务获得相同的数据集的排它锁。
  • 排它锁(X):允许获取排它锁的事务更新数据,阻止其他事务获得相同数据记得共享锁和排它锁。

image-20240721123206077

image-20240721123800548

默认情况下,InnoDB在repeatable read事务隔离级别运行,InnoDB使用next-key锁进行搜索和索引扫描,以防止幻读。

  1. 针对唯一索引进行检索式,对已存在的记录进行等值匹配时,将会自动优化为行锁。
  2. InnoDB的行锁是针对于索引加的锁,不通过索引条件检索数据,那么InnoDB将对表中的所有记录加锁,此时就会升级为表锁。

查看意向锁机行锁的加锁情况:

1
select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from performance_schema.data_locks;

间隙锁/临键锁

默认情况下,InnoDB在repeatable read事务隔离级别运行,InnoDB使用next-key锁进行搜索和索引扫描,以防止幻读。

  • 索引上的等值查询(唯一索引),给不存在的记录加锁时,优化为间隙锁。
  • 索引上等值查询(普通索引),向右遍历是最后一个值不满足查询需求时,next-key lock退化为间隙锁
  • 索引上的范围查询(唯一索引),会访问到不满足条件的第一个值为止。

注意:间隙锁唯一的目的是防止其他事务插入间隙。间隙锁可以共存,一个事务采用的间隙锁不会阻止另一个事务在同一间隙上采用间隙锁。

InnoDB引擎

逻辑存储结构

架构

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

image-20240721132801035

内存架构

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

缓冲池以Page页为单位,底层采用链表数据结构管理Page.根据状态,将Page分为三种类型:

  • free Page:空闲Page,未被使用。
  • clean Page:被使用Page,数据没有被修改过。
  • dirty Page:脏页,被使用Page,数据被修改过,而且数据域磁盘的数据产生了不一致。

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

Change Buffer的意义是什么?

与聚集索引不同,二级索引通常是非唯一的,并且以相对随机的顺序插入二级索引。同样,删除和更新可能会影响索引树中不相邻的二级索引页,如果每一次都操作磁盘,会造成大量的磁盘IO。有了Change Buffer之后,我们可以在缓冲池中进行合并处理。

Adaptive Hash Index:自适应Hash索引,用于优化对Buffer Pool数据的查询。InnoDB存储引擎会监控对表上各索引页的查询,如果观察到Hash索引可以提升速度,则建立Hash索引,称之为自适应Hash索引。

自适应哈希索引,无需人工干预,是系统根据情况自动完成

参数:adaptive_hash_index

Log Buffer:日志缓冲区,用来保存要写入到磁盘中的log日志数据(redo log,undo log),默认大小为16MB,日志缓冲区的日志会定期刷新到磁盘中。如果需要更新插入或删除许多行的事务,增加日志缓冲区的大小可以节省磁盘IO、

参数:

  • InnoDB_log_buffer_size:缓冲区大小
  • InnoDB_flush_log_at_trx_commit:日志刷新到磁盘时机
  • image-20240721135810690

磁盘结构

image-20240721140147198

image-20240721140602054

image-20240721140731017

后台线程

image-20240721145815284

  1. master Thread

核心后台线程,负责调度其他线程,还负责将缓冲池的数据异步刷新到磁盘中,保存数据的一致性,还包括脏页的刷新,合并插入缓存,undo页的回收。

  1. IO Thread

在InnoDB存储引擎中大量使用了AIO(异步IO)来处理IO请求,这样可以极大地提高了数据库的性能,而IO Thread主要负责这些IO请求的回调。

image-20240721150211592

  1. Purge Thread

主要用于回收事务已经提交了的undo log,在事务提交之后,undo log可能不用了,就用它来回收。

  1. Page Cleaner Thread

协助Master Thread刷新脏页到磁盘的线程,它可以减轻Master Thread的工作压力,减少阻塞。

事务原理

image-20240721150946619

redo log——持久性

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

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

image-20240721152037374

undo log——原子性

回滚日志,用于记录数据被修改前的信息,作用包含两个:提供回滚和MVCC(多版本并发控制)。

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

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

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

undo log+redo log —— 一致性

锁 + MVCC —— 隔离性

MVCC

当前读

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

快照读

简单的select(不加锁)就是快照读,快照读,读取的是记录数据的可见版本,有可能是历史数据,不加锁,是非阻塞读。

  • read Commited:每次select,都删除一个快照读。
  • repeatable read:开启事务后第一个select语句才是快照读的地方。
  • serializable:快照读会退化为当前读。

MVCC

全称Multi-Version Concurrency Control,多版本并发控制。指维护一个数据的多个版本,使得读写操作没有冲突,快照读为MySQL实现MVCC提供了一个非阻塞读功能。MVCC的具体实现,还需要依赖于数据库三个组件 —— 记录中三个隐式字段undo log版本链readView

实现原理

image-20240721165254445

记录中的隐藏字段

image-20240721161749803

undo log

回滚日志,在insert,update,delete的时候产生的便于数据回滚的日志。

当insert的时候,产生的undo log日志只在回滚是需要,在事务提交后,可被逻辑删除。

而update,delete的时候,产生的undo log日志不仅在回滚是需要,在快照读时也需要,不会立即被删除。

undo log版本链

image-20240721162608295

不同事务或相同事务对同一条记录进行修改,会导致该记录的undolog生成一条记录版本链表,链表的头部是最新的就记录,链表尾部是最早的旧记录。

readview

readview(读视图)是快照读SQL执行时MVCC提取数据的依据,记录并维护系统当前活跃的事务(为提交的)id。

readView中包含了四个核心字段:

image-20240721163435603

image-20240721163741850

不同的隔离级别,生成readView的时机不同:

  • read committed:在事务中每一次执行快照读时生成readView。

image-20240721164914830

  • repeatable read:仅在事务中第一次执行快照读生成readView,后续复用该readView。

image-20240721165129909

MySQL管理

系统数据库

MySQL数据库安装后,自带了四个数据库:

image-20240721205717937

常用工具

mysql:

该MySQL表示值MySQL服务,而是指MySQL的客户端工具。

image-20240721205829144

-e选项可以在MySQL客户端执行SQL语句而不用连接到MySQL数据库再执行,对于一些批处理脚本,这种方式尤其方便。

image-20240721205941236

1
mysql  -uroot -p025336 mybook -e"select * from books" 

mysqladmin

mysqladmin是一个执行管理操作的客户端程序。可以用它来检查服务器的配置当前状态,创建并删除数据库等。

image-20240721211227649

mysqlbinlog

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

image-20240721211353574

mysqlshow

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

image-20240721211816835

mysqldump

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

image-20240721212413759

1
2
#备份库的表
mysqldump -u 用户名 -p 密码 数据库 表1,表2 > d://文件名.sql

mysqlimport/source

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

语法:

1
2
3
mysqlimport [option] db_name textfile1 [textfile2...];
-- 示例
mysqlimport -uroot -pxxx xxx/xxx.txt;

如果需要导入sql文件,使用source指令;

1
2
#恢复数据库
source 文件名.sql

(1)创建用户

create user ‘用户名’@ ‘允许登录的位置’ identified by ‘密码’

声明:创建用户的同时指定密码。

(2)删除用户

drop user ‘用户名’ @ ‘允许登录位置’

(3)用户修改密码

  1. 修改自己的密码

set password = password(‘密码’)

  1. 修改他人的密码

set password for ‘用户名’ @ ‘允许登录位置’ = password(‘密码’)

(4)给用户授权

基本语法:grant 权限列表 on 库.对象名 to ‘用户名’ @ ‘登录位置’(identified by ‘密码’)

(5)权限列表,多个权限用逗号隔开

  1. grant select on...

  2. grant seleclt , delete , create on...

  3. grant all on...

(6)特别声明

.:代表本系统中所有数据库的所有对象(表,视图)

库.*:表示某个数据库中的所有对象(表,视图)

(7)identified by 可以省略,可以写出

①如果用户存在,就是修改用户密码

②如果用户不存在,就是创建该用户

(8)回收用户授权

①基本语法: revoke 权限列表 on 库.对象名 from ‘用户名’@ ‘登录位置’

②权限生效指令:如何权限没有生效,执行flush privileges;

(9)细节声明

①创建用户的时候,如果不指定host,则为%,%表示所有ip都有连接权限

②可以指定:create user ‘xxx’@ “192.168.%”表示用户在192.168.的IP可以登录mysql

③在删除用户的时候,如果host表示%,需要明确指定‘用户’@ ‘host值’

日志

错误日志

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

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

1
show variables like "log_error%";

二级制日志

介绍

二进制日志(binlog)记录了所有的DDL(数据定义语言)语句DML(数据操纵语言)语句,但不包含数据查询(select,show)语句。

作用:

  1. 灾难时的数据恢复;
  2. mysql的主从复制。

在mysql8版本中,默认二级制日志是开启着的。

1
show variables like '%log_bin%';

image-20240722170444802

日志格式

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

image-20240722170628385

1
show variables like '%binlog_format%';

日志查看

由于日志是以二进制方式存储的,不能直接读取,需要通过二进制日志查询工具mysqlbinlog来查看.

image-20240722170938266

日志删除

对于比较繁忙的业务系统,每天生成的binlog数据巨大,如果长时间不清除,将会占用大量磁盘空间。

image-20240722171842658

也可以在MySQL的配置文件中配置二进制的过期时间,设置了之后,二进制日志过期会自动删除。

1
show variables like '%binlog_expire_logs_seconds%'

查询日志

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

image-20240722172449994

1
show variables like '%general%'

修改MySQL的配置文件’/etc/my.cnf文件’,添加如下内容:

image-20240722172821669

image-20240722172651956

慢查询日志

慢查询日志记录了所有执行时间超过参数long_query_time设置值并且扫描数不小于min_examined_row_limit的所有的SQL语句的日志,默认未开启。long_query_time默认为10秒,最小为0,精度可以到微秒.

image-20240722173136984

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

image-20240722174026470

主从复制

概述

主从复制时指将主数据库的DDL和DML操作通过二进制日志传到从库服务器中,然后在从库对这些日志重新执行(也叫重做),从而使得从库和主库的数据保存同步。

MySQL支持一台主库同时向多台从库进行复制,从库同时也可以作为其他服务器的主库,实现链状复制。

MySQL复制的优点主要包含以下三个方面:

  1. 主库出现问题,可以快速切换到从库提供服务。
  2. 实现读写分离,降低主库的访问压力,增删改走主库,查询走从库。
  3. 可以在从库中执行备份,以避免备份期间影响主库服务。

image-20240722175122776

原理

MySQL的主从复制原理:

image-20240722175453393

复制过程分成三步:

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

搭建

服务器准备

image-20240722175830743

主库配置

  1. 修改配置文件/etc/my.cnf

image-20240722180012393

  1. 重启MySQL服务器
1
systemctl restart mysqld
  1. 登录MySQL,创建远程连接账号,并授予主从复制权限

image-20240722180305175

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

image-20240722180526783

从库配置

  1. 修改配置文件/etc/my.cnf

image-20240722180637795

  1. 重启MySQL服务器
1
systemctl restart mysqld
  1. 登录MySQL,设置主库配置8.0.23版本
1
change replication source to source_host = 'xxx.xxx.xxx.xx',source_user='xxx',source_password='xxx',source_log_file='xxx',source_log_pos='xxx';

8.0.23版本以前

1
change master  to master_host = 'xxx.xxx.xxx.xx',master_user='xxx',master_password='xxx',master_log_file='xxx',master_log_pos='xxx';

image-20240722181426503

image-20240722181527532

  1. 开启同步操作
1
2
3
4
# 8.0.22之后
start replica;
# 之前
start slave;
  1. 查看主从同步状态
1
2
3
4
# 8.0.22之后
show replica status;
# 之前
show slave status;

image-20240722181849437

测试

分库分表

介绍

问题分析

image-20240722202615153

随着互联网及移动互联网的发展,应用系统的数据量也是成指数式增长,若采用单数据库进行数据存储,存在以下性能瓶颈:

  • IO瓶颈:热点数据太多,数据库缓存不足,产生大量磁盘IO,效率较低。请求数据太多,带宽不够,网络IO瓶颈。
  • CPU瓶颈:排序,分组,连接查询,聚合统计等SQL会耗费大量的CPU资源,请求数太多,CPU出现瓶颈。

分库分表的中心思想是将数据分散存储,使得单一数据库/表的数据量变小来缓解单一数据库的性能问题,从而达到提升数据库性能的目的。

拆分策略

image-20240722203149788

垂直拆分

垂直分库:以表为依据,根据业务将不同表拆分到不同库中。

image-20240722203503125

特点:

  • 每个库的表的结构都不一样。
  • 每个库的数据干不也一样。
  • 所有库的并集是全量数据。

垂直分表:以字段为依据,根据字段属性将不同字段拆分到不同表中。

image-20240722203640724

特点:

  • 每个表的结果都不一样
  • 每个表的数据也不一样,一般通过一列(主键/外键)关联。
  • 所有表的并集是全量数据。

水平拆分

水平分库:以字段为依据,按照一定策略,将一个库的数据拆分到多个库中。

image-20240722203914261

特点:

  • 每个库的表结构都一样
  • 每个库的数据都不一样
  • 所有库的并集是全量数据。

水平分表:以字段为依据,按照一定策略,将一个表的数据拆分到多个表中。

image-20240722204053629

特点:

  • 每个表的表结构都一样

  • 每个表的数据都不一样

  • 所有表的并集是全量数据

实现技术

image-20240722204428625

  • shardingJDBC:基于AOP原理,在应用程序中对本地执行的SQL进行拦截,解析,改写,路由处理。需要自行编码配置实现,只支持java语言,性能较高。
  • MyCat:数据库分库分表中间件,不用调整代码即可实现分库分表,支持多种语言,性能不及前者。

MyCat

介绍

MyCat是开源的,活跃的,基于java语言编写的MySQL数据库中间件。可以向使用MySQL一样来使用MyCat,对于开发人员来说根本感觉不到MyCat的存在。

优势:

  • 性能可靠稳定
  • 强大的技术团队
  • 体系完善
  • 社区活跃

下载

http://mycat.org.cn/

目录结构

image-20240722210711916

概念介绍

image-20240722211113649

入门

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

image-20240722211421055

环境准备

image-20240722211518387

分片配置(schema.xml)

conf目录下

image-20240722211821071

server.xml

配置MyCat的用户级用户的权限信息:

image-20240722215203390

启动服务

切换到MyCat的安装目录,执行指令:

1
2
3
4
# 启动
bin/mycat start
#停止
bin/mycat stop

启动后占用端口号为:8066

启动完毕之后,可以查看logs目录下的启动日志,查看MyCat是否启动完成

image-20240722215711677

连接并登录MyCat

1
mysql -h192.168.xx.xx -p端口号(8066) -uroot -p密码

配置

schema.xml

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

主要包含以下三组标签:

  • scheme标签

image-20240723135237765

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

image-20240723135647862

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

image-20240723135911840

  • dataNode标签

dataNode标签中定义了MyCat中的数据节点,也就是我们通常说的数据分片。一个dataNode标签就是一个独立的数据分片。

image-20240723140146686

  • dataHost标签

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

image-20240723140240383

image-20240723140612273

rule.xml

rule.xml中定义了所有拆分分表的规则,在使用过程中可以灵活的使用分片算法,或者对同一个分片算法使用不同的参数,它让分片过程可配置化。主要包含类标签:tableRule,Function

image-20240723140953784

server.xml

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

system标签

user标签:

image-20240723144857429

分片

垂直拆分

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

image-20240723150058670

准备:

配置:

schema.xml

image-20240723150333645

server.xml

image-20240723150811847

测试:

在MyCat的SQL语句等同于在MySQL的SQL语句。

全局表配置:

对于省市区表,是属于数据字典表,在多个业务模块中都可能会遇到,可以将其设置为全局表,利于业务操作。

schema.xml

image-20240723152521202

水平拆分

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

image-20240723153244116

准备:

image-20240723153322446

配置:

image-20240723153414322

测试:

在MyCat的命令行中,执行如下SQL创建表,并插入数据,查看数据分布情况。

image-20240723154143522

分片规则

范围分片

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

image-20240723155112770

image-20240723155221303

需要自定义时,修改xxx.txt文件的规则即可。

取模分片

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

image-20240723155427167

image-20240723155506700

一致性Hash

一致性哈希:相同的哈希因子计算值总是被划分到相同分区表中,不会业务分区节点的增加而改变原来的数据的分区位置。

image-20240723155813171

image-20240723155831191

枚举分片

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

image-20240723160511128

image-20240723160532249

应用指定分片

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

image-20240723161806903

image-20240723161826554

固定分片Hash算法

该算法类似于十进制的求模运算,但是为二级制的操作,例如,取id的二进制低10位与1111111111进行位&运算。

image-20240723163222803

字符串Hash解析

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

image-20240723165437500

按天日期分片

image-20240723205240119

按自然月分片

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

image-20240723210821970

image-20240723210931094

MyCat管理

MyCat原理:

image-20240723212714520

image-20240723213118786

MyCat默认卡通2个端口,可以在server.xml中进行修改

  • 8066数据访问端口,即进行DML和DDL操作。
  • 9066数据库管理端口,即MyCat服务管理控制功能,用于管理MyCat的整个集群状态。
1
mysql -h 192.168.200.210 -p 9066 -uroot -p123456

image-20240723213430498

MyCat监控

MyCat-eye

MyCat-web(MyCat-eye)是对MyCat-server提供监控服务,功能不局限于对MyCat-server使用。它通过JDBC连接对MyCat,MySQL监控,监控远程服务器(目前仅限于Linux系统的)CPU,内存,网络,磁盘。

MyCat-eye运行过程中需要依赖zookeeper,因此需要先安装zookeeper。

安装

  • zookeeper安装

  • MyCat-eye安装

配置

  • 开启MyCat的实时统计功能(server.xml)
1
2
# 1为开启,0关闭
<property name="userSqlStat">1</property>
  • 在MyCat监控界面配置服务地址

image-20240723220123636

读写分离

介绍

读写分离,简单地说是把对数据库的读和写操作分开,以对应不同的数据库服务器。主数据库提供写操作,从数据库提供读操作,这样能有效地减轻单台数据库的压力。

通过MyCat即可实现上述功能,不仅可以支持MySQL,也可以支持oracle和SQL server。

image-20240723221812247

一主一从读写分离

配置

MyCat控制后台数据库的读写分离和负载均衡有schema.xml文件dataHost标签的balance属性控制。

image-20240724143239899

image-20240724143634451

存在的问题:主节点Master主库宕机之后,业务系统就只能够读,而不能写入数据了,为了高可用,创建双主双从读写分离的数据库。

双主双从

介绍

一个主机Master1用于处理所有写请求,它的从机slave1和另一台主机Master2还有他的从机slave2负责所有读请求。当Master1主机宕机后,Master2主机否则写请求,Master1,Master2互为备机。架构图如下:

image-20240724145549638

准备

我们需要准备5台服务器,具体的服务器及软件安装情况如下:

image-20240724145647478

搭建

主库1配置:

  1. 修改配置文件:/etc/my.cnf

image-20240724150042094

  1. 重启MySQL服务器
1
systemctl restart mysqld

主库2配置:

image-20240724150348274

两台主库创建账号并授权(两台主库操作相同):

image-20240724150519131

从库1配置:

image-20240724150812209

从库2配置:

image-20240724150900634

两台从库配置关联的主库:

image-20240724150944492

两台主库相互复制:

image-20240724151520154

双主双从读写分离

配置

MyCat控制后台数据库的读写分离和负载均衡有schema.xml文件datahost标签的balance属性控制,通过writeType及switchType来完成失败自动切换的。

image-20240724152144066

image-20240724152506951

测试

登录MyCat,测试查询机更新操作,判定是否能够进行读写分离,以及读写分离的策略是否正确。

当主库挂掉一个之后,是否能够自动切换。