MySQL学习笔记 - 进阶部分

MySQL进阶部分

字符集的相关操作:

字符集和比较规则:

utf8与utf8mb4:

utf8字符集表示一个字符需要14字节,但通常常用的字符是13字节且一个字符所用的最大字节长度可能会影响系统的存储和性能。MySQL设计者定义了两个概念:

  • utf8mb3:阉割的utf8字符集,只使用1~3个字节表示字符;
  • utf8mb4:完整的utf8字符集,使用1~4个字节表示字符,相较下额外的包含4字节的emoji表情等;

MySQL中,utf8mb3是utf8的别名。

在这里插入图片描述

比较规则:

上图中,可以看出MySQL中支持41中字符集,其中的default_collation列表示这种字符集中一种默认的比较规则。

后缀表示该比较规则是否区分语言中的重音、大小写。具体如下表:

后缀英文释义描述
_aiaccent insensitive不区分重音
_asaccent sensitive区分重音
_cicase insensitive不区分大小写
_cscase sensitive区分大小写
_binbinary以二进制方式比较

常见的字符集和对应的Maxlen:

字符集名称Maxlen
ascii1
latin11
gbk23122
gbk2
utf8(别称utf8mb3)3
utf8mb44

Centos7中linux下配置字符集:

在这里插入图片描述

各个级别的字符集:

  • 服务器级别character_set_server:当服务器启动时,会读取配置文件/etc/my.cnf中的这两个系统变量的值;
# 可以通过修改配置文件/etc/my.cnf
[server]
character_set_server=gbk         # 默认字符集
collation_server=gbk_chinese_ci  # 对应的字符集的比较规则

# 查看服务器的字符集和比较规则
show variables like '*_server';
  • 数据库级别character_set_database:
# 创建库时,指定字符集和比较规则
create database 库名 [[default]character set 字符集名称][collate 比较规则];

# 修改库的字符集和比较规则
alter database 库名 [[default]character set 字符集名称][collate 比较规则];
# 比如character set gbk2312 collate gbk2312_chinese_ci

# 查看数据库的字符集和比较规则
show variables like '*_database';
# 查看具体的数据库的字符集和比较规则
show create database 库名;
  • 表级别:
# 创建表时,指定字符集和比较规则
create table 表名(
	列信息
)[[default] character set 字符集名称][collate 比较规则];

# 修改表的字符集和比较规则
create table 表名 [[default] character set 字符集名称][collate 比较规则];
# 比如character set utf8 collate utf8_general_ci

# 查看具体的表的字符集和比较规则
show create table 表名;
  • 列级别:
# 创建表时,指定字符集和比较规则
create table 表名(
	列名 类型 [character set 字符集名称][collate 比较规则]
);

# 修改表的字符集和比较规则
alter table 表名 modify 列名 类型 [character set 字符集名称][collate 比较规则];
# 比如character set utf8 collate utf8_general_ci

注意:

  1. 这四个级别的字符集和比较规则是从上到下相互继承的关系,即如果**本级没有指定使用的字符集和比较规则,则使用紧接上一级的**,比如创建表/修改表时没有指定字符集和比较规则,则将使用所在数据库的字符集和比较规则。
  2. 对中、英文来说:utf8_general_ci校对快但精度差、utf_unicode_ci准确度高但校对慢;对于其他语言德语、法语、俄语等来说,一定要使用utf8_unicode_ci;
  3. 修改数据库的默认字符集和比较规则后,原有的已经创建的库和表的字符集和比较规则不会改变,如有需要,则要单独修改。

执行show variables like '%character%'语句:

在这里插入图片描述

  • character_set_server:服务级别的字符集
  • character_set_database:当前数据库的字符集
  • character_set_client:服务器解码请求时使用的字符集
  • character_set_connection:服务器处理请求时,会把请求字符串从character_set_client转为character_set_connection
  • character_set_results:服务器向客户端返回数据时使用的字符集

请求到响应过程中字符集的变化:

在这里插入图片描述

set names 字符集名;

# 等价于下列三条:
set character_set_client = 字符集名; 
set character_set_connection = 字符集名; 
set character_set_results = 字符集名; 

# 或者直接在配置文件/etc/my.cnf中设置,即在启动客户端时就将三个系统变量设置为一样的
[client]
default-character-set=utf8   # 启动选项

MySQL的数据目录:

MySQL在linux中的主要目录结构:

在这里插入图片描述

数据库文件存放的路径:/var/lib/mysql/

MySQL服务器程序在启动时会到文件系统的某个目录下加载一些文件,之后在运行过程中产生的数据也会存储在该目录下的某个文件中,该目录称为数据目录datadir=/var/lib/mysql/

在这里插入图片描述

相关命令目录:

相关命令目录:/usr/bin/usr/sbin。bin目录中存放着许多关于控制客户端程序和服务端程序的命令,即许多可执行文件。

在这里插入图片描述

在这里插入图片描述

配置文件目录:

配置文件目录:/usr/share/mysql(命令及配置文件) 和 /etc/my.cnf

在这里插入图片描述

数据库和文件系统的关系:

像InnoDB、MyISAM这样的存储引擎,将表存储在磁盘上的,而操作系统则通过文件系统来管理磁盘结构。

查看默认数据库:

在这里插入图片描述

  • msql:MySQL系统自带的核心数据库,存储MySQL的用户账户和权限信息,一些存储过程、事件的定义信息,一些运行过程中产生的日志信息,一些帮助信息以及时区信息等。
  • information_schema:保存着MySQL服务器维护的所有其他数据库的信息,比如哪些表、视图、触发器、列、索引等。这些信息只是一些描述性的信息,有时称为元数据。其中提供的一些以innodb_sys开头的表,用于表示内部系统表。

在这里插入图片描述

  • performance_schema:主要用来表示MySQL运行过程中的一些状态信息,用来监控MySQL服务的各类性能指标,包括统计最近执行了哪些语句、执行过程中每个阶段花费了多长时间、内存使用情况等信息。
  • sys:该数据库,主要用来将information_schemaperformance_schema结合起来,帮助系统管理员和开发人员监控MySQL的技术性能。

数据库在文件系统中的表示:

MySQL8.0之后,将不再有***.opt文件,***.frm文件会被整合到***.ibd文件中

在这里插入图片描述

表在文件系统中的表示:

InnoDB存储引擎模式:

1、数据是以记录的形式插入表中的,每个表含有的信息包括:

  1. 表的结构定义,即***.frm文件:有该表的名称、列(多少、类型)、约束条件和索引、使用的字符集和比较规则等各种信息;
  2. 表中的数据,即***.ibd文件;
  3. MySQL8.0之后,***.frm会被整合到***.ibd文件中

2、表中的数据和索引:

  • InnoDB是以为基本单位来管理存储空间的,默认大小是16KB
  • 对于InnoDB来说,每个索引都对应着一棵B+树,该B+树的每个节点都是一个数据页,数据页之间通过双向链表来维护着这些页的顺序。
  • InnoDB的聚簇索引的叶子节点存储了完整的用户记录,也就是所谓的索引即数据、数据即索引。

3、为了更好的管理这些页,InnoDB提出了一个表空间或者文件空间的概念。表空间是一个抽象的概念,可以对应文件系统中的一个/多个真实文件(不同表空间对应的文件数可能不同)。每个表空间可以被划分为很多页。表数据就存放在某个表空间下的某些页里。

表空间的类型:

  • 系统表空间system tablespace:默认情况下,InnoDB会在数据目录datadir=/var/lib/mysql下,创建一个名为ibdata1的大小为12M(该文件是子扩展文件,当大小不够用时,自动扩展文件大小)的文件,即系统表空间在文件系统上的表示。
# 如果想让系统表空间在文件系统中对应多个实际文件,则需要修改配置文件my.cnf
[server]
innodb_data_file_path=data1:512M;data2:128M;autoextend
# 这样MySQL启动后会创建两个大小为512M的data1和data2文件;autoextend表示这两个文件不够用时,系统会自动扩展data2文件的大小

注意:一个MySQL服务器中,系统表空间只有一份mysql5.5~5.6的各个版本中,表中的数据都会被默认存储在这个系统表空间。

  • 独立表空间file-per-table tablespacemysql5.6.6之后,InnoDB并不会默认将各个表的数据存储在系统表空间中,而会为每个表建一个独立表空间,其在文件系统中对应的实际文件是***.ibd
# 如果想指定村使用系统表空间还是独立表空间,可以修改配置文件/etc/my.cnf
[server]
innodb_file_per_table=0  
# 0:表示使用系统表空间、1:表示使用独立表空间
# 修改后,只能对新建的表有效
  • 通用表空间general tablespace
  • 临时表空间temporary tablespace

MyISAM存储引擎模式:

MyISAM存储引擎中,数据和索引分别保存在.MYD.MYI文件中,表结构存放在.frm文件中。

MySQL8.0之后,***.frm、***.MYD、***.MYI文件被整合到了.sdi文件中

在这里插入图片描述

视图在文件系统中的表示:

视图本质是虚拟表,故数据目录下的数据库文件中只会含有其结构文件即***.frm,并不会有其数据文件。

其他文件

数据目录datadir=/var/lib/mysql下,还包含了为了更好运行程序的一些额外的文件:

  • 服务器进程文件:每启动一个MySQL服务器程序,意味着启动一个进程,MySQL会将自己的进程id写入到一个文件中。
  • 服务器日志文件:服务器在运行时会产生各种日志,如查询日志、错误日志、二进制日志、redo日志、undo日志等。
  • 默认/自动生成的SSLRSA正数和密钥文件:主要是为了客户端与服务器安全通信而创建的一些文件。

总结

在这里插入图片描述

用户与权限管理:

用户管理:

MySQL用户可以分为普通用户(只拥有被授予的各种权限)和root用户(超级管理员,拥有所有权限,包括创建用户、删除用户、修改用户的密码等管理权限)。

MySQL提供许多语句用来管理用户账号,包括登录、退出MySQL服务器、创建用户、删除用户、密码管理和权限管理等内容。

MySQL数据库的安全性,需要通过账户管理来保证。

创建用户:

MySQL数据库中,推荐使用create user语句创建新用户,使用此语句时,必须拥有creat user的权限,每添加一个用户,MySQL.user表中会添加一条新纪录,但新创建的用户没有任何权限。

create user 用户名 [identified by '密码'];       # 用户名:host,user,默认host是%

# 添加的用户会体现在MySQL.user表中,表中的host和user字段是联合主键
#,即host和user任一个不同,即可添加成功

修改用户:

update mysql.user set user='new_name'  where user='old_name';
flush privileges;

删除用户:

drop user 'user_name'@'host_name';
# 使用drop命令会删除用户以及对应的权限,执行命令后mysql.user和mysql.db表中的相应的数据都会删除

# 不推荐使用,删除后会有部分记录保留
delete from mysql.user where host='host_name' and user='user_name';
flush privileges;

修改当前用户的密码:

适用于root用户修改自己的密码,以及普通用户登陆后修改密码。

alter user user() identified by 'new_password';

set password = 'new_password';

修改其他用户的密码:

alter user 'user_name'@'host_name' identified by 'new_password';

set password for 'user_name'@'host_name' = 'new_password';

权限管理:

show privileges;
// 可以用来查看数据库的权限

MySQL的权限分布:

权限分布可能设置的权限
表权限select、insert、update、delete、create、drop、grant、references、index、alter
列权限select、insert、update、references
过程权限execute、alter routine、grant

授权的原则:

  1. 只授予满足需要的最小权限
  2. 限制用户的登录主机,一般通过限制成指定IP或内网IP段
  3. 为每个用户设置满足密码复杂度的密码;
  4. 定期清理不需要的用户,收回权限或者删除用户;

授权权限:

给用户授权的两种方式:

1、通过把角色授予用户给用户权限

2、直接给用户权限

授予权限的命令:
grant 权限1,...,权限n on 数据库名称.表名称 to 用户名@用户地址 [identified by '密码'];

// 只有root用户才能给其他用户赋予不同的权限:(因其含有"with grant option")
// 给该用户的所有数据库和所有表赋予全部权限,但“不包括grant权限”
grant all privileges on *.* to 用户名@用户地址 [identified by '密码'];  

注意:如果需要赋予包括grant权限,则需要添加参数"with grant option"这个选项即可,表示该用户可以将自己拥有的权限授予别人。grant重复给用户添加权限,“权限会叠加”,如先后分别赋予insert和select权限则最后该用户就同时拥有select和insert权限。

查看权限:
show grants;      // 查看当前用户的权限
show grants for 'user'@'主机地址';    // 查看某个用户的权限
收回权限:

收回用户不必要的权限,可以在一定程度上保证系统的安全性。

revoke 权限1,...,权限n on 数据库名称.表名称 from 用户名@用户地址;

// 收回所有权限:
revoke all privileges on *.* from 用户名@用户地址;

使用revoke收回权限之后,用户账户的记录将从db、host、tables_priv、columns_priv表中删除,但用户账户记录仍然保存在user表中,删除user表中的账户记录需要使用drop user语句

注:在将用户账户从user表删除之前,应该收回相应用户的所有权限。

总结:

建议尽量使用数据库自己的角色和用户机制(即使用具体的用户名来登录和访问)来控制访问权限,不要轻易使用root账号(权限太大)。

权限表:

MySQL服务器通过权限表来控制用户对数据库的访问,权限表存放在mysql数据库中,如下图:

在这里插入图片描述

,MySQL数据库会根据这些权限表中的内容来给每个用户赋予权限相应的权限。

权限表(mysql数据库):user表、db表、table_priv表、columns_priv表、proc_priv表等,最重要的是前两个表。在MySQL启动时,服务器将这些数据库表中的权限信息的内容读入内存中。

访问控制:

MySQL的访问控制阶段分为,连接核实阶段、请求核实阶段。

**连接核实阶段:**客户端在连接请求时提供的用户名、主机地址、密码,MySQL服务器接收到用户请求后,会使用user表中的host、user、authentication_string这三个字段匹配客户端提供的信息。

请求核实阶段:

在这里插入图片描述

角色管理:

角色是MySQL8.0引入的新功能,是权限的集合,并且可以为角色添加和移除权限。用户可以被授予角色,也可以被授予角色包含的权限。引入角色的目的是,方便管理拥有相同权限的用户

在这里插入图片描述

对角色的操作需要较高的权限,并像用户账户一样,角色可以拥有授予和撤销的权限。

创建/删除角色、赋予/回收角色的权限、查看角色的权限:

create role 'role_name'@'hostname';   # 创建角色
drop role 'role_name'@'hostname';     # 删除角色

grant privileges_ on db.table_name to 'role_name'@'hostname';   # 赋予角色权限
grant all privileges on *.* to 'boss'@'%';   # 赋予所有权限给boss角色

revoke privileges_ on db.table_name from 'role_name'@'hostname';   # 回收角色权限

show grants for 'role_name'@'hostname';   # 看看角色的权限

给用户赋予角色、激活角色、撤销用户角色:

角色创建并授权后,要赋给用户并处于激活状态才能发挥作用。

grant role to 'role_name'@'hostname';   # 给用户赋予角色

# 使用'role_name'@'hostname'用户登录,并查询当前的角色
select current_role();   # 如果显示为none,则表示角色未激活
# MySQL中创建角色后,默认是没有激活的状态,即不能使用,必须手动激活且重新登陆后,该用户才能拥有角色对应的权限

# 激活角色
set default role all to 'user_name'@'hostname';  # 方式一
# 方式二:将active_all_roles_on_login设置为on
show variables like 'active_all_roles_on_login';   # 查看该全局变量是on/off
set global active_all_roles_on_login=on;   # 即对所有角色永久激活

# 撤销用户的角色
revoke role from 'user_name'@'hostname';

小结:

在这里插入图片描述

逻辑架构:

MySQL是典型的C/S架构,即Client/Server架构,服务器端程序使用mysqld。

MySQL server结构,可分为三层:连接层、服务层、引擎层。

在这里插入图片描述

逻辑架构剖析:

服务器处理客户端请求:

无论客户端进程和服务端进程如何通信,最后实现的效果是:客户端进程向服务器进程发送一段文本(SQL语句),服务器进程处理后再向客户端进程发送一段文本(处理结果)。

在这里插入图片描述

具体设计的组件:

在这里插入图片描述

connectors:

MySQL首先是一个网络程序,即需要在TCP之上定义自己的应用层协议。 编写代码与MySQL server建立TCP连接,之后按照其定义好的协议进行交互。

通过SDK来访问MySQL,本质上就是在TCP连接上通过MySQL协议跟MySQL进行交互

第①层 连接层:

系统(客户端)访问MySQL服务器前,第一件事就是建立TCP连接

经过三次握手建立连接成功后,MySQL服务器会对TCP传输过来的账号、密码进行身份验证。一旦用户名、密码验证通过,会从权限表中查出账号拥有的权限与连接关联,之后的权限判断逻辑都将依赖于此时读到的权限。

MySQL可以与多个系统建立连接,且每个系统建立的连接不止一个。为解决TCP无限创建与频繁创建销毁带来的资源损耗、性能下降等问题。MySQL服务器中专门的TCP连接池限制连接数,采用长连接模式复用TCP连接

在这里插入图片描述

TCP连接收到请求后,必须要分配给一个线程专门与这个客户端交互。每个连接都只会从线程池中获取线程,从而省去了创建和销毁线程的开销。

第②层 服务层:

服务层提供核心的功能,如SQL接口、缓存查询、SQL分析和优化、内置函数的执行等。所有跨存储引擎的功能也在这一层实现,如过程、函数等。

该层会解析查询并建立相应的内部解析树,并完成相应的优化,如确定查询表的顺序、是否利用索引、最后生成一个执行计划。执行计划,表明应该使用哪些索引进行查询(全表检索、索引检索),表之间的连接顺序如何,最后会按照执行计划中的步骤调用存储引擎提供的方法来真正的执行查询,并将查询结果返回给用户。

使用 “选取-投影-连接” 策略进行查询:

select id,name from student where gender = '女';
# 通过where语句选取,之后通过属性投影,最终将两个查询条件连接起来生成最终查询结果

MySQL 内部维持了一些 Cache 和 Buffer,如查询缓存 Query Cache 用来缓存一条 select 语句的执行结果,如果能在其中找到查询结果则不用再执行分析、优化、执行等操作,直接将结果反馈给客户端。这种查询机制,是通过一系列小缓存组成的,如表缓存、记录缓存、key缓存、权限缓存等,还可以在不同客户端之间共享。如果缓存空间足够大,这样在大量读操作的环境中能够很好的提升系统的性能。但这种查询缓存的命中率很低

第③层引擎层:

插件式的存储引擎 Storage Engines,真正负责了MySQL中数据的存储和提取,对物理服务器级别维护的底层数据执行操作

服务器通过API与存储引擎进行通信,不同的存储引擎具有的功能不同。

存储层:

所有数据、数据表、数据库的定义,表的每一行内容、索引等都存储在文件系统上,以文件的形式存在MySQL的数据目录中,并完成于存储引擎的交互

小结:

在这里插入图片描述

  1. 连接层:客户端与服务器端建立连接,并通过客户端发送SQL至服务端;
  2. 服务器/SQL层:对SQL语句进行查询处理;
  3. 存储引擎层:与数据库文件打交道,负责数据的存储和读取;

MySQL中SQL的执行流程:

在这里插入图片描述

  • 查询缓存:之前执行过的语句及其结果可能会以key-value对的形式,被直接缓存在内存中。Server如果在查询缓存中,发现了这条SQL语句,就会直接将结果返回给客户端;没有则进入解析器阶段。

    需要说明,查询缓存命中率低,MySQL 8.0以弃用该功能。

    ① 只有相同的查询操作才能命中查询缓存,MySQL中查询缓存命中率不高。

    ② 如果查询请求中含有某些系统函数、用户自定义变量和函数、一些系统表(如mysql、information_schema、performance_schema中的表),那么该请求就不会被缓存。

    ③ 缓存失效:当MySQL的缓存系统检测出到涉及到的表的结构或者数据被修改时,如果缓存中对该表使用了 insert、delete、update、truncate table、alter table、drop table、drop database 等语句,则该表的所有高速缓存都会被删除。

在这里插入图片描述

一般情况下,建议在静态表(即极少更新的表)中使用查询缓存。MySQL提供了这种 “按需使用” 的方式,可以将my.cnf中参数query_cache_type=demand,代表当SQL语句中有 SQL_CACHE关键词时才缓存。

# query_cache_type有三个值,0:关闭查询缓存、1:表示开启、2:表示demand
query_cache_type=2;
show global variables like "%query_cache_type%"; 

# 将查询和查询结果,以键值对key-value的形式,存放在查询缓存中
select SQL_CACHE * from student where ID=5;
select SQL_no_CACHE * from student where ID=5;

# 监控查询缓存的命中率
show status like "%Qcache%";
  • 解析器:对MySQL语句,通过词法分析和语法分析,生成语法树

在这里插入图片描述

词法分析过程:

在这里插入图片描述

  • 优化器:确定SQL语句的执行路径,比如是全表检索,还是索引检索。优化器的作用,就是找到其中最好的执行计划

    比如,表中有多个索引时,决定使用哪个索引;一个与语句有多表关联join时,决定各个表的连接顺序;表达式简化、子查询转为连接、外连接转为内连接等。

在这里插入图片描述

查询优化中,可分为逻辑查询优化物理查询优化

  1. 逻辑查询优化:通过对SQL语句进行等价变换,即重写查询,来使得SQL查询更高效;同时为物理查询优化提供更多的候选执行计划。
  2. 物理查询优化:该阶段,对于单表、多表连接的操作,需要高效地使用索引,提高查询效率。
  • 执行器:产生的执行计划后,会进入执行器阶段。

    执行前需要判断用户是否有权限:

    • 没有权限,则返回权限错误;
    • 具有权限,则进行解析、优化的步骤(MySQL8.0以下的版本,如果设置了查询缓存,则会将查询结果进行以key-value地形式进行缓存。),之后执行器就会根据表中的引擎定义,调用该存储引擎提供的API对表进行读写。

在这里插入图片描述

总结:SQL语句在MySQL中的执行流程就是:SQL语句->查询缓存->解析器->优化器->执行器。

在这里插入图片描述

mysql5.7中,相同两条语句的执行过程分析:

在这里插入图片描述

存储引擎:

存储引擎之前被称为表处理器,后改名为存储引擎,功能:接受上层传下来的指令后对表中的数据进行提取或写入操作。存储引擎就是指表的类型。

查看存储引擎:

在这里插入图片描述

查看/修改默认的存储引擎

# 查看默认的存储引擎
select @@default_storage_engine;
# 查看表使用的存储引擎
show create tables tableName;
# 修改表的存储引擎
alter table tableName engine=InnoDB;

# 直接修改存储引擎
set default_storage_engine = InnoDB;

# 修改my.cnf文件
default-storage-engine=InnoDB;
# 重启服务
systemctl restart mysqld.service

在这里插入图片描述

引擎的对比:

InnoDB引擎:具备外键支持功能的事务存储引擎
  • 支持事务:被设计用来处理大量的短期short-lived事务,可确保事务的完整提交commit和回滚rollback。
  • 除了增加和查询外,还需要更新和删除操作,那么应优先选择InnoDB存储引擎。
  • 数据文件的结构:表名.frm:存储表结构、表名.ibd:存储数据和索引
  • InnoDB是为处理巨大数据量的最大性能设计的
  • 对比MyISAM和InnoDB存储引擎,InnoDB的缺点:
    1. InnoDB写的处理效率差一些,并且会占用更多的磁盘空间用来保存数据和索引。
    2. MyISAM只缓存索引,不缓存真实的数据;InnoDB不仅要缓存索引还要缓存真实的数据,对内存要求较高,且内存对性能有决定性的影响。
MyISAM引擎:主要的非事务处理存储引擎
  • 提供了大量的特性,包括全文检索、压缩、空间函数等。
  • 不支持事务、行级锁、外键等,崩溃后无法安全恢复
  • 优势:访问速度快、对事务完整性没有要求,以select、insert为主。
  • 针对数据统计有额外的常数存储,故count(*)的查询效率高
  • 数据文件结构:表名.frm:存储表结构、表名.myd:存储数据、表名.myi:存储索引
  • 应用场景:只读应用或者以读为主的业务。
InnoDB与MyISAM的区别:
  • InnoDB存储引擎,优点:提供了良好的事务管理、崩溃修复能力和并发控制。因为InnoDB支持事务,故对事务完整性有要求的场合需要选择InnoDB。缺点:其读写效率相等,占用的数据空间相对较大
  • MyISAM存储引擎:小型应用,以读操作和插入操作为主,只有很少的更新、删除操作,并且对事务没有要求的业务可选择该存储引擎。优势:占用空间小、处理速度快;缺点:不支持事务的完整性和并发性
  • 这两个存储引擎各有特点,可有针对性的选择:
对比项InnoDBMyISAM
外键×
事务×
锁机制行锁,操作时只锁某一行,适合高并发的操作表锁,即使操作一条记录也会锁住整个表,不适合高并发的操作
缓存缓存索引和真实数据(表名.ibd文件中),对内存要求较高,而且内存大小对性能有决定性影响只缓存索引(表名.myi文件中),不缓存数据(表名.myd文件中)
关注点事务:并发写、崩溃修复能力、更大资源性能:节省资源、消耗少、简单业务
自带系统表使用×
Memory:置于内存的表

所有数据保存在内存中(不需要进行磁盘I/O操作),响应速快,但当mysqld守护进程崩溃时数据会丢失。但memory表的结构(.frm表结构文件存储在磁盘中)重启后还会保留,只是数据会丢失。另外,要求存储的数据是数据长度不变的格式。

主要特征:
  • memory同时支持哈希hash索引和B+树索引默认使用哈希hash索引,其速度要比使用B型树索引快。

  • memory表至少比myISAM表快一个数量级

  • 数据文件和索引文件是分开存储的:

    每个基于memory存储引擎的表实际上对应一个磁盘文件,该文件的文件名和表名相同为frm类型,只存储表的结构数据文件存储在内存中

  • memory表的大小是受限制的。主要由max_rows和max_heap_table_size控制。max_rows是在创建表时指定,max_heap_table_size大小是默认的16MB,需要时可进行扩大。

使用memory的场景:
  • 目标数据比较少,而且非常频繁的进行访问,在内存中存放数据,如果太大的数据会造成内存溢出。可通过max_heap_table_size控制memory表的大小。
  • 如果数据是临时的,且必须立即使用,则可放在内存中。
  • 由于断电会丢失的影响,只能将这些表作为临时表或缓存来用。
CSV引擎:数据以逗号分隔
  • CSV引擎可将,普通的CSV文件作为MySQL的表来处理,但不支持索引。
  • CSV引擎可作为一种数据交换的机制。对数据的快速导入、导出有明显的优势。
  • CSV存储的数据,可直接在操作系统里文本编辑器或excel读取。

创建CSV表时,服务器会创建一个纯文本数据文件,其名称为表名.csv。当你将数据存储到表中时,存储引擎将其以逗号分隔的格式保存到数据文件中。

create table test_csv(i int not null, c char(10) not null) engine=csv;
# 其中,CSV存储引擎使用时,所有字段必须在创建表时加上“非空约束”

InnoDB和MyISAM、MEMORY三种存储引擎的选择:

  • InnoDB:是一种MySQL的默认存储引擎,支持事务和外键。适用于,对事物的完整性有较高要求、并发条件下要求数据的一致性、除了查询和插入外对删除和更新操作也有要求,这些场景下InnoDB是一个不错的选择。
  • MyISAM:适用于,以读操作和插入操作为主只有少量的更新和删除操作、对事物的完整性、并发要求不高,这些场景下MyISAM是一个比较合适。
  • MEMORY:将所有数据保存在内存中,访问速度快,通常用作临时表和缓存。MEMORY的缺点:对表的大小有限制(太大的表无法缓存到内存中),而且无法保障数据的安全性(断电易丢失)。

数据库缓冲池:

InnoDB存储引擎以页为单位来管理存储空间的,我们进行的增删改查操作本质上就是在访问页面(包括读页面、写页面、创建新页面等操作)。DBMS会申请占用内存来作为数据缓冲池,在真正访问页面之前,会将磁盘上的页缓存到内存中的buffer pool之后才可访问。这样做可以极大的减少磁盘I/O操作,从而极大地提高mysql的性能。

缓冲池 vs 查询缓存:

缓冲池:

本质上是InnoDB向操作系统申请的一块连续内存空间

在这里插入图片描述

缓冲池的重要性:

对于InnoDB作为存储引擎的表来说,无论是用于存储用户数据的索引(聚集索引和二级索引),还是各种系统信息,都是以的形式存放在表空间中。而表空间是InnoDB对文件系统中的一个/几个实际文件的抽象,故数据最终还是存储在磁盘上的。

InnoDB存储引擎在处理客户端请求时,当需要访问某个页的数据时,就会把完整的页全部加载到内存中(即使只访问页中的一条数据),之后对其进行读写访问,且读写访问后并不会立即释放其占用的内存空间,而是将其缓存起来,当再次访问该页面时,就减少了磁盘I/O的开销

缓存原则:

“位置 * 频次” 这个原则,可以帮我们对I/O访问效率进行优化。

  • 位置决定效率,提供缓冲池就是为了在内存中可直接访问数据
  • 频次决定优先级顺序。因缓冲池的大小有限(在内存中),需要根据优先级,优先对使用频次高的热数据进行加载
缓冲池的预读特性:

读取数据时存在一个 “局部性原理”:当使用一些数据时,大概率还会使用其周围的一些数据,因此采用 “预读” 的机制提前加载,可减少未来可能的磁盘I/O开销

查询缓存:

查询缓存是提前将查询结果进行缓存,这样下次不需要执行即可直接拿到结果。

但命中条件极其苛刻,只要数据表发生变化,查询缓存就会失效。

总结:

缓冲池服务于数据库整体的I/O操作查询缓存是提前将查询结果进行缓存,但共同点是通过缓存的机制提升效率

缓冲池如何读取数据?

缓冲池管理器,会尽量将经常使用的数据保存起来,在数据库进行页面的读操作时,首先会判断页面是否在缓冲池中,存在则直接读取,不存在则会通过内存或磁盘将页面存放到缓冲池中再进行读取。

缓冲池在数据库中的结构和作用:

在这里插入图片描述

如果执行sql语句时,更新了缓冲池中的数据,那么会马上同步到磁盘上吗?

在这里插入图片描述

  • 当对数据库中的记录进行修改时,首先会修改缓冲池中页里面的记录信息,然后数据库会以一定的频率刷新到磁盘上(每次发生更新完后,并不会立即进行磁盘回写)。

    存在的问题:当buffer pool中修改了数据,但没来得及回写到磁盘,mysql服务挂掉了,即更新的数据只存在在buffer pool中。这就需要Redo Log & Undo Log

  • 缓冲池采用一种**checkpoint的机制**,将数据回写到磁盘,这样能提升数据库的整体性能。

当缓冲池不够用时,需要释放掉不常用的页时,就可以强行采用checkpoint的方式,将不常用的脏页回写到磁盘上,然后再从缓冲池中将这些页释放掉

注意:这里的脏页,指缓冲池中 被修改过的页,与磁盘上的数据页不一致

查看/设置缓冲池的大小:

MyISAM存储引擎,只缓存索引,并不缓存数据,对应的键缓存参数为key_buffer_size

InnoDB存储引擎,命令如下:

# 查看缓冲池大小:
show variables like 'innodb_buffer_pool_size';

#设置缓冲池的大小:
set global innodb_buffer_pool_size=新的大小(单位B字节);

多个buffer pool实例:

缓冲池buffer pool本质上是InnoDB向操作系统申请的一块连续内存空间。

多线程环境下,访问buffer pool数据需要进行加锁处理。

在缓冲池特别大且多线程并发访问特别高时,单一的buffer pool会影响请求的处理速度,故需要将其拆分为若干个小的buffer pool(每个buffer pool被称为一个实例,相互之间是独立、独立的管理各种链表)。这样在多线程并发访问时,多个buffer pool并不会相互影响,从而提高并发处理能力。

# 服务器启动时,通过设置innodb_buffer_pool_instances的值来修改buffer pool实例的个数
[server]
innodb_buffer_pool_instances=实例个数

# 查询当前mysql的buffer pool的实例数:
show variables like 'innodb_buffer_pool_instances';

注意:buffer pool的实例个数并不是越多越好,分别管理各个buffer pool实例也是需要性能开销的。建议当buffer pool大于等于1G时,设置多个buffer pool实例。

索引:

一次查询同一张表,只能使用一条索引。当存在多个索引时,选择查询数据少的索引。

索引的数据结构:

为何要使用索引?

索引是存储引擎用于快速找到数据记录的一种数据结构。在进行数据查找时,首先查看查询条件是否命中某条索引,命中则通过索引查找相关数据,否则则需要全表扫描,即一条条查找记录。

在这里插入图片描述

在没有索引的情况下,CPU必须不断从磁盘逐行的找到记录,加载到内存,再进行处理判断。该过程,涉及多次的磁盘I/O非常耗时(要考虑磁盘的旋转时间(速度较快)、磁头的寻道时间(速度快且耗时))。

给上图中的col2加了索引,即相当于在硬盘上为col2维护了一个索引的数据结构,即二叉搜索树,其每个结点存储的是(key,Value)键值对(key是col2,Value是Key所在行的文件指针(即地址))。通过索引能快速的定位到记录的地址,即提高查询速度。

建索引的目的:减少磁盘I/O的次数,加快查询效率。

索引及其优缺点:

官方的定义:索引Index是帮助MySQL高效获取数据的数据结构。

索引的本质是数据结构。可以简单看作 “排好序的快速查找数据结构” ,这些数据结构以某种方式指向数据,这样就可以在这些数据结构的基础上实现高级查找算法

索引在存储引擎中实现,每种存储引擎的索引不一定完全相同,并且每种存储引擎不一定支持所有索引类型。存储引擎可以定义每个表的最大索引数和最大索引长度。所有存储引擎支持每个表至少16个索引,总索引长度至少为256字节。

优点:

  1. 创建索引的主要原因,减少数据库的I/O成本
  2. 创建唯一索引,可确保数据库每行数据的唯一性
  3. 加速表与表之间的连接,即对于有依赖关系的子/父表的联合查询可提高查询速度;
  4. 可以显著减少分组查询和排序的时间,降低CPU的消耗。

缺点:

  1. 创建索引和维护索引要耗费时间,并且随着数据量的增大,耗费的时间也会增加;
  2. 索引需要占用磁盘空间,大量的索引,可能出现索引文件比数据文件更快达到最大尺度;
  3. 降低了更新表的速度,当对表中的数据进行增/删/改时,索引也需要动态维护,故会降低数据的维护速度。

提示:索引可以提高查询速度,但会影响增/删/改的速度(这种情况下,最好的办法:删除索引 --> 更新表 --> 插入索引)。

InnoDB中索引:

索引之前的查找:
页内查找:
  • 以主键为搜索条件:可以在页目录中使用二分法快速定位到对应的槽,之后遍历该槽对应分组中的记录即可。
  • 以其他列作为搜索条件:因在数据页中并没有对非主键列建立所谓的页目录,故只能从最小的记录开始依次遍历单链表中的每条记录。
很多页中查找:(定位所在的页、页内查找)
  • 没有索引的情况下,只能从第一页沿着双向链表一直往下找;
  • 页内,则按照之前的方法查找。
常见的索引类型:

在这里插入图片描述

InnoDB的索引按照物理实现方式,分为:聚簇索引、非聚簇索引(又称二级索引或辅助索引)。

聚簇索引:

聚簇索引并不是一种单独的索引类型,而是一种数据存储方式(所有用户记录都存储在叶子节点),也就是索引即数据,数据即索引

注:这里 “聚簇” 表示数据行和相邻的键值聚簇的存储在一起。

特点:

  • 使用记录主键值的大小进行记录和页的排序,即:

    1)页内记录按照主键的大小组成单链表

    2)不同的用户记录页,按照主键的大小组成双向链表

    3)存放目录项记录的页,在同一层中按照目录页中主键的大小组成双向链表

  • B+树的叶子节点存储的是完整的用户记录(即每条记录中,存储了所有列的值)。

注:聚簇索引并不需要我们在MySQL语句中显式的使用INDEX语句创建,InnoDB存储引擎会自动为我们创建聚簇索引

优点:

  • 数据访问速度更快,因聚簇索引将索引和数据保存在同一个B+树中,因此从聚集索引中获取数据比非聚簇索引更快。
  • 聚簇索引对于主键的排序查找和范围查找速度非常快,且节省大量的I/O操作

缺点:

  • 插入速度严重依赖于插入顺序,按照主键的顺序插入是最快的方式,否则将会出现页分裂,严重影响性能。一般用自增的ID列作主键
  • 更新主键的代价更高,因将会导致被更新行移动,故对于InnoDB表来说,一般定义主键不可更新
  • 二级索引的访问需要两次索引查找,第一次找到主键值、第二次根据主键值找到行数据。

限制:

  • 对于MySQL数据库来说,只有InnoDB数据引擎支持聚簇索引,而MyISAM并不支持。
  • 由于数据物理存储排序方式只能有一种,即每个MySQL表只能有一个聚簇索引,一般是该表的主键(主键列尽可能地选用有序地顺序id)。
  • 如果表中未定义主键,InnoDB会选择非空的唯一索引;也没有非空的唯一索引,则InnoDB会隐式定义一个主键作为聚簇索引
二级索引(非聚簇索引):

聚簇索引只能在搜索条件是主键值时,才能发挥作用,且B+树中的数据都是按照主键进行排序的。

如果要以别的列作为搜索条件怎么办?可多建几棵B+树,不同的B+树中的数据采用不同的排序规则。

在这里插入图片描述

上图的二级索引与之前介绍的聚簇索引的不同点:

  • 使用记录c2列的大小进行记录和页的排序。
  • B+树的叶子节点存储的并不是完整的用户记录,而只是c2列+主键这两列的值。
  • 目录项记录不再是主键+页号,而变成了c2+页号

根据以c2列大小排序的B+树只能确定需要查找的记录的主键值,如果需要查找完整的记录,仍然需要回到聚簇索引中再查一遍,该过程称为回表。总的来说,根据c2列的值查询一条完整的用户记录,需要使用2棵B+树。

直接把完整地用户记录放在以c2列构建的B+树的叶子节点中是否可行?不行,这样每建立一棵B+树就需要存储完整地用户记录,太浪费空间了。

这种按照非主键列建立的B+树,需要一次回表操作才能定位完整地用户记录,这种B+树称为二级索引/辅助索引/非聚簇索引

非聚簇索引的存在并不影响数据在聚簇索引中的组织,故一张表可有多个非聚集索引。

在这里插入图片描述

由于非聚簇索引的节点只包含了 “key+主键”,即包含的信息更少,故每页能容纳的信息就越多,树的高度会极大地降低,进而查找时有更少的磁盘I/O。

索引下推

  • 索引下推是MySQL 5.6及之后版本推出的,用来优化查询。

  • 索引下推,是将本应在server层进行筛选的条件,下推到存储引擎层进行筛选判断,这样能有效减少回表的次数

    select * from t where c1 > 12 and c2 = 2;
    
    # 如果没有索引下推,则只会走c1的索引,筛选出数据(满足c1 > 12条件的数据),之后在server层对c2字段进行过滤。
    # 有了索引下推之后,会将 c1>12和C2=2 全部下推到存储引擎,进行数据筛选,极大的减少了回表的次数。
    

聚簇索引和非聚簇索引的原理不同,在使用上也有区别:

  • 聚簇索引叶子节点存储的就是数据记录非聚簇索引的叶子节点存储的是数据位置,不会影响数据表的物理存储顺序
  • 一个表只能有一个聚簇索引(只能由一种存储方式),但可有多个非聚簇索引(即可有多个索引目录提供数据检索)
  • 使用聚簇索引时,数据的查询效率高(不用进行回表操作),但对数据的增/删/改等操作效率比非聚簇索引低
联合索引:

本质上将联合索引,是一种特殊的非聚簇索引。

在这里插入图片描述

  • 每条目录项记录都由c2、c3、页号三部分构成,各条记录先按照c2列的值进行排序,相同则按照c3列的值进行排序。
  • B+树的叶子节点处的数据由c2、c3、主键c1列组成。

以c2、c3列的大小为排序规则而建立的B+树称为联合索引

InnoDB的B+树索引的注意事项:
  1. 根页面位置一直不动

  2. 非叶子节点(内节点)中目录项记录的唯一性

    如果当多条记录的某个字段不唯一,导致目录项不唯一,则需要用索引列和主键列来构建B+树,保证目录项的唯一性。

    在这里插入图片描述

  3. 一个页面最少存储2条记录

MyISAM中的索引方案:

在这里插入图片描述

InnoDB和MyISAM默认的索引是BTree索引,而Memory默认的索引是Hash索引。

MyISAM引擎使用B+Tree作为索引结构,叶子节点的data域存放的是数据记录的地址,即索引和数据是分开存储的。

MyISAM索引的原理:

在这里插入图片描述

  • 将表中的数据按照记录的插入顺序单独存储在一个文件中且不用划分为若干的数据页,称之为数据文件。

    (由于插入时,并没有刻意按照主键大小排序,所以并不能用二分法查找。)

  • 使用MyISAM存储引擎的表中,将索引信息单独存储在索引文件中,且索引的叶子节点中存储的是主键值+数据记录的地址的组合。

MyISAM与InnoDB的对比:

MyISAM的索引方式都是 “非聚簇” 的,与InnoDB包含一个聚簇索引是不同的。

  • InnoDB存储引擎中,需要根据主键值对聚簇索引进行一次查找就能找到对应的完整用户记录

    MyISAM中,则需要进行一次回表操作,故MyISAM中建立的索引相当于全部是二级索引

  • InnoDB的数据文件本身就是索引文件,而MyISAM索引文件和数据文件是分离的(索引文件仅保存数据记录的地址)。

  • InnoDB的非聚簇索引data域存储相应记录主键的值(InnoDB的所有非聚簇索引都引用主键作为data域),而MyISAM索引记录的是数据的地址

  • InnoDB要求表必须有主键,但MyISAM没有要求。

    InnoDB如果没有显示指定主键,则mysql系统会选择一个非空且唯一的数据记录列作为主键,也没有则生成一个隐含字段作为主键(类型为长整型,长度为6字节)。

为什么MyISAM比InnoDB快?

  1. MyISAM的回表操作十分迅速,地址偏移量直接到文件中取数据。

    InnoDB的回表,通过获取的主键再去聚簇索引中找到记录,虽说也不慢,但比不上直接用地址去访问。

  2. MyISAM只缓存了索引块,减少了缓存换入换出的频率。

  3. InnoDB还需要维护MVCC一致,而MyISAM表锁牺牲了写性能,提高了读性能。

在这里插入图片描述

索引的代价:

  • 空间上的代价:

    每建立一个索引都要为它建立一颗B+树,其中树的每个节点都是一个数据页(默认大小为16KB),故一颗B+树由许多数据页组成,需要占用很大的存储空间。

  • 时间上的代价:

    每次对表中的数据进行增/删/改操作时,都需要去修改各个B+树的索引。

    • B+树每层节点(数据页,默认16KB)都是按照索引列的值从小到大顺序排列组成的双向链表。
    • 不论是叶子节点中的用户记录,还是内节点中的目录项记录,都是按照索引值从小到大的顺序组成的单向链表。

    增/删/改操作,可能会对节点和记录的排序造成破坏,所以存储引擎需要额外的时间进行一些记录移位,页面分裂、页面回收等操作来维护好节点和记录的排序。如果索引太多,维护操作也就会相应的增多,导致性能下降。

总结:一个表上索引建的索引越多,占用存储空间就越多,在增/删/改操作时性能就越差,故并非索引越多越好。

MySQL数据结构选择的合理性:

查找都是索引操作,一般来说索引非常大(尤其关系型数据库),可能几个G甚至更多。为了减少索引在内存中的占用,数据库索引是存储在外部磁盘上的,当利用索引查询时不可能把整个索引全部加载到内存,只能逐一加载,故mysql衡量查询效率的标准就是磁盘I/O次数

注意:磁盘I/O操作次数对索引的使用效率至关重要。

全表扫描
Hash结构

Hash本身是一个函数,又称散列函数,可帮助我们大幅提升检索数据的效率。

加速查找速度的数据结构,常见的两种:
  1. 树:如平衡二叉树,查/增/删/改的平均时间复杂度都是O(log2n)

  2. 哈希:如hashmap,查/增/删/改的平均时间复杂度都是O(1)

    在这里插入图片描述

注意:哈希函数可能将两个不同的关键字映射到相同的位置,称为碰撞。数据库中,一般采用链表法解决,即将散列到同一槽位的元素放在一个链表中。

hash结构效率高,为何索引结构要设计为树型?
  1. hash索引,仅能满足=in的查询的O(1)级别,范围查询会退化为O(n)级别

    树型结构,“有序” 特性,保证了查询效率稳定在O(log2N)

  2. hash索引,数据存储是无序的order by时还需对数据重新排序

  3. 联合索引中,hash值是将联合索引键合并后一起计算的,故无法单独对某一个键或几个索引进行查询

  4. “等值查询”中,通常hash索引的效率更高,但对索引列的重复值来太多,效率会降低(hash冲突时,一个桶中要用行指针进行比较,找到查询的关键字,非常耗时)。hash索引通常不会用在重复值太多的列上。

hash索引适用存储引擎如下:

在这里插入图片描述

hash索引的适用性:

hash索引在键值类型(key-value)数据库中,redis存储的核心是hash表

mysql中的memory存储引擎支持hash索引,如需查询临时表时,可选择memory存储引擎,将某个字段设置为hash索引。当**“字段的重复率低”且频繁进行“等值查询”时,hash索引**是不错的选择。

InnoDB本身不支持hash索引,但提供自适应hash索引Adaptive Hash Index。如果InnoDB存储引擎监测到同样的二级索引不断被使用,则会在内存上根据二级索引数上的索引值创建一个哈希索引(将该数据页的地址存放在hash表中),下次查询时可直接找到该页面所在的位置。这样B+树也具备了Hash索引的优点。

在这里插入图片描述

采用自适应hash索引,可方便根据sql的查询条件加速定位叶子节点,特别是当B+树比较深时,通过自适应hash索引可明显提高数据的检索效率。在高并发场景下,自适应hash索引的不同的分区(共8个分区)持有不同的锁,但同一个分区只有一把锁,意味着如果一个RW_latch的等待线程数过多,则会导致性能的下降。

# 默认情况下,innodb_adaptive_hash_index是开启状态
show variables like '%innodb_adaptive_hash_index%';

# 查看mysql的自适应哈希索引中桶的个数
show variables like 'innodb_adaptive_hash_index_parts';

show engine innodb status
# 能看到两个较为重要的信息:
# 1. RW-latch等待的线程数量(自适应hash索引的不同的分区(共8个分区)持有不同的锁),即同一个分区等待的线程数量;
# 2. 走“自适应hash索引”和“二级索引树”的搜索频率
二叉搜索树:

存在的问题:可能会让树型结构退化为链表,查询的时间复杂度会从O(log2N)提升至O(n)。

为了提高查询效率,就需要减少磁盘I/O次数,故需要尽可能降低树的高度,每层的分叉越多越好

AVL树:

为解决二叉搜索树退化为链表的问题,提出了平衡二叉树/AVL树。

每访问一次节点就需要进行一次磁盘I/O操作,故由于平衡二叉树的深度较高,会导致磁盘I/O次数过大,影响整体数据查询的效率。

B-Tree:

B树英文Balanced Tree,即多路平衡查找树。它的高度远小于AVL树。

在这里插入图片描述

B-Tree相比AVL树来说磁盘的I/O操作要少,故查询效率更高。只要树的高度足够低,I/O次数足够少,就可以提高查询性能。

  • B-Tree在插入和删除时,如果导致了树的不平衡,需要通过自动调整节点的位置来保持树的自平衡。
  • 关键字集合分布在整棵树中,即叶子节点和非叶子节点都存放数据,搜索有可能在非叶子节点结束
  • 其搜索性能等价于在关键字全集内做一次二分查找。

在这里插入图片描述

B+Tree:

B+树也是一种多路搜索树,基于B-Tree进行的改进。B+Tree更适合文件索引系统。

在这里插入图片描述

B+Tree与B-Tree的差异:
  • B+Tree中,孩子节点数 = 关键字数B-Tree中,孩子节点数 = 关键字数 + 1。

  • B+Tree中,非叶子节点的关键字也同时存在在子节点中,且是子节点中所有关键字的最大/最小。

  • B+Tree中,非叶子节点仅用于索引,不保存数据,所有用户记录都存放在叶子节点中。

    B-Tree中,非叶子节点既保存索引,也保存数据记录。

  • B+树所有关键字都在叶子节点出现,叶子节点构成一个有序链表,且叶子节点本身按照关键字大小从小到大顺序链接。

B+Tree和B-Tree的根本区别在于:
  1. B+Tree非叶子节点不直接存放数据,好处在于?

    • B+Tree查询效率更稳定。B+Tree每次只有访问到叶子节点才能找到对应的数据,但B-Tree有可能在中间非叶子节点找到数据
    • B+Tree的查询效率更高。B+Tree同样的磁盘页大小,其可存储更多的节点关键字,这样会使B+Tree更加矮胖(阶数更大,深度更低),故减少了磁盘I/O次数进而提升了效率。
  2. B+Tree的叶子节点通过双链表进行递增有序连接,在范围查询上(可直接通过指针查找),效率更高

    B-Tree则需要通过中序遍历,才能完成范围查询,故效率更低。

为了减少I/O,索引数会一次性加载吗?
  1. 数据库索引存储在硬盘上,如果数据量大,必然索引的大小也很大,超过几个G。
  2. 当利用索引查询时,只能进行逐一加载每个磁盘页,因为磁盘页对应着索引树的节点。
为什么B+Tree比B-Tree更适合实际操作系统的文件索引和数据库索引?
  1. B+Tree的磁盘读写代价更低

    B+Tree的内部节点相对B-Tree较小同等磁盘页大小,可存储更多的关键字,故极大的减少了I/O次数。

  2. B+树的查询效率更加稳定

    B+Tree所有关键字的查找必须走一条从根节点到叶子节点的完整路径,即关键字查询的路径长度相同,这样每个数据的查询效率是相当的

总结:
  1. 使用索引可帮助我们快速从海量的数据中定位要查找的数据。但也存在很多不足,占用存储空间、降低数据库写操作的性能等,如果有多个索引还会增加索引选择的时间。
  2. 当使用索引时,需要平衡索引的利(提升查询效率)和弊(维护索引所需代价)。

InnoDB数据存储结构:

InnoDB的存储结构:

在这里插入图片描述

区Extent:

可实现大多数页在申请时是连续的,会更好的加快查询寻址速度、范围检索速度减少随机I/O

为什么要有区?

B+Tree中每一层的页都会形成一个双链表,如果以页为单位分配存储空间,双向链表相邻的两个页之间的物理位置可能相距很远。范围查询则只能进行随机I/O,非常慢。如果能让链表中相邻的页物理位置也相邻,这样在范围查询时就可使用顺序I/O

引入区的概念,一个区就是物理位置上连续64个页。这样就能够实现大多数页在申请时都是连续的,会加快范围查询速度,减少随机I/O。

区的分类:

在这里插入图片描述

段Segement:

是逻辑上的概念,是数据库中的分配单位不同类型的数据库对象以不同的段形式存在。InnoDB对段的管理由引擎自身实现。

当范围查询(即对叶子节点进行顺序检索)时,需要查找所有叶子节点,故通过隔离索引段和数据段,可实现直接顺序检索

常见的段:
  • 数据段(B+Tree叶子节点的区集合)
  • 索引段(B+Tree非叶子节点的区集合)
  • 回滚段(rollback segement,管理undo log segment)
某个段分配存储空间的策略:
  1. 刚开始向表中插入数据时,段是从某个碎片区以单个页面为单位来分配存储空间的
  2. 当某个段已经占用了32个碎片区页面后,就会申请以完整的区为单位来分配存储空间。

综上,段不能仅仅定义为某些区的集合,准确定义是某些零散的页面以及一些完整的区的集合

表空间Tablespace:

是一个逻辑容器,存储对象是段。一个表空间可有多个段,一个段只属于一个表。

  • 数据库是由一个或多个表空间组成。

  • 表空间从管理上分为:系统表空间、独立表空间、撤销表空间、临时表空间等。

    • 独立表空间:每张表有一个独立的表空间,且独立表空间可在不同的数据库之间迁移

      结构:段、区、页组成

      真实表空间的大小:InnoDB中.ibd文件只占用96KB=16KB*6,即6个页面大小(mysql5.7)。

    • 系统表空间:

      系统表空间和独立表空间基本类似,(由于整个mysql进程只有一个系统表空间)在系统表空间中会额外记录一些有关整个系统信息的页面。

为什么要有碎片区?

为了考虑以完整的区为单位分配给某个段对于数据量较小的表太浪费空间的情况。InnoDB提出碎片fragment区的概念。

在一个碎片区内的页,可用于不同的目的,即有些页用于段A、有些页用于段B、有些页不属于任何段。

碎片区直属于表空间,不属于任何一个段。

5.2.2 磁盘与内存交互基本单位:页

InnoDB将数据划分为若干页,默认页的大小为16KB。

#查看InnoDB默认页的大小:
show variables like '%innodb_page_size%';

以页作为磁盘和内存之间交互的基本单位,即磁盘 --[最少读取16KB]–> 内存、内存 --[最少刷新16KB]–> 磁盘。

数据库中,不论读取一行、多行,都是将这些行所在的页(页中有多个行记录)加载到内存。数据库管理存储空间的基本单位是Page页,数据库I/O操作的最小单位是页

注:记录以行存储,但读取是按页读取。

数据页加载的三种方式:

InnoDB从磁盘中读取数据的最小单位为数据页。

MySQL存放的数据逻辑上称之为表磁盘等物理层面上是按数据页形式进行存放的,当其加载到MySQL中我们称之为缓存页

如果缓冲池中没有该页数据,那么缓冲池有三种读取数据的方式,每种方式的读取效率都不同:

内存读取:

如果该数据存在内存中,基本上执行时间在1ms左右,效率很高。

在这里插入图片描述

随机读取:

如果数据没有内存中,就需要在磁盘上对该页查找,整体时间预估在10ms左右。

在这里插入图片描述

顺序读取:

顺序读取本质是一种批量读取的方式,因为我们请求的数据往往是相邻存储的,这样一次性加载到缓冲池中就不需要再对其他页面单独进行磁盘I/O操作。

批量读取的方式,即使从磁盘进行读取,效率也比内存中只单独读取一个页的效率更高。

页的内部结构:

这些页不在物理结构上相连,通过双向链表相关联。

在这里插入图片描述

数据页中的记录会按照主键值从大到小的顺序组成一个单向链表每个数据页会为存储在其中的记录生成一个页目录(数组结构),通过主键查找某条记录时可在页目录用二分法快速定位槽,然后再遍历该槽对应分组的记录即可快速定位指定的记录。

按类型划分,常见的页有:数据页(保存B+树节点,包括目录页)、系统页、Undo页和事务数据页。

数据页16KB大小的存储空间被划分为7部分,如下图:

在这里插入图片描述

File Header & FileTailer:

  • File Header:记录各种页的一些通用信息,如页的编号、上一页、下一页(双向链表就依靠此完成)等。

  • File Tailer:8字节中,前4字节存放校验和,后4字节存该页刷盘时对应得redo log中得LSN

    为了加快速度,页都会被加载到buffer pool中修改,之后按照一定频率回写到磁盘中,但回写中途掉电如何处理?

    答:页头部和页尾部,存放当前页的校验和,如果不一致则说明刷盘中途出现了问题。

Page Header & Page Directory:

  • Page Header:

在这里插入图片描述

  • Page Directory:为了提高页内查找的速度,采用了一个槽对记录行进行分组

    1. 将所有记录(包括最大最小记录、但不包括 “已删除” 的记录)分成几组。
    2. 最小分组独立成槽,最大记录所在的分组可拥有18条记录</u>,其余则<u>48条记录为一组。
    3. 每组中最后一条记录的头信息中会存储该组有多少条记录,作为n_owned字段
    4. 页目录用来存储每组最后一条记录的地址偏移量(按照先后顺序存储起来)。每组的地址偏移量又被称为槽slot每个槽相当于指针指向不同组的最后一个元素

    总结:在查询一条记录时,先通过二分查找到记录所在的槽,之后从该的最小记录开始,通过**单向链表遍历(不超过8行记录)**即可找到对应的记录。

在这里插入图片描述

在这里插入图片描述

Infimum+supremum & User Records & Free Space:

  • User Records & Free Space:

    一开始生成的页中,并没有User Records这部分。每当添加一条记录,都会从Free Space中申请一个记录大小的空间划分到User Records中,直到Freee Space的空间全部被User Record替换,(表明该页已满)需要重新申请新的页。

    在这里插入图片描述

    User Records是按照指定的行格式,一条条摆放在User Records中,相互之间形成单链表(根据行格式的的记录头信息所得)。

  • Infimum+supremum(最大最小记录):非自定义的记录,主要是用来辅助定位的。

    在这里插入图片描述

从数据页的角度看B+树如何查询?

B+Tree分为两部分:叶子节点(存储行记录)和非叶子节点(存储索引和页面指针,并不存储行记录本身)。

在这里插入图片描述

B+Trees是如何进行记录检索的?如果通过B+Tree的索引查询行记录,

  1. 从B+Tree的根节点开始逐层检索(DFS),直到找到叶子节点即找到记录所在的数据页。
  2. 将该数据页加载到内存中,对页目录中的槽进行二分查找找到记录所在的分组。
  3. 分组内,通过遍历链表(最多8次遍历)来找到记录的准确位置。

InnoDB行格式:

COMPACT行格式:

在这里插入图片描述

  • 变长字段长度列表:存放所有变长字段的真实数据占用的字节长度。

    注意:这里存储的变长长度和字段顺序是反过来的

    在这里插入图片描述

  • NULL值列表:将可为NULL的列统一管理。如果表中没有允许为NULL的列,则NULL值列表就不存在了。

    通过逆序表示NULL值列,1表示为空,0表示不为空。

    在这里插入图片描述

  • 记录头信息:

在这里插入图片描述

被删除的记录为什么还在页中存储?

答:移除被删除的记录,需要对B+树进行排列,导致性能下降。被删除的记录会组成一个所谓的垃圾链表,这个链表中占用的空间为可重用的空间,之后有新的记录插入到表中,可能会将这些被删除的记录占用的空间覆盖掉。

在这里插入图片描述

为什么添加的数据的heap_no值直接从2开始?

答:mysql会自动为每个页添加两个最大/最小记录,又被称为虚拟记录/伪记录最小记录的 heap_no=0,最大记录的 heap_no=1。

在这里插入图片描述

  • 记录的真实数据:

    在这里插入图片描述

行溢出:

一个页的大小默认为16KB(16384B),但一个varchar(M)类型的列最多可存储65533B。这样可能出现一个页都无法存储一条记录,这种现象称为行溢出

在Compact格式下,针对行溢出,只会存储该列前768B的数据,和一个指向其他页的地址(将剩余数据存放在该页)。

在这里插入图片描述

Dynamic行格式:

dynamic采用完全溢出的方式,在数据页只存储20B的指针(溢出页地址),实际数据全部存放在off Page(溢出页)中。

在这里插入图片描述

索引的创建与设计原则:

索引的声明和使用:

索引的分类:

MySQL的索引包括普通索引、唯一性索引、全文索引、单列索引、所列索引、空间索引等。

  1. 功能逻辑上,可分为4种:普通索引(允许出现相同)、唯一性索引(可有一个null值)、主键索引(非空唯一索引,且一张表只有一个主键索引)、全文索引
    • 普通索引:可创建在任何数据类型中,其值是否唯一和非空,要由字段本身的完整性约束条件决定。
    • 唯一性索引:限制该索引值必须唯一,但允许为空值,且一张表中可有多个唯一性索引。
    • 主键索引:是unique+not null索引,且一张表中只能有一个主键索引
  2. 物理实现方式,可分为2种:聚簇索引、非聚簇索引
  3. 作用字段个数,可分为2种:单列索引、联合索引(多个key组成)
    • 单列索引:在表中单个字段上创建索引。单列索引可以是普通索引/唯一性索引/全文索引,只要保证该索引值对应一个字段即可。一张表可有多个单列索引。
    • 联合索引(多列索引):在表的多个字段组合上创建索引。使用联合索引查询时,只有用到第一个字段时才使用联合索引。使用组合索引时,遵循最左前缀原则
不同存储引擎使用的索引类型:
  • InnoDB / MyISAM:支持B-TreeFull-text等索引,不支持Hash索引;
  • Memory:支持B-TreeHash等索引,不支持Full-text索引;

创建索引:

创建表时创建索引:
  1. 创建普通索引:
create table if not exists student (
	id int,
	age int,
	phone varchar(15),
	address varchar(25),
	# 创建普通索引
	index idx_id(id)
);

# 通过命令查看索引
show create table student; # 方式二
show index from student;  # 方式二,更直观

# 性能分析工具:explain
  1. 创建唯一索引

声明有唯一性索引的字段,添加数据时要保证唯一性(可添加null值)

create table if not exists student (
	id int,
	age int,
	phone varchar(15),
	address varchar(25),
	# 创建唯一性索引
	unique index un_idx_id(id)
);
  1. 创建主键索引:

通过定义主键约束的方式定义主键索引

create table if not exists student (
    # 隐式创建主键索引
	id int primary key,
	age int,
	phone varchar(15),
	address varchar(25),
);

# 通过删除主键约束的方式删除主键索引
alter table student drop primary key;
  1. 创建联合索引:

联合索引查询时,遵循 “最左前缀原则”。

create table if not exists student (
	id int,
	age int,
	phone varchar(15),
	address varchar(25),
	# 创建联合索引
	index mulIdx_id_age(id, age)
);
  1. 创建全文索引/空间索引
create table table_name (
	col data_type 
	[fulltext | spatial] index idx_name(col)
);
# 全文索引可用于全文搜索,只能在char、varchar、text列创建索引。索引总是对整列进行,不支持局部(前缀)索引。
# 创建空间索引时,要求控件类型字段必须非空
已存在的表上创建索引:
# 方式一:
alter table student add index idx_id(id);
alter table student add unique index unIdx_id(id);
alter table student add index mulIdx_id_age(id,age);

# 方式二:
create index idx_id on student(id);
create index unIdx_id on student(id);
create index mulIdx_id_age on student(id,age);

删除索引:

  1. 添加auto_increment约束字段的唯一性约束/主键约束不能被删除

  2. 删除表中列时,则该列也会从索引中删除。

    如果组成索引的所有列都被删除,则整个索引将被删除。

# 方式一:
alter table student drop index idx_id(id);
alter table student drop unique index unIdx_id(id);
alter table student drop index mulIdx_id_age(id,age);

# 方式二:
drop index idx_id on student(id);
drop index unIdx_id on student(id);
drop index mulIdx_id_age on student(id,age);

索引失效的场景:

  1. 字符串列创建索引时,尽量规定索引的长度,而不能让索引值的长度key_len过长,否则索引会失效;
  2. 索引字段涉及类型强转、mysql函数调用、表达式计算等,索引就用不上了;

索引的设计原则

适合创建索引的场景:
  1. 字段的数值有唯一性约束

    索引本身可以起到约束的作用,如唯一性索引、主键索引都可起到唯一性约束的作用。

    唯一性限制的字段,创建唯一性索引/主键索引(非空),可极大的提高通过索引定位某条记录的速度。

  2. 频繁作为where查询条件的字段

    尤其是在数据量较大时,创建普通索引,就可以大幅提升数据查询效率。

  3. 经常group by和order by的列

    使用group by对数据进行分组查询、order by对数据进行排序时,就需要对分组或排序的字段进行索引

    如果待排序的列有多个,则可在这些列上建立联合索引

    注:如果同时group by和order by时,最优的索引创建方式是给group by和order by后的字段构建联合索引。(受sql语句的执行顺序影响,联合索引中group by后的字段在前,order by后的字段在后)

  4. update、delete的where条件列

    根据where先筛选出记录,然后再更新或删除。如果where字段创建了索引,可大幅提升效率。

    如果进行update时,更新字段是非索引字段,提升效率则更明显(非索引字段的更新,不需要对索引进行维护)。

  5. distinct字段需要创建索引

    索引会对字段进行排序,这样去重就快很多。

  6. 多表join连接操作时,创建索引注意事项

    连接表的数量尽量不要超过3个,每增加一张表就相当于增加了一次嵌套循环,这严重影响效率。

    对where条件创建索引,因where才是对数据条件的过滤,在数据量大时影响巨大。

    join时,用于连接的字段创建索引 ,且该字段在多张表中的类型必须一致

  7. 使用列类型小的创建索引

    数据类型小,查询时比较操作较快

    数据类型小,索引占用的存储空间少,同一个数据页就能存储更多的记录,从而减少磁盘I/O带来的性能消耗。

    这个建议对主键更适用。主键不仅在聚簇索引中出现,在二级索引中也会存储主键,如果主键使用更小的数据类型,即可节省更多的存储空间和更高效的I/O。

  8. 使用字符串前缀创建索引

    通过截取字段的前缀作为索引,称为前缀索引。这样虽然不能精确定位记录的位置,但能定位到相应前缀所在的位置,并根据前缀相同的记录的主键值回表查询完整的字符串。既节省了空间,又减少了字符串的比较时间

    前缀长度的选择问题:多了,达不到节省索引存储空间的目的;少了,重复内容太多,字段的散列度(选择性)会降低。

    使用索引前缀的方式,无法支持使用索引排序

  9. 区分度高(散列性高)的列适合作为索引

    在记录行数一定的情况下,列的基数(指某列中不重复数据的个数)越大,该列中的值越分散,创建索引的效果越好。

    # 计算表中列的散列性:
    select count(distinct 列)/count(*) from 表名
    

    注:联合索引应把区分度高(散列性高)的列放在前面。

  10. 使用频繁的列放在联合索引的最左边

    由于 “最左前缀原则” ,可以增加联合索引的使用率。

  11. 在多个字段都需要索引时,联合索引优于单值索引

限制索引的数目:

实际中,单张表索引的数量不超过6个

  • 每个索引都需要占用磁盘空间,索引越多,需要的磁盘空间越大。
  • 索引影响增/删/改的性能。表中数据修改时,索引也要进行相应的调整,会造成负担。
  • 优化器在选择如何优化查询时,会对每个可用的索引进行评估,生成一个最好的执行计划,如果同时存在多个索引都可用于查询,会增加mysql优化器生成执行计划的时间,降低查询性能。
不适合创建索引的场景:
  1. where条件(包括group byorder by)中使用不到的字段,不要设置索引

  2. 数据量小的表,创建索引纯属浪费空间

  3. 有大量重复数据(散列度低)时,不要创建索引

  4. 避免对经常更新的表创建过多的索引

  5. 不建议用无序的值作为索引

    插入时可能造成页分裂。

  6. 删除不再使用或者很少使用的索引

  7. 不要定义冗余或重复的索引

    主键本身就会生成聚簇索引,就没必要再定义唯一索引或普通索引,否则会重复。

总结:

索引是一把双刃剑,可提高查询效率,但也会降低增/删/改的效率并占用磁盘空间。

事务:

事务概念:

  1. 事务是一组SQL语句的执行,要么全部成功,要么全部失败,不能出现部分成功、部分失败的结果。保证事务执行的原子操作。
  2. 事务的所有SQL语句全部执行成功,才能提交(commit)事务,把结果写回磁盘上。
  3. 事务执行过程中,有的SQL出现错误,那么事务必须要回滚(rollback)到最初的状态。

ACID特性:

MySQL最重要的就是日志!!!,用来保证事务操作的原子性(undo日志)、一致性、隔离性(锁/MVCC)、持久性(redo日志)。

每一个事务必须满足下面的4个特性:

  • 事务的原子性(Atomic):事务是一个不可分割的整体,事务必须具有原子特性,即不允许事务部分的完成undo日志,保证了事务执行出错时,能回滚到事务执行前的数据库的状态。

  • 事务的一致性(Consistency):一个事务执行之前和执行之后,数据库数据必须保持一致性状态。数据库的一致性状态必须由用户来负责,由并发控制机制实现,需要通过原子性、隔离性和持久性来保证。

  • 事务的隔离性(Isolation):当两个或者多个事务并发执行时,为了保证数据的安全性,将一个事物内部的操作与其它事务的操作隔离起来(不被其它正在执行的事务所看到),使得并发执行的各个事务之间不能互相影响。由mysql的事务的锁机制或MVCC来保证的。

  • 事务的持久性(Durability):事务完成(commit)以后,DBMS保证它对数据库中的数据的修改是永久性的,即使数据库因为故障出错,也应该能够恢复数据。redo日志,保证了数据库的持久性,即使突然宕机也能在重启后恢复之前的状态,即恢复已提交的数据。

事务并发存在的问题:

事务处理不经隔离并发执行事务时通常会发生以下的问题:

  • 脏读(Dirty Read):一个事务读取了另一个事务未提交的数据

    例如:当事务A和事务B并发执行时,当事务A更新后,事务B查询读取到A尚未提交的数据,此时事务A回滚,则事务B读到的数据就是无效的脏数据。(事务B读取了事务A尚未提交的数据)

  • 不可重复读(NonRepeatable Read):一个事务的操作导致另一个事务前后两次读取到不同的数据

    例如:当事务A和事务B并发执行时,当事务B查询读取数据后,事务A更新操作更改事务B查询到的数据,此时事务B再次去读该数据,发现前后两次读的数据不一样。(事务B读取了事务A已提交的数据)

  • 幻读(Phantom Read)幻读:一个事务的操作导致另一个事务前后两次查询的结果数据量不同

    例如:当事务A和事务B并发执行时,当事务B查询读取数据后,事务A新增或者删除了一条满足事务B查询条件的记录,此时事务B再去查询,发现查询到前一次不存在的记录,或者前一次查询的一些记录不见了。(事务B读取了事务A新增加的数据或者读不到事务A删除的数据)

事务的隔离级别

MySQL支持的四种隔离级别是:

  1. TRANSACTION_READ_UNCOMMITTED未提交读。说明在提交前一个事务可以看到另一个事务的变化。这样读脏数据,不可重复读和幻读都是被允许的。
  2. TRANSACTION_READ_COMMITTED已提交读。说明读取未提交的数据是不允许的。这个级别仍然允许不可重复读和虚读产生。
  3. TRANSACTION_REPEATABLE_READ可重复读。说明事务保证能够再次读取相同的数据而不会失败,但幻读仍然会出现。
  4. TRANSACTION_SERIALIZABLE串行化。最高的事务级别,它防止读脏数据、不可重复读和幻读。
隔离级别脏读不可重复读幻读并发效率数据安全性
未提交读最高最低
已提交读(oracle默认)×兼顾并发效率和数据安全性
可重复读(mysql默认)××兼顾并发效率和数据安全性
串行化×××最低最高

注意:

  • 事务隔离级别越高,为避免冲突所花费的性能也就越多。
  • 在“可重复读”(innodb默认的隔离级别),实际上可以解决部分的幻读问题(如insert和select等),但是不能防止执行update后产生的幻读问题。
  • 已提交读、可重复读,是通过MVCC多版本并发控制实现的。串行化,是通过间隙锁实现的。
  • 防止幻读的产生,还是需要设置串行化隔离级别。
  • 设置串行化隔离级别后,如果多个事务修改一个表时可能会产生阻塞等待,超时后执行失败并会提示。(通过读写锁实现,即读时共享、写时互斥)

举例:

Orcale默认采用“已提交读”的隔离级别(避免了脏读),MySQL中InnoDB默认采用“可重复读”的隔离级别(避免了不可重复读)。

事务处理的命令:

开启/提交/回滚事务:

begin # 开启一个事务
commit # 提交一个事务
rollback # 事务回滚到最初的位置
savepoint point1 # 设置一个名为point1的保存点
rollback to point2 # 事务回滚到point1保存点

查询mysql是否自动提交事务:

select @@autocommit;
# 0:手动提交事务
# 1:自动提交事务

set autocommit = 0;  # 设置为手动提交事务

查询事物的隔离级别:

select @@tx_isolation;

set tx_isolation='read-committed';
# 设置事务的隔离级别为“读未提交”

set tx_isolation='read-uncommitted';
# 设置事务的隔离级别为“读已提交”

set tx_isolation='repeatable-read';
# 设置事务的隔离级别为“可重复读”

set tx_isolation='serializable';
# 设置事务的隔离级别为“串行化”

MySQL锁机制:

表级锁&行级锁::

表级锁:对整张表加锁。开销小,加锁快,不会出现死锁锁粒度大,发生锁冲突的概率高,并发度低

行级锁:对某行记录加锁。开销大,加锁慢,会出现死锁锁粒度最小,发生锁冲突的概率最低,并发度高

注意:MyISAM不支持事务,故只支持表级锁;MySQL支持事务,故需要行级锁。

排他锁&共享锁:

  • 排它锁(Exclusive),又称 X 锁,写锁。
  • 共享锁(Shared),又称 S 锁,读锁。

XS锁之间有以下的关系:SS可以兼容的,XS、SX、XX之间是互斥的

  • 一个事务对数据对象 O 加了 S 锁,可以对 O 进行读取操作但不能进行更新操作。加锁期间其它事务能对O 加 S 锁但不能加 X 锁。
  • 一个事务对数据对象 O 加了 X 锁,就可以对 O 进行读取和更新。加锁期间其它事务不能对 O 加任何锁。

显示加锁:

select ... lock in share mode  # 强制获取共享锁

select ... for update          # 获取排它锁

InnoDB行级锁:

行级锁:

InnoDB存储引擎支持事务处理,表支持行级锁定,并发能力更好

  1. InnoDB行锁是通过给(聚簇(主键)、非聚簇(二级、辅助))索引上的索引项加锁来实现的,而不是给表的行记录加锁实现的,这就意味着只有通过索引条件检索数据,InnoDB才使用行级锁,否则InnoDB将使用表锁。
  2. 由于InnoDB的行锁实现是针对索引字段添加的锁,因此虽然访问的是InnoDB引擎下表的不同行,但是如果使用相同的索引字段作为过滤条件,依然会发生锁冲突,只能串行进行,不能并发进行。
  3. 即使SQL中使用了索引,但是经过MySQL的优化器后,如果认为全表扫描比使用索引效率更高,此时会放弃使用索引,故也不会使用行锁,而是使用表锁,比如对一些很小的表,MySQL就不会去使用索引。

间隙锁:

当我们用范围条件而不是相等条件检索数据,并请求共享或排它锁时,

  1. InnoDB 会给符合条件的已有数据记录的索引项加锁;
  2. 对于键值在条件范围内但并不存在的记录,叫做**“间隙(GAP)”** ,InnoDB 也会对这个“间隙”加锁,即间隙锁。

举例来说, 假如 user 表中只有 101 条记录, 其 userid 的值分别是 1,2,…,100,101, 下面的 SQL:

select * from user where userid > 100 for update;  # 范围条件的检索

InnoDB 不仅会对符合条件的 userid 值为 101 的记录加锁,也会对 userid 大于 101(但是这些记录并不存在)的"间隙"加锁,防止其它事务在表的末尾增加数据,那么本事务如果再次执行上述语句,就会发生幻读。

在这里插入图片描述

注意:InnoDB使用间隙锁的目的,为了防止幻读,以满足串行化隔离级别的要求。

对辅助索引加间隙锁,因辅助索引可能存在相同的值(但主键值不相同且升序排列),则

在这里插入图片描述

  • 当范围查询的字段值边界和待插入的字段值相同时,需要等待另一个范围查询的事务提交后释放间隙锁,才能插入成功。

在这里插入图片描述

  • 当等值查询时,相当于在该辅助索引字段值两端加了间隙锁,如果其他事务想在该两段插入,则阻塞等待。

    在这里插入图片描述

注意:等值查询和范围查询、主键索引和辅助索引,对应加锁的不同情况分析。

意向共享锁和意向排他锁:

意向共享锁(IS锁):事务计划给记录加行共享锁,事务在给一行记录加共享锁前,必须先取得该表的 IS

意向排他锁(IX锁):事务计划给记录加行排他锁,事务在给一行记录加排他锁前,必须先取得该表的 IX

在这里插入图片描述

  1. 意向锁是由 InnoDB 存储引擎获取行锁之前自己获取的
  2. 意向锁之间都是兼容的,不会产生冲突;
  3. 意向锁存在的意义是为了更高效的获取表锁(表格中的XS指的是表锁,不是行锁!!!);
  4. 意向锁是表级锁,协调表锁和行锁的共存关系。主要目的:显示事务正在锁定某行或者试图锁定某行

死锁:

死锁一般是应用自身造成的问题,即在对数据库的多个表做更新时,不同的代码段应对这些表按相同的顺序进行更新操作,以防止锁冲突导致的死锁问题。

一般一旦检测到死锁,会对其中的一个事务进行回滚,造成的代价较大!!!

锁的优化建议:

  1. 尽量使用较低的隔离级别;
  2. 设计合理的索引并尽量使用索引访问数据,使加锁更加准确,减少锁冲突的机会提高并发能力;
  3. 选择合理的事务大小,小事务发生锁冲突的概率小;
  4. 不同的程序访问一组表时,应尽量约定以相同的顺序访问各表,对一个表而言,尽可能以固定的顺序存取表中的行。这样可以大大减少死锁的机会;
  5. 尽量用相等条件访问数据,这样可以避免间隙锁对并发插入的影响;
  6. 不要申请超过实际需要的锁级别;
  7. 除非必须,查询时不要显示加锁;

MVCC多版本并发控制:

介绍和用途:

多版本并发控制(Multi-Version Concurrency Control,简称MVCC),是MySQL中基于乐观锁理论实现隔离级别的方式,用于实现已提交读和可重复读隔离级别

MVCC机制会生成一个数据请求时间点的一致性数据快照 (Snapshot), 并用这个快照来提供一定级别 (语句级或事务级) 的一致性读取。从用户的角度来看,好像是数据库可以提供同一数据的多个版本(系统版本号和事务版本号)。

MVCC中,读操作可以分为两类:

快照读(snapshot read):

读的是记录的**可见版本**,不用加锁(非锁定读)。如select

当前读(current read):

读取的是记录的**最新版本**,并且当前读返回的记录。如insert,delete,update,select...lock in share mode/for update

*如何通过MVCC实现已提交读和可重复读?

MVCC:每一行记录实际上有多个版本,每个版本的记录除了数据本身之外,增加了其它字段:

  • DB_TRX_ID:记录当前事务ID;
  • DB_ROLL_PTR:指向undo log日志上数据的指针;

已提交读每次执行语句(每次select查询时)的时候,都重新生成一次快照(Read View)

  • 解决了脏读?每次select生成的数据快照,必须是提交过的。
  • 未能解决可重复读的问题?每次select都会生成一个快照,其他事务更新并已提交的数据,能实时的反应在当前事务的select查询结果中。
  • 未能解决幻读的问题?每次select都会生成一个快照,其他事务增加了和当前查询条件相同的新的数据并且已经成功commit提交,导致当前事务内再次按照同样的条件查询时数据多了,即幻读。

可重复读:同一个事务开启时生成一个当前事务全局性的快照(Read View),产生时机:第一次select查询时产生且只产生一次

  • 解决了可重复读?每次select生成的数据快照,其他事务虽然更新了最新数据,但当前数据select仍然查看的是自己的全局快照

  • 部分解决了幻读?每次select生成的数据快照,其他事务虽然更新了最新数据,但当前数据仍然查看的是自己的全局快照。当当前事务内对另一个事务中新增的数据进行update后,再次select则会看到多了一条数据,即出现了幻读。但如果不进行上述操作,则不会出现幻读。

    注意:**当前事务内自己事务的修改,是可以读到的 **。(通过undo日志中的事务ID来获取)

快照内容读取原则

  1. 版本未提交,无法读取生成快照
  2. 版本已提交,但是在快照创建后提交的,无法读取;
  3. 版本已提交,但是在快照创建前提交的,可以读取
  4. 当前事务内自己事务的修改,是可以读到的

日志与备份/恢复:

在这里插入图片描述
在这里插入图片描述
binlog和redolog的两阶段提交(时序图):在这里插入图片描述
两阶段提交的主要用意是:为了保证redolog和binlog的数据一致性

  • 只有这两个日志文件在逻辑上高度一致了,才能放心地使用redolog将数据库中的状态恢复成crash之前的状态,使用binlog实现数据备份、恢复、以及主从复制。
  • 两阶段提交的机制,可以保证这两个日志文件在逻辑上是高度一致的。没有错误、没有冲突。

错误日志:

记录了当 mysqld 启动和停止时,以及服务器在运行过程中发生任何严重错误时的相关信息。

当数据库出现任何故障导致无法正常使用时,可以首先查看此日志。

查询日志:

查询日志记录了客户端的所有语句。由于上线项目sql特别多,开启查询日志 IO 太多导致 MySQL 效率低,只有在调试时才开启,比如通过查看sql发现热点数据进行缓存。

binlog日志:

二进制日志二进制日志 BIN LOG 记录了所有的 DDL(数据定义语言)语句和 DML(数据操纵语言)语句,但是不包括数据查询语句。

语句以“事件”的形式保存,它描述了数据的更改过程,对于灾难时的数据恢复起着极其重要的作用。

两个重要的应用场景:主从复制、数据恢复(mysql 的数据恢复,一般需要 “数据备份~/data.sql + bin log二进制” 共同完成)。

查看binlog:show binary logs

注意:通过 mysqlbinlog 工具(mysql原生自带的工具)可以快速解析大量的 binlog 日志文件。

数据备份和恢复:

在这里插入图片描述

举例:

  1. 备份数据:mysqldump -u root -p database_name table_name > ~/data.sql
  2. 备份数据的恢复: mysql> source ~/data.sql

慢查询日志:

详见 “MySQL优化/SQL和索引优化” 部分。

redo log&undo log:

redo log(物理日志,重做日志):

重做日志,用于记录事务操作的变化,确保事务的持久性

  • redo log是在事务开始后就开始记录,不管事务是否提交都会记录下来,在异常发生时(如数据持久化过程中掉电),重启后mysqld会恢复redo log中的记录的数据,从而保证数据的完整性。
  • innodb_log_buffer_size默认是16M,就是redo log缓冲区的大小,它随着事务开始,就开始写redo log,如果事务比较大,为了避免事务执行过程中花费过多磁盘IO,可以设置比较大的redo log缓存,节省磁盘IO。
  • InnoDB修改操作数据,不是直接修改磁盘上的数据,实际只是修改Buffer Pool中的数据。InnoDB总是先把Buffer Pool中的数据改变记录到redo log中,用来进行崩溃后的数据恢复
  • 优先记录redo log,然后再慢慢的将Buffer Pool中的脏数据刷新到磁盘上

innodb_log_group_home_dir指定的目录下的两个文件:ib_logfile0ib_logfile1,该文件被称作重做日志。

buffer pool缓存池:加速读和加速写,直接操作data page,写redo log修改就算完成,有专门的线程去做把buffer pool中的dirty page写入磁盘。

在这里插入图片描述

在这里插入图片描述

注意:undo log也会记录在redo log中。

undo log(逻辑日志,回滚日志):

undo log:回滚日志,保存了事务发生之前的数据的一个版本,主要作用:

  1. 事务执行发生错误时进行回滚操作;
  2. 提供多版本并发控制(MVCC)的非锁定读(快照读)

在这里插入图片描述

MySQL优化:

SQL和索引的优化:

开启慢查询日志,并设置慢查询时间,记录慢查询sql,进而用explain分析sql执行计划,进而给出优化措施。

数据库服务器的优化步骤:

分为观察Show Status和行动Action两部分。

在这里插入图片描述

总结:

在这里插入图片描述

查看性能参数

show status like '参数';

常用的性能参数如下:

  • connections:连接mysql服务器的次数

  • uptime:mysql服务器上线时间

  • show_queries:慢查询的次数

    可结合慢查询日志,找出慢查询语句,针对该语句进行表结构优化查询语句优化

  • innodb_rows_insert、innodb_rows_delete、innodb_rows_update、innodb_rows_read:执行增/删/改/查的行数

  • com_insert、com_delete、com_update、com_select:增/删/改/查的次数

统计SQL的查询成本

如果一条查询语句有多个执行计划,mysql会为每个执行计划计算成本,并从中选择成本最小的作为最终的执行计划。

show status like 'last_query_cost',可得到当前查询的成本(即sql语句所需要读取的页的数量),用来评价查询语句执行效率的指标之一

SQL查询是一个动态的过程,从页加载的角度看:

  1. 位置决定效率

    如果页在buffer pool中,效率是最高的,否则需要从内存或磁盘中读取。针对单个页的读取来说,如果页存在内存中,会比在磁盘中读取效率高很多。

  2. 批量决定效率

    如果从磁盘中对单个页进行随机读取,效率很差(10ms),而采用顺序读取(批量读取),平均一页的读取效率会提升很多(甚至快于单个页在内存中的随机读取)。

总的来说,将常用的数据尽量要放在buffer pool中,其次可充分利用磁盘的吞吐能力进行一次性批量读取(平均单个页的读取效率提升很多)。

定位执行慢的SQL:慢查询日志

# 检查慢查询日志是否开启
show variables like '%slow_query_log';

# 设置慢查询日志的状态
set global slow_query_log = on/off;

MySQL的慢查询日志,用来记录在MySQL中响应时间超过阈值的语句,即运行时间超过long_query_time值(默认为10s)的SQL查询,则会被记录到慢查询日志中。

# 慢查询的时间阈值
show variables like '%long_query_time%';

# 设置慢查询志的时间阈值
set long_query_time = 1;
set global long_query_time = 1;
# 修改配置文件my.cnf,可永久修改
[mysqld]
slow_query_log = on/off
slow_query_log_file = /var/lib/mysql/slow_query.log  # 如果不指定慢查询日志文件名,默认为hostname-slow.log
log_query_time = 3
log_output=FILE

检查慢查询日志,找到执行时间特别长的SQL查询,针对性的进行优化,可提高系统的整体效率。

注意:非调优需要的话,一般不建议启动慢查询。

慢查询日志支持将日志记录写入文件:

# 慢查询日志文件"slow_query_log_file"
show variables like '%slow_query_log%'; 

查看慢查询数目:

show global status like '%Slow_queries%';

慢查询日志分析工具:

mysqldumpslow

重新生成慢查询日志文件:

mysqladmin -u root -p flush-logs slow
# 注意:如果需要旧的日志文件,则需要进行备份。

查看SQL执行成本show profiles:

# 查询近期的sql执行语句
show profiles;

# 查询Query_ID条sql语句的执行状态和开销
show profile for query ID

# 也可执行查询参数,如下:
show profile cpu, block io for query ID
# 常用的查询参数:
all               # 查询所有的开销信息
block io          # 显示快io开销
context switches  # 上下文切换开销
cpu               # 显示cpu开销
IPC               # 显示发送和接受开销信息
memory            # 显示内存开销信息
page faults       # 显示页面错误开销信息
source            # 显示和source_function、source_file、source_line相关的开销信息
swaps             # 显示交换次数开销信息

分析查询语句:explain

应用的优化:

除了优化SQL和索引,很多时候,在实际生产环境中,由于数据库服务器本身的性能局限,就必须要对上层的应用来进行一些优化,使得上层应用访问数据库的压力能够减到最小。

连接池:

参考本人写的另一篇博客中,mysql连接池的实现

应用访问数据库,都要先和MySQL Server创建连接(包括三次握手、权限验证等过程),然后发送SQL语句,并经Server处理完成后,再把结果通过网络返回给应用,之后再关闭与MySQL Server的连接。因此,如果短时间大量的数据库访问,则建立和断开连接中TCP三次握手和四次挥手所花费的时间就很大了。

一般都会在应用访问数据库中间层,添加连接池模块,相当于应用与MySQL Server事先创建一组连接,当需要请求MySQL Server时,不需要再进行TCP连接和释放连接了,直接从连接池获取连接即可发送sql语句。

一般连接池都会维护以下资源:

  1. 连接池里面保持固定数量的活跃TCP连接,供应用使用。
  2. 如果应用瞬间访问MySQL的量增大时,那么连接池会实时进行**“扩容”**,以满足应用的需求。
  3. 当连接池里面的TCP连接一段时间内没有被用到,连接池会进行**“缩容”**释放多余的连接资源,仅保留其设置的最大空闲连接量

增加缓存层:

业务上增加redis、memcache,即用缓存把经常访问的热点数据缓存起来,提高其查询效率。

以redis为例,引入缓存后,需要考虑**缓存一致性、缓存击穿(加锁,避免并发查询同一个过期数据)、缓存雪崩(设置过期后的异步操作,一旦过期立即从磁盘获取并更新数据;将缓存数据失效时间分散开,避免key的批量过期出现)**等问题。

MySQL Server的优化:

主要指的就是MySQL Server启动时加载的配置文件my.ini(windows中)或my.cnf(linux中)中配置项内容的优化。

MySQL查询缓存:

将select查询语句上一次的查询结果记录下来放在查询缓存中,下一次再查询相同内容时,直接从缓存中查询,不用再进行一遍真正的SQL查询。但是当两个select查询中间出现insert,update,delete语句的时候,查询缓存就会被清空。

适用场景:查询缓存适用更新不频繁的表,因为过多的查询缓存的数据添加和删除会不停的写入和删除查询缓存,这会影响MySQL的执行效率,还不如每次都从磁盘上查来得快(缓存指的就是一块内存,内存I/O比磁盘I/O快很多)。

查看查询缓存的设置:(通过修改配置文件,可改变变量的值,如query_cache_typequery_cache_type

在这里插入图片描述

查看缓存的使用情况:

在这里插入图片描述

索引和数据缓存:

主要指配置文件中innodb_buffer_pool_size配置项,其定义了 InnoDB 存储引擎的表数据和索引数据的最大内存缓冲区大小,值越高,访问表中数据需要的磁盘 I/O 就越少。

MySQL线程缓存:

主要指配置文件中thread_cache_size配置项。

MySQL Server 网络模块采用经典的 I/O复用+线程池模型,引入线程池就是在业务使用之前,先创建一组固定数量的线程,等待事件发生,当有 SQL 请求到达 MySQL Server 的时候,在线程池中取一个线程来执行该 SQL 请求就可以了,执行完成后把线程再归还到线程池中,并继续等待下一次任务的处理。

注意:MySQL会根据连接量,对线程池进行“扩/缩容”,保证 MySQL Server 的性能不受影响。

并发连接数量和超时时间:

MySQL Server 作为一个服务器,可以设置客户端的最大连接量和连接超时时间(MySQL Server 对超时未通信的连接,进行主动关闭操作,单位是秒)。

在这里插入图片描述

在这里插入图片描述

MySQL集群:

在实际生产环境中,如果对mysql数据库的读和写都在一台数据库服务器中操作,无论是在安全性、高可用性,还是高并发等各个方面都是不能满足实际需求的。一般要通过主从复制的方式来同步数据,再通过读写分离来提升数据库的并发负载能力

  1. 数据备份 - 热备份&容灾&高可用。
  2. 读写分离,支持更大的并发。

主从复制:

原理:

  1. 主库是业务数据库, 从库相当于主库的备份,并能分担主库的读任务降低其的业务压力。
  2. 做数据的热备:主数据库服务器故障后,可切换到从数据库继续工作,避免数据丢失。
    在这里插入图片描述
    在这里插入图片描述

主从复制的流程:两个日志(bin log 二进制日志 & relay log 日志)和三个线程(master 的一个线程 binlog dump thread和 slave 的二个线程sql thread、I/O thread)。

  1. 主库的更新操作写入 bin log 二进制日志中。
    master 服务器创建一个 binlog 转储线程binlog dump),将二进制日志内容发送到从服务器

  2. salve 从服务器会在一定时间间隔内对 master 主服务器上的二进制日志进行探测,探测其是否发生过改变。
    如果探测到 master 主服务器的二进制事件日志发生了改变,slave 机器执行 START SLAVE 命令开启一个工作线程(I/O 线程)接收 master 的 bin log 并复制到其中继日志 relay log

    注意:slave 从 master 的二进制日志中读取事件时,如果已经跟上 master,它会睡眠并等待 master 产生新的事件。

  3. sql slave threadsql 线程)处理该过程的最后一步:从中继日志 redo log 中Exec_Master_Log_Pos位置开始执行读取到的更新事件,将更新内容写入到slave的db,从而实现 slave 与 master 的数据保持一致。

    中继日志通常会位于os缓存中,所以中继日志的开销很小。

主从复制,配置示例。

主从复制中数据同步的方式:

异步复制(默认),5.5版本之后半同步复制,5.6版本之后新增GTID复制,包括5.7版本的多源复制。

  1. 异步复制方式不足之处:当主库把event写入二进制日志后,并不知道从库是否已经接受并应用日志了。如果主库发生意外宕机或者是奔溃,很有可能主库提交的事物没有传到任何一台从库机器上。
    在高可用集群架构下做主备切换,就会造成新的主库丢失数据。
    在这里插入图片描述

  2. mysql5.5版本之后,引入了半同步复制功能,主从服务器必须安装半同步复制插件,才能开启该复制功能。该功能确保从库接收完主库传递过来的binlog内容已写入自己的relay log里,才会通知主库上的等待线程,该操作完毕。
    在这里插入图片描述
    主从复制中数据复制方式。

存在的问题及解决办法:

mysql主从复制存在的问题:

  1. 主库宕机后,数据可能丢失
  2. 从库只有一个sql Thread,主库写压力大,复制很可能延时

解决方法:

  1. 半同步复制—解决数据丢失的问题 (5.5集成到mysql,以插件的形式存在,需要单独安装。事务在主库写完binlog后需要从库返回一个已接受,才返回给客户端;确保事务提交后binlog至少传输到一个从库。但是性能有一定的降低。)
  2. 并行复制—-解决从库复制延迟的问题。

读写分离:

多数项目开始时用单机数据库就够了,但随着服务器的请求越来越多,需要对数据库进行读写分离。 主库(Master)负责写、多个从库副本(Slave)负责读,并通过主从复制实现“数据同步更新”,保持数据一致。slave 从库可以不断的水平扩展,故读的压力会被不断的分摊,故不会存在太大的问题。

在这里插入图片描述
目前较为常见的 MySQL 读写分离方式有:程序代码内部实现、引入中间代理层 MySQL_proxyMycat

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小白要努力sgy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值