可以通过( )命令来启动MySQL服务,MySQL高级篇 | 胖虎的工具箱

文章目录MySQL高级一 Linux下MySQL的安装与使用1. 安装前说明2. MySQL的Linux版安装3. MySQL登录4. Linux下修改配置5. 字符集的相关操作5.1、各级别的字符集5.2、请求到响应过程中字符集的变化二 MySQL的数据目录三 用户与权限管理四、逻辑架构4.1、逻辑架构剖析4.1.1、服务器处理客户端请求4.1.2、Connectors4.1.3、第1层:连接层4.1.4、第2层:服务层4.1.5、第3层:引擎层4.1.6、存储层4.1.7、小结4.2、SQL执行流程4.2.1、MySQL 中的 SQL执行流程4.2.2、MySQL8中SQL执行原理4.2.3、MySQL5.7中SQL执行原理4.2.4、SQL语法顺序4.2.5、Oracle中的SQL执行流程(了解)4.3、数据库缓冲池(buffer pool)4.3.1、缓冲池vs查询缓存4.3.2、缓冲池如何读取数据4.3.3、查看/设置缓冲池的大小4.3.4、多个Buffer Pool实例4.3.5、引申问题五、存储引擎5.1、查看存储引擎5.2、设置系统默认的存储引擎5.3、设置表的存储引擎5.4、引擎介绍5.4.1、InnoDB 引擎:具备外键支持功能的事务存储引擎5.4.2、MyISAM引擎:主要的非事务处理存储引擎5.4.3、Archive 引擎:用于数据存档5.4.4、Blackhole 引擎:丢弃写操作,读操作会返回空内容5.4.5、CSV 引擎:存储数据时,以逗号分隔各个数据项5.4.6、Memory 引擎:置于内存的表5.4.7、Federated 引擎:访问远程表5.4.8、Merge引擎:管理多个MyISAM表构成的表集合5.4.9、NDB引擎:MySQL集群专用存储引擎5.5、MyISAM和InnoDB六、索引的数据结构6.1、为什么使用索引6.2、索引及其优缺点6.2.1 、索引概述6.2.2、优点6.2.3、缺点6.3、InnoDB中索引的推演6.3.1、索引之前的查找6.3.2、设计索引6.3.2.1、一个简单的索引设计方案6.3.2.2、InnoDB中的索引方案6.3.3、常见索引概念6.3.4、InnoDB的B+树索引的注意事项6.4、MyISAM中的索引方案6.4.1、MyISAM索引的原理6.4.2、MyISAM与 InnoDB对比6.5、索引的代价6.6、MySQL数据结构选择的合理性6.6.1、全表遍历6.6.2、Hash结构6.6.3、二叉搜索树6.6.4、AVL树6.6.5、B-Tree6.6.6、B+Tree6.6.7、R树6.6.8、小结七、InnoDB数据存储结构7.1、数据库的存储结构:页7.1.1、磁盘与内存交互基本单位:页7.1.2、页结构概述7.1.3、页的大小7.1.4、页的上层结构7.2、页的内部结构7.2.1、第1部分:文件头部和文件尾部7.2.1.1、File Header(文件头)7.2.1.2、File Trailer(文件尾部)7.2.2、第2部分:空闲空间、用户记录和最小最大记录7.2.2.1、Free Space (空闲空间)7.2.2.2、User Records (用户记录)7.2.2.3、Infimum + Supremum(最小最大记录)7.2.3、第3部分:页目录和页面头部7.2.3.1、Page Directory(页目录)7.2.3.2、Page Header(页面头部)从数据页的角度看B+树如何查询7.3、InnoDB行格式(或记录格式)7.3.1、指定行格式的语法7.3.2、COMPACT行格式7.3.2.1、变长字段长度列表7.3.2.2、NULL值列表7.3.2.3、记录头信息(5字节)7.3.2.4、记录的真实数据7.3.3、Dynamic和Compressed行格式行溢出7.3.4、Redundant行格式7.4、区、段和碎片区7.4.1、为什么要有区?7.4.2、为什么要有段?7.4.3、为什么要有碎片区?7.4.4、区的分类7.5、表空间7.5.1、独立表空间7.5.2、系统表空间八、索引的创建与设计原则8.1、索引的声明与使用8.1.1、索引的分类8.1.2、创建索引1. 创建表的时候创建索引2. 在已经存在的表上创建索引8. 1.3、删除索引8.2、MySQL8.0索引新特性8.2.1、支持降序索引8.2.2、隐藏索引8.3、索引的设计原则8.3.1、哪些情况适合创建索引8.3.2、限制索引的数目8.3.3、哪些情况不适合创建索引九、性能分析工具的使用9.1、数据库服务器的优化步骤9.2、查看系统性能参数9.3、统计SQL的查询成本:last_query_cost9.4、定位执行慢的SQL:慢查询日志9.4.1、开启慢查询日志参数9.4.2、查看慢查询数目9.4.3、慢查询日志分析工具:mysqldumpslow9.4.4、关闭慢查询日志9.4.5、删除慢查询日志9.5、查看SQL执行成本:SHOW PROFILE9.6、分析查询语句:EXPLAIN9.6.1、基本语法9.6.2、EXPLAIN各列作用9.7、EXPLAIN的进一步使用9.7.1、EXPLAIN四种输出格式9.7.2、SHOW WARNINGS的使用9.8、分析优化器执行计划:trace9.9、MySQL监控分析视图-sys schema第10章 索引优化与查询优化1. 索引失效案例1.1、全值匹配我最爱1.2、最佳左前缀法则1.3、主键插入顺序1.4、计算、函数、类型转换(自动或手动)导致索引失效1.5、类型转换导致索引失效1.6、范围条件右边的列索引失效1.7、不等于(!= 或者)索引失效1.8 is null可以使用索引,is not null无法使用索引1.9 like以通配符%开头索引失效1.10 OR、前后存在非索引的列,索引失效1.11、数据库和表的字符集统一使用utf8mb4**2. 关联查询优化2.1 Index Nested-Loop Join(索引嵌套循环连接)2.2 Block Nested-Loop Join(块嵌套循环连接)2.3 Hash Join3. 子查询优化4. 排序优化5. GROUP BY优化6. 优化分页查询7. 优先考虑覆盖索引7.1、什么是覆盖索引?7.2、覆盖索引的利弊8. 索引条件下推8.1、使用前后的扫描过程9. 其它查询优化策略9.1 EXISTS、和 **IN、的区分9.2 COUNT(\*)与COUNT(具体字段)效率9.3、关于SELECT(\*)9.4 LIMIT 1、对优化的影响9.5、多使用COMMIT**第11章 数据库的设计规范1. 范 式1.1、范式简介1.2、范式都包括哪些1.3、键和相关属性的概念1.4、第一范式(1st NF)1.5、第二范式(2nd NF)1.6、第三范式(3rd NF)1.7 小结2. 反范式化2.1、概述2.2、反范式的新问题2.3、反范式的适用场景3. BCNF(巴斯范式)4. ER模型4.1 ER 模型包括那些要素?4.2、关系的类型5. 数据表的设计原则6. 数据库对象编写建议6.1、关于库6.2、关于表、列6.3、关于索引6.4 SQL编写第12章 数据库其它调优策略1. 数据库调优的措施1.1、调优的目标1.2、如何定位调优问题1.3、调优的维度和步骤2. 优化MySQL服务器2.1、优化服务器硬件2.2、优化MySQL的参数3. 优化数据库结构3.1、拆分表:冷热数据分离3.2、增加中间表3.3、增加冗余字段3.4、优化数据类型3.5、优化插入记录的速度3.6、使用非空约束3.7、分析表、检查表与优化表第13章 事务基础知识1. 数据库事务概述1.1、基本概念1.2、事务的ACID特性1.3、事务的状态2. 如何使用事务2.1、显式事务2.2、隐式事务2.3、隐式提交数据的情况3. 事务隔离级别3.1、数据并发问题3.2 SQL中的四种隔离级别3.3、如何设置事务的隔离级别第14章 MySQL事务日志1. redo日志1.1、为什么需要REDO日志1.2 REDO日志的好处、特点1.3 redo的组成1.4 redo的整体流程1.5 redo log的刷盘策略1.6、不同刷盘策略演示1.7、写入redo log buffer过程1.8 redo log file**2. Undo日志2.1、如何理解Undo日志2.2 Undo日志的作用2.3 undo的存储结构2.4 undo的类型2.5 undo log的生命周期2.6、小结第15章 锁1. 概述2. MySQL并发事务访问相同记录2.1、读-读情况2.2、写-写情况2.3、读-写或写-读情况2.4、并发问题的解决方案3. 锁的不同角度分类3.1、从数据操作的类型划分:读锁、写锁3.2、从数据操作的粒度划分:表级锁、页级锁、行锁3.3、从对待锁的态度划分:乐观锁、悲观锁3.4、按加锁的方式划分:显式锁、隐式锁3.5、其它锁之:全局锁3.6、其它锁之:死锁4. 锁的内存结构5. 锁监控第16章 多版本并发控制1. 什么是MVCC**2. 快照读与当前读2.1、快照读2.2、当前读3. 复习3.1、再谈隔离级别3.2、隐藏字段、Undo Log版本链4. MVCC实现原理之ReadView**4.1、什么是ReadView**4.2、设计思路4.3 ReadView的规则4.4 MVCC整体操作流程5. 举例说明5.1 READ COMMITTED隔离级别下5.2 REPEATABLE READ隔离级别下5.3、如何解决幻读6. 总结第17章 其它数据库日志1. MySQL支持的日志1.1、日志类型1.2、日志的弊端2. 通用查询日志(general query log)2.1、查看当前状态2.2、启动日志2.3、停止日志3.错误日志(error log)3.1、启动日志3.2、查看日志3.3、删除\刷新日志4. 二进制日志(bin log)4.1、查看默认情况4.2、日志参数设置4.3、查看日志4.4、使用日志恢复数据4.5、删除二进制日志5. 再谈二进制日志(binlog)5.1、写入机制5.2 binlog与redolog对比5.3、两阶段提交6. 中继日志(relay log)6.1、介绍6.2、恢复的典型错误第18章 主从复制1. 主从复制概述1.1、如何提升数据库并发能力1.2、主从复制的作用2. 主从复制的原理2.1、原理剖析2.2、复制的基本原则3. 同步数据一致性问题3.1、理解主从延迟问题3.2、主从延迟问题原因3.3、如何减少主从延迟3.4、如何解决一致性问题第19章 数据库备份与恢复1. 物理备份与逻辑备份2. mysqldump实现逻辑备份2.1、备份一个数据库2.2、备份全部数据库2.3、备份部分数据库2.4、备份部分表2.5、备份单表的部分数据2.6、排除某些表的备份2.7、只备份结构或只备份数据2.8、备份中包含存储过程、函数、事件3. mysql命令恢复数据3.1、单库备份中恢复单库3.2、全量备份恢复3.3、从全量备份中恢复单库3.4、从单库备份中恢复单表4. 表的导出与导入4.1、表的导出4.2、表的导入

MySQL高级一 Linux下MySQL的安装与使用1. 安装前说明####1.1查看是否安装过MySQL- 如果你是用rpm安装, 检查一下RPM PACKAGE:rpm -qa | grep -i mysql # -i 忽略大小写- 检查mysql service:systemctl status mysqld.service#### 1.2MySQL的卸载 1. 关闭mysql服务 systemctl stop mysqld.service2. 查看当前mysql安装状况 rpm -qa | grep -i mysql# 或yum list installed | grep mysql3. 卸载上述命令查询出的已安装程序 yum remove mysql-xxx mysql-xxx mysql-xxx mysqk-xxxx务必卸载干净,反复执行`rpm -qa | grep -i mysql 确认是否有卸载残留4. 删除mysql相关文件 - 查找相关文件find / -name mysql- 删除上述命令查找出的相关文件rm -rf xxx5. 删除 my.cnf**rm -rf /etc/my.cnf2. MySQL的Linux版安装####2.1 CentOS7下检查MySQL依赖1. 检查/tmp临时目录权限(必不可少) 由于mysql安装过程中,会通过mysql用户在/tmp目录下新建tmp_db文件,所以请给/tmp较大的权限。执行 :chmod -R 777 /tmp2. 安装前,检查依赖 rpm -qa|grep libaiorpm -qa|grep net-tools####2.2 CentOS7下MySQL安装过程1. 将安装程序拷贝到/opt目录下 在mysql的安装文件目录下执行:(必须按照顺序执行)rpm -ivh mysql-community-common-8.0.25-1.el7.x86_64.rpm rpm -ivh mysql-community-client-plugins-8.0.25-1.el7.x86_64.rpm rpm -ivh mysql-community-libs-8.0.25-1.el7.x86_64.rpm rpm -ivh mysql-community-client-8.0.25-1.el7.x86_64.rpm rpm -ivh mysql-community-server-8.0.25-1.el7.x86_64.rpm- `rpm`是Redhat Package Manage缩写,通过RPM的管理,用户可以把源代码包装成以rpm为扩展名的文件形式,易于安装。- `-i`, --install 安装软件包- `-v`, --verbose 提供更多的详细信息输出- `-h`, --hash 软件包安装的时候列出哈希标记 (和 -v 一起使用效果更好),展示进度条> 若存在mariadb-libs问题,则执行**yum remove mysql-libs**即可####2.3、查看MySQL版本 mysql --version #或mysqladmin --version####2.4、服务的初始化 为了保证数据库目录与文件的所有者为 mysql 登录用户,如果你是以 root 身份运行 mysql 服务,需要执行下面的命令初始化:mysqld --initialize --user=mysql说明: --initialize 选项默认以“安全”模式来初始化,则会为 root 用户生成一个密码并将`该密码标记为过期`,登录后你需要设置一个新的密码。生成的`临时密码`会往日志中记录一份。查看密码:cat /var/log/mysqld.logroot@localhost: 后面就是初始化的密码####2.5、启动MySQL,查看状态#加不加.service后缀都可以 启动:systemctl start mysqld.service 关闭:systemctl stop mysqld.service 重启:systemctl restart mysqld.service 查看状态:systemctl status mysqld.service####2.6、查看MySQL服务是否自启动 systemctl list-unit-files|grep mysqld.service- 如不是enabled可以运行如下命令设置自启动systemctl enable mysqld.service- 如果希望不进行自启动,运行如下命令设置systemctl disable mysqld.service3. MySQL登录####3.1、首次登录 通过`mysql -hlocalhost -P3306 -uroot -p`进行登录,在Enter password:录入初始化密码####3.2、修改密码 ALTER USER 'root'@'localhost' IDENTIFIED BY 'new_password';####3.3、设置远程登录 1.确认网络1.在远程机器上使用ping ip地址`保证网络畅通`2.在远程机器上使用telnet命令`保证端口号开放`访问2. 关闭防火墙或开放端口 方式一:关闭防火墙 - CentOS6 :service iptables stop- CentOS7:#开启防火墙systemctl start firewalld.service#查看防火墙状态systemctl status firewalld.service#关闭防火墙systemctl stop firewalld.service#设置开机启用防火墙 systemctl enable firewalld.service #设置开机禁用防火墙 systemctl disable firewalld.service方式二:开放端口 - 查看开放的端口号firewall-cmd --list-all- 设置开放的端口号firewall-cmd --add-service=http --permanentfirewall-cmd --add-port=3306/tcp --permanent- 重启防火墙firewall-cmd --reload4. Linux下修改配置# 修改允许远程登陆use mysql;select Host,User from user;update user set host = '%' where user ='root';flush privileges;> `%`是个 通配符 ,如果Host=192.168.1.%,那么就表示只要是IP地址前缀为“192.168.1.”的客户端都可以连接。如果`Host=%`,表示所有IP都有连接权限。>> 注意:在生产环境下不能为了省事将host设置为%,这样做会存在安全问题,具体的设置可以根据生产环境的IP进行设置。配置新连接报错:错误号码 2058,分析是 mysql 密码加密方法变了。解决方法一: 升级远程连接工具版本解决方法二: ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY 'abc123';5. 字符集的相关操作5.1、各级别的字符集show variables like 'character%';character_set_server:服务器级别的字符集character_set_database:当前数据库的字符集character_set_client:服务器解码请求时使用的字符集character_set_connection:服务器处理请求时会把请求字符串从character_set_client转为character_set_connectioncharacter_set_results:服务器向客户端返回数据时使用的字符集

小结

如果创建或修改列时没有显式的指定字符集和比较规则,则该列默认用表的字符集和比较规则如果创建表时没有显式的指定字符集和比较规则,则该表默认用数据库的字符集和比较规则如果创建数据库时没有显式的指定字符集和比较规则,则该数据库默认用服务器的字符集和比较规则5.2、请求到响应过程中字符集的变化

使用操作系统的字符集编码请求字符串

使用操作系统的字符集解码响应的字符串

客户端

从character_set_client转换为character_set_connection

从character_set_connection转换为具体的列使用的字符集

将查询结果从具体的列上使用的字符集转换为character_set_results

二 MySQL的数据目录###1. MySQL8的主要目录结构 find / -name mysql####1.1、数据库文件的存放路径show variables like 'datadir'; # /var/lib/mysql/####1.2、相关命令目录 相关命令目录:/usr/bin 和/usr/sbin。 ####1.3、配置文件目录 配置文件目录:/usr/share/mysql-8.0(命令及配置文件),/etc/mysql(如my.cnf) ###2. 数据库和文件系统的关系 ####2.1、表在文件系统中的表示#####2.3.1 InnoDB存储引擎模式1. 表结构 为了保存表结构,`InnoDB`在`数据目录`下对应的数据库子目录下创建了一个专门用于`描述表结构的文件`表名.frm2. 表中数据和索引 2.1 系统表空间(system tablespace) 默认情况下,InnoDB会在数据目录下创建一个名为`ibdata1`、大小为`12M`的`自拓展`文件,这个文件就是对应的`系统表空间`在文件系统上的表示。2.2 独立表空间(file-per-table tablespace)在MySQL5.6.6以及之后的版本中,InnoDB并不会默认的把各个表的数据存储到系统表空间中,而是为`每一个表建立一个独立表空间`,也就是说我们创建了多少个表,就有多少个独立表空间。使用`独立表空间`来存储表数据的话,会在该表所属数据库对应的子目录下创建一个表示该独立表空间的文件,文件名和表名相同。表名.ibd> MySQL8.0中不再单独提供`表名.frm`,而是合并在`表名.ibd`文件中。2.3 系统表空间与独立表空间的设置 我们可以自己指定使用`系统表空间`还是`独立表空间`来存储数据,这个功能由启动参数`innodb_file_per_table`控制[server] innodb_file_per_table=0 # 0:代表使用系统表空间; 1:代表使用独立表空间2.4 其他类型的表空间 随着MySQL的发展,除了上述两种老牌表空间之外,现在还新提出了一些不同类型的表空间,比如通用表空间(general tablespace)、临时表空间(temporary tablespace)等。#####2.3.2 MyISAM存储引擎模式1. 表结构 - 在存储表结构方面, MyISAM 和 InnoDB 一样,也是在`数据目录`下对应的数据库子目录下创建了一个专门用于描述表结构的文件表名.frm2. 表中数据和索引 - 在MyISAM中的索引全部都是`二级索引`,该存储引擎的`数据和索引是分开存放`的。所以在文件系统中也是使用不同的文件来存储数据文件和索引文件,同时表数据都存放在对应的数据库子目录下。test.frm 存储表结构 #MySQL8.0 改为了 b.xxx.sditest.MYD 存储数据 (MYData) test.MYI 存储索引 (MYIndex三 用户与权限管理###1. 用户管理 ####1.1、登录MySQL服务器 启动MySQL服务后,可以通过mysql命令来登录MySQL服务器,命令如下:mysql –h hostname|hostIP –P port –u username –p DatabaseName –e "SQL语句"- `-h参数`后面接主机名或者主机IP,hostname为主机,hostIP为主机IP。 - `-P参数`后面接MySQL服务的端口,通过该参数连接到指定的端口。MySQL服务的默认端口是3306,不使用该参数时自动连接到3306端口,port为连接的端口号。- `-u参数`后面接用户名,username为用户名。- `-p参数`会提示输入密码。- `DatabaseName参数`指明登录到哪一个数据库中。如果没有该参数,就会直接登录到MySQL数据库中,然后可以使用USE命令来选择数据库。- `-e参数`后面可以直接加SQL语句。登录MySQL服务器以后即可执行这个SQL语句,然后退出MySQL服务器。mysql -uroot -p -hlocalhost -P3306 mysql -e "select host,user from user"####1.2、创建用户 CREATE USER 用户名 [IDENTIFIED BY '密码'][,用户名 [IDENTIFIED BY '密码']];举例:CREATE USER zhang3 IDENTIFIED BY '123123'; # 默认host是 %CREATE USER 'kangshifu'@'localhost' IDENTIFIED BY '123456';####1.3、修改用户 UPDATE mysql.user SET USER='li4' WHERE USER='wang5'; FLUSH PRIVILEGES;####1.4、删除用户 方式1:使用DROP方式删除(推荐) DROP USER user[,user]…;举例:DROP USER li4 ; # 默认删除host为%的用户DROP USER 'kangshifu'@'localhost';方式2:使用DELETE方式删除(不推荐,有残留信息) DELETE FROM mysql.user WHERE Host=’hostname’ AND User=’username’;FLUSH PRIVILEGES;####1.5、设置当前用户密码 1. 使用ALTER USER命令来修改当前用户密码 ALTER USER USER() IDENTIFIED BY 'new_password';2. 使用SET语句来修改当前用户密码 SET PASSWORD='new_password';####1.6、修改其它用户密码1. 使用ALTER语句来修改普通用户的密码 ALTER USER user [IDENTIFIED BY '新密码'] [,user[IDENTIFIED BY '新密码']]…;2. 使用SET命令来修改普通用户的密码 SET PASSWORD FOR 'username'@'hostname'='new_password';###2. 权限管理 ####2.1、权限列表 show privileges\G- `CREATE和DROP权限`,可以创建新的数据库和表,或删除(移掉)已有的数据库和表。如果将MySQL数据库中的DROP权限授予某用户,用户就可以删除MySQL访问权限保存的数据库。- `SELECT、INSERT、UPDATE和DELETE权限`允许在一个数据库现有的表上实施操作。- `SELECT权限`只有在它们真正从一个表中检索行时才被用到。- `INDEX权限`允许创建或删除索引,INDEX适用于已有的表。如果具有某个表的CREATE权限,就可以在CREATE TABLE语句中包括索引定义。- `ALTER权限`可以使用ALTER TABLE来更改表的结构和重新命名表。- `CREATE ROUTINE权限`用来创建保存的程序(函数和程序),`ALTER ROUTINE权限`用来更改和删除保存的程序,`EXECUTE权限`用来执行保存的程序。- `GRANT权限`允许授权给其他用户,可用于数据库、表和保存的程序。- `FILE权限`使用户可以使用LOAD DATA INFILE和SELECT ... INTO OUTFILE语句读或写服务器上的文件,任何被授予FILE权限的用户都能读或写MySQL服务器上的任何文件(说明用户可以读任何数据库目录下的文件,因为服务器可以访问这些文件)。####2.2、授予权限的原则 权限控制主要是出于安全因素,因此需要遵循以下几个`经验原则`:1、只授予能`满足需要的最小权限`,防止用户干坏事。比如用户只是需要查询,那就只给select权限就可以了,不要给用户赋予update、insert或者delete权限。2、创建用户的时候`限制用户的登录主机`,一般是限制成指定IP或者内网IP段。3、为每个用户`设置满足密码复杂度的密码`。 4、`定期清理不需要的用户`,回收权限或者删除用户。####2.3、授予权限 GRANT 权限1,权限2,…权限n ON 数据库名称.表名称 TO 用户名@用户地址 [IDENTIFIED BY ‘密码口令’];- 该权限如果发现没有该用户,则会直接新建一个用户。- 给li4用户用本地命令行方式,授予atguigudb这个库下的所有表的插删改查的权限。GRANT SELECT,INSERT,DELETE,UPDATE ON atguigudb.* TO li4@localhost;- 授予通过网络方式登录的joe用户 ,对所有库所有表的全部权限,密码设为123。注意这里唯独不包括grant的权限GRANT ALL PRIVILEGES ON *.* TO joe@'%' IDENTIFIED BY '123';####2.4、查看权限 - 查看当前用户权限SHOW GRANTS; # 或 SHOW GRANTS FOR CURRENT_USER; # 或 SHOW GRANTS FOR CURRENT_USER();- 查看某用户的全局权限SHOW GRANTS FOR 'user'@'主机地址';####2.5、收回权限 注意:在将用户账户从user表删除之前,应该收回相应用户的所有权限。 - 收回权限命令REVOKE 权限1,权限2,…权限n ON 数据库名称.表名称 FROM 用户名@用户地址;- 举例#收回全库全表的所有权限 REVOKE ALL PRIVILEGES ON *.* FROM joe@'%'; #收回mysql库下的所有表的插删改查权限 REVOKE SELECT,INSERT,UPDATE,DELETE ON mysql.* FROM joe@localhost;- 注意:`须用户重新登录后才能生效` ###3. 角色管理 ####3.1、创建角色 CREATE ROLE 'role_name'[@'host_name'] [,'role_name'[@'host_name']]...角色名称的命名规则和用户名类似。如果`host_name省略,默认为%`,`role_name不可省略`,不可为空。####3.2、给角色赋予权限 GRANT privileges ON table_name TO 'role_name'[@'host_name'];上述语句中privileges代表权限的名称,多个权限以逗号隔开。可使用SHOW语句查询权限名称SHOW PRIVILEGES;####3.3、查看角色的权限 SHOW GRANTS FOR 'role_name';只要你创建了一个角色,系统就会自动给你一个“`USAGE`”权限,意思是`连接登录数据库的权限`。####3.4、回收角色的权限 REVOKE privileges ON tablename FROM 'rolename';####3.5、删除角色 DROP ROLE role [,role2]...注意,`如果你删除了角色,那么用户也就失去了通过这个角色所获得的所有权限`。####3.6、给用户赋予角色 角色创建并授权后,要赋给用户并处于`激活状态`才能发挥作用。GRANT role [,role2,...] TO user [,user2,...];查询当前已激活的角色SELECT CURRENT_ROLE();####3.7、激活角色 方式1:使用set default role 命令激活角色 SET DEFAULT ROLE ALL TO 'kangshifu'@'localhost';方式2:将activate_all_roles_on_login设置为ON**SET GLOBAL activate_all_roles_on_login=ON;这条 SQL 语句的意思是,对`所有角色永久激活`。####3.8、撤销用户的角色 REVOKE role FROM user;####3.9、设置强制角色(mandatory role) - 方式1:服务启动前设置[mysqld] mandatory_roles='role1,role2@localhost,r3@%.atguigu.com'- 方式2:运行时设置SET PERSIST mandatory_roles = 'role1,role2@localhost,r3@%.example.com'; #系统重启后仍然有效SET GLOBAL mandatory_roles = 'role1,role2@localhost,r3@%.example.com'; #系统重启后失效四、逻辑架构4.1、逻辑架构剖析4.1.1、服务器处理客户端请求

首先Mysql是典型的C/S架构,服务器端程序使用的 mysqld

不论客户端进程和服务器进程是采用哪种方式进行通信,最后实现的效果都是:客户端进程向服务器进程发送一段文本(SQL语句),服务器进程处理后再向客户端进程发送一段文本(处理结果)。

那服务器进程对客户端进程发送的请求做了什么处理,才能产生最后的处理结果呢?这里以查询请求为例展示:

MySQL高级篇

MySQL高级篇

4.1.2、Connectors4.1.3、第1层:连接层

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

经过三次握手建立连接成功后,MySQL服务器对TCP传输过来的账号密码做身份认证、权限获取。

用户名或密码不对,会收到一个Access denied for user错误,客户端程序结束执行用户名密码认证通过,会从权限表查出账号拥有的权限与连接关联,之后的权限判断逻辑,都将依赖于此时读到的权限

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

4.1.4、第2层:服务层

SQL Interface: SQL接口

接收用户的SQL命令,并且返回用户需要查询的结果。比如SELECT … FROM就是调用SQL InterfaceMySQL支持DML(数据操作语言)、DDL(数据定义语言)、存储过程、视图、触发器、自定义函数等多种SQL语言接口

Parser: 解析器

在解析器中对 SQL 语句进行语法分析、语义分析。将SQL语句分解成数据结构,并将这个结构传递到后续步骤,以后SQL语句的传递和处理就是基于这个结构的。如果在分解构成中遇到错误,那么就说明这个SQL语句是不合理的。在SQL命令传递到解析器的时候会被解析器验证和解析,并为其创建语法树,并根据数据字典丰富查询语法树,会验证该客户端是否具有执行该查询的权限。创建好语法树后,MySQL还会对SQL查询进行语法上的优化,进行查询重写。

Optimizer:查询优化器

SQL语句在语法解析之后、查询之前会使用查询优化器确定 SQL 语句的执行路径,生成一个执行计划。这个执行计划表明应该使用哪些索引进行查询(全表检索还是使用索引检索),表之间的连接顺序如何,最后会按照执行计划中的步骤调用存储引擎提供的方法来真正的执行查询,并将查询结果返回给用户。它使用“选取-投影-连接”策略进行查询。例如:SELECT id,name FROM student WHERE gender = '女';

这个SELECT查询先根据WHERE语句进行选取,而不是将表全部查询出来以后再进行gender过滤。 这个SELECT查询先根据id和name进行属性投影,而不是将属性全部取出以后再进行过滤,将这两个查询条件连接起来生成最终查询结果。

Caches & Buffers: 查询缓存组件

MySQL内部维持着一些Cache和Buffer,比如Query Cache用来缓存一条SELECT语句的执行结果,如果能够在其中找到对应的查询结果,那么就不必再进行查询解析、优化和执行的整个过程了,直接将结果反馈给客户端。这个缓存机制是由一系列小缓存组成的。比如表缓存,记录缓存,key缓存,权限缓存等 。这个查询缓存可以在不同客户端之间共享。从MySQL 5.7.20开始,不推荐使用查询缓存,并在MySQL 8.0中删除。4.1.5、第3层:引擎层

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

4.1.6、存储层

所有的数据,数据库、表的定义,表的每一行的内容,索引,都是存在 文件系统 上,以 文件 的方式存在的,并完成与存储引擎的交互。当然有些存储引擎比如InnoDB,也支持不使用文件系统直接管理裸设备,但现代文件系统的实现使得这样做没有必要了。在文件系统之下,可以使用本地磁盘,可以使用DAS、NAS、SAN等各种存储系统。

4.1.7、小结

MySQL高级篇

简化为三层结构:

连接层:客户端和服务器端建立连接,客户端发送 SQL 至服务器端;

SQL 层(服务层):对 SQL 语句进行查询处理;与数据库文件的存储方式无关;

存储引擎层:与数据库文件打交道,负责数据的存储和读取。

4.2、SQL执行流程4.2.1、MySQL 中的 SQL执行流程

MySQL高级篇

MySQL的查询流程:

1、查询缓存 :Server 如果在查询缓存中发现了这条 SQL 语句,就会直接将结果返回给客户端;如果没有,就进入到解析器阶段。需要说明的是,因为查询缓存往往效率不高,所以在 MySQL8.0 之后就抛弃了这个功能。

大多数情况查询缓存就是个鸡肋,为什么呢?

SELECT employee_id,last_name FROM employees WHERE employee_id = 101;

查询缓存是提前把查询结果缓存起来,这样下次不需要执行就可以直接拿到结果。需要说明的是,在MySQL 中的查询缓存,不是缓存查询计划,而是查询对应的结果。这就意味着查询匹配的鲁棒性大大降低,只有相同的查询操作才会命中查询缓存。两个查询请求在任何字符上的不同(例如:空格、注释、大小写),都会导致缓存不会命中。因此 MySQL 的 查询缓存命中率不高 。

同时,如果查询请求中包含某些系统函数、用户自定义变量和函数、一些系统表,如 mysql 、information_schema、performance_schema 数据库中的表,那这个请求就不会被缓存。以某些系统函数举例,可能同样的函数的两次调用会产生不一样的结果,比如函数 NOW ,每次调用都会产生最新的当前时间,如果在一个查询请求中调用了这个函数,那即使查询请求的文本信息都一样,那不同时间的两次查询也应该得到不同的结果,如果在第一次查询时就缓存了,那第二次查询的时候直接使用第一次查询的结果就是错误的!

此外,既然是缓存,那就有它缓存失效的时候。MySQL的缓存系统会监测涉及到的每张表,只要该表的结构或者数据被修改,如对该表使用了INSERT、UPDATE、DELETE、TRUNCATE TABLE、ALTER TABLE、DROP TABLE或DROP DATABASE语句,那使用该表的所有高速缓存查询都将变为无效并从高速缓存中删除!对于更新压力大的数据库来说,查询缓存的命中率会非常低。

总之,因为查询缓存往往弊大于利,查询缓存的失效非常频繁。

一般建议大家在静态表里使用查询缓存,什么叫静态表呢?就是一般我们极少更新的表。比如,一个系统配置表、字典表,这张表上的查询才适合使用查询缓存。好在MysQL也提供了这种”按需使用”的方式。你可以将my.cnf参数query_cache_type设置成DEMAND,代表当sql语句中有SQL_CACHE关键词时才缓存。比如:

# query_cache_type有3个值,0代表关闭查询缓存OFF,1代表开启ON,2(DEMAND)query_cache_type=2

这样对于默认的SQL语句都不使用查询缓存。而对于你确定要是使用查询缓存的语句,可以用SQL_CACHE显示指定,像下面这个语句一样:

select SQL_CACHE * from test where ID=5;

查看当前mysql实例是否开启缓存机制

# mysql5.7中:mysql> show global variables like "%query_cache_type%";# mysql8.0中mysql> show global variables like "%query_cache_type%";Empty set (0.02 sec)

监控查询缓存的命中率:

show status like '%Qcache%';

MySQL高级篇

运行结果解析:

Qcache_free_blocks :表示查询缓存中还有多少剩余的blocks,如果该值显示较大,则说明查询缓存中的内存碎片过多了,可能在一定的时间进行整理。

Qcache_free_memory :查询缓存的内存大小,通过这个参数可以很清晰的知道当前系统的查询内存是否够用,是多了,还是不够用,DBA可以根据实际情况做出调整。

Qcache_hits :表示有多少次命中缓存。我们主要可以通过该值来验证我们的查询缓存的效果。数字越大,缓存效果越理想。

Qcache_inserts:表示多少次未命中然后插入,意思是新来的sQL请求在缓存中未找到,不得不执行查询处理执行查询处理后把结果insert到查询缓存中。这样的情况的次数越多,表示查询缓存应用到的比较少,效果也就理想。当然系统刚启动后,查询缓存是空的,这很正常。

Qcache_lowmem_prunes :该参数记录有多少条查询因为内存不足而被移除出查询缓存。通过这个值,用户可以适当的调整缓存大小。

Qcache_not_cached :表示因为query_cache_type的设置而没有被缓存的查询数量。

Qcache_queries.in.cache :当前缓存中缓存的查诎数量。

Qcache_total_blocks :当前缓存的block数量。

2、解析器 :在解析器中对 SQL 语句进行语法分析、语义分析。

MySQL高级篇

如果没有命中查询缓存,就要开始真正执行语句了。首先,MySQL需要知道你要做什么,因此需要对SQL语句做解析。SQL语句的分析分为词法分析与语法分析。

分析器先做“词法分析”。你输入的是由多个字符串和空格组成的一条 SQL 语句,MySQL 需要识别出里面的字符串分别是什么,代表什么。 MySQL 从你输入的"select"这个关键字识别出来,这是一个查询语句。它也要把字符串“T”识别成“表名 T”,把字符串“ID”识别成“列 ID”。

接着,要做“语法分析”。根据词法分析的结果,语法分析器(比如:Bison)会根据语法规则,判断你输入的这个 SQL 语句是否满足 MySQL 语法。

如果你的语句不对,就会收到“You have an error in your SQL syntax”的错误提醒,比如下面这个语句from写成了“rom”。

# 语句:select * rom test where id=1;# 错误:ERROR 1064 (42800 ) : You have an error in your SQL syntax; check the manual that corresponds toyour MySQL server version for the right syntax to use near 'fro test where id=1' at line 1

如果SQL语句正确,则会生成一个语法树。

MySQL高级篇

下面是SQL语法分析的过程步骤:

MySQL高级篇

3、优化器 :在优化器中会确定 SQL 语句的执行路径,比如是根据全表检索,还是根据索引检索等。在查询优化器中,可以分为逻辑查询优化阶段和物理查询优化阶段。

select * from test1 join test2 using(ID) where test1.name='zhangwei' and test2.name='mysql高级课程';# 方案1:可以先从表 test1 里面取出 name='zhangwei'的记录的 ID 值,再根据 ID 值关联到表 test2,再判断 test2 里面 name的值是否等于 'mysql高级课程'。# 方案2:可以先从表 test2 里面取出 name='mysql高级课程' 的记录的 ID 值,再根据 ID 值关联到 test1,再判断 test1 里面 name的值是否等于 zhangwei。这两种执行方法的逻辑结果是一样的,但是执行的效率会有不同,而优化器的作用就是决定选择使用哪一个方案。优化器阶段完成后,这个语句的执行方案就确定下来了,然后进入执行器阶段。如果你还有一些疑问,比如优化器是怎么选择索引的,有没有可能选择错等。后面讲到索引我们再谈。

在查询优化器中,可以分为 逻辑查询 优化阶段和 物理查询 优化阶段。

逻辑查询优化:就是通过改变SQL语句的内容来使得SQL查询更高效,同时为物理查询优化提供更多的候选执行计划。通常采用的方式是对SQL语句进行等价交换,对查询进行 重写,而查询重写的数学基础就是关系代数。对条件表达式进行等价谓词重写、条件简化,对视图进行重写,对子查询进行优化,对连接语义进行了外连接消除、嵌套连接消除等。

物理查询优化:是基于关系代数进行的查询重写,而关系代数的每一步都对应着物理计算,这些物理计算往往存在多种算法,因此需要计算各种物理路径的代价,从中选择代价最小的作为执行计划。在这个阶段里,对于单表和多表连接的操作,需要高效地使用索引,提升查询效率。

4、执行器 :

MySQL高级篇

在执行之前需要判断该用户是否 具备权限 。如果没有,就会返回权限错误。如果具备权限,就执行 SQL查询并返回结果。在 MySQL8.0 之前的版本,如果设置了查询缓存,这时会将查询结果进行缓存。

select * from test where id=1;

如果有权限,就打开表继续执行。打开表的时候,执行器就会根据表的引擎定义,调用存储引擎API对表进行的读写。存储引擎API只是抽象接口,下面还有个存储引擎层,具体实现还是要看表选择的存储引擎。

MySQL高级篇

比如:表 test 中,ID 字段没有索引,那么执行器的执行流程是这样的:

- 调用 InnoDB 引擎接口取这个表的第一行,判断 ID 值是不是1,如果不是则跳过,如果是则将这行存在结果集中;调用引擎接口取“下一行”,重复相同的判断逻辑,直到取到这个表的最后一行。- 执行器将上述遍历过程中所有满足条件的行组成的记录集作为结果集返回给客户端。

至此,这个语句就执行完成了。对于有索引的表,执行的逻辑也差不多。

SQL 语句在 MySQL 中的流程是: SQL语句→查询缓存→解析器→优化器→执行器 。

MySQL高级篇

4.2.2、MySQL8中SQL执行原理确认profiling 是否开启mysql> select @@profiling;mysql> show variables like 'profiling';

profiling=0 代表关闭,我们需要把 profiling 打开,即设置为 1:

mysql> set profiling=1;多次执行相同SQL查询

然后我们执行一个 SQL 查询(你可以执行任何一个 SQL 查询):

mysql> select * from employees;查看profiles

查看当前会话所产生的所有 profiles:

mysql> show profiles; # 显示最近的几次查询

MySQL高级篇

查看profile

显示执行计划,查看程序的执行步骤:

mysql> show profile;

MySQL高级篇

此外,还可以查询更丰富的内容:

Syntax:SHOW PROFILE [type [ , type ] ... ][ FOR QUERY n ][LIMIT row_count [ OFFSET offset ] ]type : {ALL -- 显示所有参数的开销信息BLOCK IO -- 显示IO的相关开销CONTEXT SWITCHES -- 上下文切换相关开销CPU -- 显示CPU相关开销信息IPC -- 显示发送和接收相关开销信息MEMORY -- 显示内存相关开销信息PAGE FAULTS -- 显示页面错误相关开销信息SOURCE -- 显示和Source_function, Source_file,Source_line相关的开销信息SWAPS -- 显示交换次数相关的开销信息}mysql> show profile cpu,block io for query 6;

MySQL高级篇

4.2.3、MySQL5.7中SQL执行原理

上述操作在MySQL5.7中测试,发现前后两次相同的sql语句,执行的查询过程仍然是相同的。不是会使用缓存吗?这里我们需要 显式开启查询缓存模式 。在MySQL5.7中如下设置:

配置文件中开启查询缓存

在 /etc/my.cnf 中新增一行:

query_cache_type=1重启mysql服务systemctl restart mysqld开启查询执行计划

由于重启过服务,需要重新执行如下指令,开启profiling。

mysql> set profiling=1;执行语句两次:mysql> select * from locations;mysql> select * from locations;查看profiles

MySQL高级篇

查看profile

显示执行计划,查看程序的执行步骤:

mysql> show profile for query 1;

MySQL高级篇

mysql> show profile for query 2;

MySQL高级篇

结论不言而喻。执行编号2时,比执行编号1时少了很多信息,从截图中可以看出查询语句直接从缓存中获取数据。

4.2.4、SQL语法顺序

随着Mysql版本的更新换代,其优化器也在不断的升级,优化器会分析不同执行顺序产生的性能消耗不同而动态调整执行顺序。

需求:查询每个部门年龄高于20岁的人数且高于20岁人数不能少于2人,显示人数最多的第一名部门信息

下面是经常出现的查询顺序:

MySQL高级篇

4.2.5、Oracle中的SQL执行流程(了解)

Oracle 中采用了 共享池 来判断 SQL 语句是否存在缓存和执行计划,通过这一步骤我们可以知道应该采用硬解析还是软解析。

我们先来看下 SQL 在 Oracle 中的执行过程:

MySQL高级篇

从上面这张图中可以看出,SQL 语句在 Oracle 中经历了以下的几个步骤。

1.语法检查:检查 SQL 拼写是否正确,如果不正确,Oracle 会报语法错误。

2.语义检查:检查 SQL 中的访问对象是否存在。比如我们在写 SELECT 语句的时候,列名写错了,系统就会提示错误。语法检查和语义检查的作用是保证 SQL 语句没有错误。

3.权限检查:看用户是否具备访问该数据的权限。

4.共享池检查:共享池(Shared Pool)是一块内存池,最主要的作用是缓存 SQL 语句和该语句的执行计划。Oracle 通过检查共享池是否存在 SQL 语句的执行计划,来判断进行软解析,还是硬解析。那软解析和硬解析又该怎么理解呢?

在共享池中,Oracle 首先对 SQL 语句进行 Hash 运算 ,然后根据 Hash 值在库缓存(Library Cache)中查找,如果 存在 SQL 语句的执行计划 ,就直接拿来执行,直接进入“执行器”的环节,这就是 软解析 。

如果没有找到 SQL 语句和执行计划,Oracle 就需要创建解析树进行解析,生成执行计划,进入“优化器”这个步骤,这就是 硬解析 。

5.优化器:优化器中就是要进行硬解析,也就是决定怎么做,比如创建解析树,生成执行计划。

6.执行器:当有了解析树和执行计划之后,就知道了 SQL 该怎么被执行,这样就可以在执行器中执行语句了。

共享池是 Oracle 中的术语,包括了库缓存,数据字典缓冲区等。我们上面已经讲到了库缓存区,它主要缓存 SQL 语句和执行计划。而 数据字典缓冲区 存储的是 Oracle 中的对象定义,比如表、视图、索引等对象。当对 SQL 语句进行解析的时候,如果需要相关的数据,会从数据字典缓冲区中提取。

库缓存 这一个步骤,决定了 SQL 语句是否需要进行硬解析。为了提升 SQL 的执行效率,我们应该尽量避免硬解析,因为在 SQL 的执行过程中,创建解析树,生成执行计划是很消耗资源的。

你可能会问,如何避免硬解析,尽量使用软解析呢?在 Oracle 中, 绑定变量 是它的一大特色。绑定变量就是在 SQL 语句中使用变量,通过不同的变量取值来改变 SQL 的执行结果。这样做的好处是能 提升软解析的可能性 ,不足之处在于可能会导致生成的执行计划不够优化,因此是否需要绑定变量还需要视情况而定。

举个例子,我们可以使用下面的查询语句:SQL> select * from player where player_id = 10001;你也可以使用绑定变量,如:SQL> select * from player where player_id = :player_id;

这两个查询语句的效率在 Oracle 中是完全不同的。如果你在查询 player_id = 10001 之后,还会查询10002、10003 之类的数据,那么每一次查询都会创建一个新的查询解析。而第二种方式使用了绑定变量,那么在第一次查询之后,在共享池中就会存在这类查询的执行计划,也就是软解析。

因此,我们可以通过使用绑定变量来减少硬解析,减少 Oracle 的解析工作量。但是这种方式也有缺点,使用动态 SQL 的方式,因为参数不同,会导致 SQL 的执行效率不同,同时 SQL 优化也会比较困难。

Oracle的架构图:

MySQL高级篇

MySQL高级篇

小结:

Oracle 和 MySQL 在进行 SQL 的查询上面有软件实现层面的差异。Oracle 提出了共享池的概念,通过共享池来判断是进行软解析,还是硬解析。

4.3、数据库缓冲池(buffer pool)

InnoDB存储引擎是以页为单位来管理存储空间的,我们进行的增删改查操作其实本质上都是在访问页面(包括读页面、写页面、创建新页面等操作)。而磁盘 I/O 需要消耗的时间很多,而在内存中进行操作,效率则会高很多,为了能让数据表或者索引中的数据随时被我们所用,DBMS 会申请占用内存来作为数据缓冲池,在真正访问页面之前,需要把在磁盘上的页缓存到内存中的Buffer Pool之后才可以访问。

这样做的好处是可以让磁盘活动最小化,从而减少与磁盘直接进行 I/O 的时间。要知道,这种策略对提升 SQL 语句的查询性能来说至关重要。如果索引的数据在缓冲池里,那么访问的成本就会降低很多。

4.3.1、缓冲池vs查询缓存缓冲池(Buffer Pool)

在InnoDB存储引擎中有一部分会放到内存中,缓冲池则占了这部分内存的大部分,他用来存储各种数据的缓存,

MySQL高级篇

从图中,你能看到 InnoDB 缓冲池包括了数据页、索引页、插入缓冲、锁信息、自适应 Hash 和数据字典信息等。

缓存池的重要性:

对于使用InnoDB 作为存储引擎的表来说,不管是用于存储用户数据的索引(包括聚簇索引和二级索引),还是各种系统数据,都是以 页 的形式存放在 表空间中的,而所谓的表空间只不过是InnoDB对文件系统上一个或几个实际文件的抽象,也就是说我们的数据说到底还是存储在磁盘上的。但是各位也都知道,磁盘的速度慢跟乌龟一样,怎么能配得上“快如风,疾如电"的CPU呢?这里,缓冲池可以帮助我们消除CPU和磁盘之间的鸿沟。所以InnoDB存储引擎在处理客户端的请求时,当需要访问某个页的数据时,就会把完整的页的数据全部加载到内存中,也就是说即使我们只需要访问一个页的一条记录,那也需要先把整个页的数据加载到内存中。将整个页加载到内存中后就可以进行读写访问了,在进行完读写访问之后并不着急把该页对应的内存空间释放掉,而是将其缓存起来,这样将来有请求再次访问该页面时,就可以省去磁盘IO的开销了。

缓存原则:

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

首先,位置决定效率,提供缓冲池就是为了在内存中可以直接访问数据。

其次,频次决定优先级顺序。因为缓冲池的大小是有限的,比如磁盘有 200G,但是内存只有 16G,缓冲池大小只有 1G,就无法将所有数据都加载到缓冲池里,这时就涉及到优先级顺序,会优先对使用频次高的热数据进行加载。

缓冲池的预读特性:

缓冲池的作用就是为了提升I/O效率,而我们进行读取数据的时候存在一个“局部性原理”,也就是说我们使用了一些数据,大概率还会使用它周围的一些数据,因此采用“预读”的机制提前加载,可以减少未来可能的磁盘I/O操作。

查询缓存

查询缓存是提前把查询结果缓存起来,这样下次不需要执行就可以直接拿到结果。需要说明的是,在MySQL 中的查询缓存,不是缓存查询计划,而是查询对应的结果。因为命中条件苛刻,而且只要数据表发生变化,查询缓存就会失效,因此命中率低。

4.3.2、缓冲池如何读取数据

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

MySQL高级篇

如果我们执行 SQL 语句的时候更新了缓存池中的数据,那么这些数据会马上同步到磁盘上吗?

4.3.3、查看/设置缓冲池的大小

如果你使用的是MySQL MyISAM存储引擎,它只缓存索引,不缓存数据,对应的键缓存参数为key_buffer_size,你可以用它进行查看。

如果你使用的是InnoDB存储引擎,可以通过查看innodb_buffer_poll_size变量来查看缓冲池的大小

show variables like 'innodb_buffer_pool_size';

设置缓冲池的大小

set global innodb_buffer_pool_size = 268435456;

或者

[server] innodb_buffer_pool_size = 2684354564.3.4、多个Buffer Pool实例

Buffer Pool 本质是InnoDB向操作系统申请的一块连续的内存空间,在多线程环境下,访问Buffer Pool中的数据都需要 加锁 处理。在Buffer Pool 特别大而且多线程并发访问特别高的情况下,单一的Buffer Pool 可能会影响请求的处理速度。所以在Buffer Pool特别大的时候,我们可以把他们 差分成若干个小的Buffer Pool,每个Buffer Pool都称为一个 实例,他们都是独立的,独立的去申请内存空间,独立的管理各种链表。所以在多线程并发访问时并不会相互影响,从而提高并发处理能力。

# 我们可以在服务器启动的时候通过设置innodb_buffer_pool_instances的值来修改 Buffer Pool实例的个数[server] innodb_buffer_pool_instances = 2

何查看缓冲池的个数

show variables like 'innodb_buffer_pool_instances';

每个Buffer Pool实例实际占内存空间

innodb_buffer_pool_size/innodb_buffer_pool_instances

也就是总共的大小除以实例的个数,结果就是每个Buffer Pool实例占用的大小。

不过也不是说Buffer Pool实例创建的越多越好,分别管理各个Buffer Pool也是需要性能开销的,InnoDB规定:innodb_buffer_pool_size的值小于1G的时候设置多个实例是无效的L,InnoDB会默认把innodb_buffer_pool_instances的值修改为1。而我们鼓励在Buffer Pool大于或等于1G的时候设置多个Buffer Pool实例。

4.3.5、引申问题

Buffer Pool是MySQL内存结构中十分核心的一个组成,你可以先把它想象成一个黑盒子。

黑盒下的更新数据流程:

当我们查询数据的时候,会先去Buffer Pool中查询。如果Buffer Pool中不存在,存储引擎会先将数据从磁盘加载到Buffer Pool中,然后将数据返回给客户端;同理,当我们更新某个数据的时候,如果这个数据不存在于BufferPool,同样会先数据加载进来,然后修改修改内存的数据。被修改过的数据会在之后统一刷入磁盘。

MySQL高级篇

这个过程看似没啥问题,实则是有问题的。假设我们修改Buffer Pool中的数据成功,但是还没来得及将数据刷入磁盘MySQL就挂了怎么办?按照上图的逻辑,此时更新之后的数据只存在于Buffer Pool中,如果此时MySQL宕机了,这部分数据将会永久地丢失;

我更新到一半突然发生错误了,想要回滚到更新之前的版本,该怎么办?连数据持久化的保证、事务回滚都做不到还谈什么崩溃恢复?

答案:Redo Log & Undo Log

五、存储引擎

为了管理方便 ,人们把连接管理、查询缓存、语法解析、查询优化这些并不涉及真实数据存储的功能划分为MySQL server的功能,把真是存取数据的功能划分为 存储引擎 的功能。所以在Mysql server完成了查询优化后,只需按照生成的执行计划调用底层存储引擎提供的API,获取到数据后返回给客户端就好了。

MySQL中提到了存储引擎的概念。简而言之,存储引擎就是指表的类型。其实存储引擎以前叫作表处理器,后来改名为 存储疫情,它的功能就是接受上层传下来的指令,然后对表中的数据进行提取或写入操作。

5.1、查看存储引擎show engines;# 或show engines\G

MySQL高级篇

查询结果显示,MySQL 8支持9种存储引擎,分别为MEMORY、MRG_MYISAM、CSV、FEDERATED、PERFORMANCE_SCHEMA、MyISAM、InnoDB、BLACKHOLE 和ARCHIVE。

Engine参数:表示存储引擎名称。Support参数:表示MySQL数据库管理系统是否支持该存储引擎: YES表示支持,NO表示不支持。DEFAULT:表示系统默认支持的存储引擎。Comment参数:表示对存储引擎的评论。Transactions参数:表示存储引擎是否支持事务:YES表示支持,NO表示不支持。XA参数:表示存储引擎所支持的分布式是否符合XA规范: YES表示支持,NO表示不支持。代表着该存储引擎是否支持分布式事务。Savepoints参数:表示存储引擎是否支持事务处理的保存点: VES表示支持,NO表示不支持。也就是说,该存储引擎是否支持部分事务回滚。5.2、设置系统默认的存储引擎查看默认的存储引擎:show variables like '%storage_engine%'; #或SELECT @@default_storage_engine;

MySQL高级篇

MySQL高级篇

修改默认的存储引擎

如果在创建表的语句中没有显式指定表的存储引擎的话,那就会默认使用InnoDB作为表的存储引擎。

SET DEFAULT_STORAGE_ENGINE=MyISAM;

或者修改my.cnf文件:

default-storage-engine=MyISAM # 重启服务 systemctl restart mysqld.service5.3、设置表的存储引擎

存储引擎是负责对表中的数据进行提取和写入工作的,我们可以为不同的表设置不同的存储引擎,也就是说不同的表可以有不同的物理存储结构,不同的提取和写入方式。

5.3.1、创建表时指定存储引擎

CREATE TABLE 表名(建表语句; ) ENGINE = 存储引擎名称;

5.3.2、修改表的存储引擎

ALTER TABLE 表名 ENGINE = 存储引擎名称;5.4、引擎介绍5.4.1、InnoDB 引擎:具备外键支持功能的事务存储引擎MySQL从3.23.34a开始就包含InnoDB存储引擎。大于等于5.5之后,默认采用InnoDB引擎。InnoDB是MySQL的默认事务型引擎,它被设计用来处理大量的短期(short-lived)事务。可以确保事务的完整提交(Commit)和回滚(Rollback)。除了增加和查询外,还需要更新、删除操作,那么,应优先选择InnoDB存储引擎。除非有非常特别的原因需要使用其他的存储引擎,否则应该优先考虑InnoDB引擎。数据文件结构:表名.frm 存储表结构(MySQL8.0时,合并在表名.ibd中)表名.ibd 存储数据和索引InnoDB是为处理巨大数据量的最大性能设计。在以前的版本中,字典数据以元数据文件、非事务表等来存储。现在这些元数据文件被删除了。比如:.frm,.par,.trn,.isl,.db.opt等都在MySQL8.0中不存在了。对比MyISAM的存储引擎,InnoDB写的处理效率差一些,并且会占用更多的磁盘空间以保存数据和索引。MyISAM只缓存索引,不缓存真实数据;InnoDB不仅缓存索引还要缓存真实数据,对内存要求较高,而且内存大小对性能有决定性的影响。5.4.2、MyISAM引擎:主要的非事务处理存储引擎MyISAM提供了大量的特性,包括全文索引、压缩、空间函数(GIS)等,但MyISAM不支持事务、行级锁、外键,有一个毫无疑问的缺陷就是崩溃后无法安全恢复。5.5之前默认的存储引擎优势是访问的速度快,对事务完整性没有要求或者以SELECT、INSERT为主的应用针对数据统计有额外的常数存储。故而 count(*) 的查询效率很高数据文件结构:表名.frm 存储表结构表名.MYD 存储数据 (MYData)表名.MYI 存储索引 (MYIndex)应用场景:只读应用或者以读为主的业务5.4.3、Archive 引擎:用于数据存档archive是归档的意思,仅仅支持插入和查询两种功能(行被插入后不能再修改)。在MySQL5.5以后支持索引功能。拥有很好的压缩机制,使用zlib压缩库,在记录请求的时候实时的进行压缩,经常被用来作为仓库使用。创建ARCHIVE表时,存储引擎会创建名称以表名开头的文件。数据文件的扩展名为.ARZ。根据英文的测试结论来看,同样数据量下,Archive表比MyISAM表要小大约75%,比支持事务处理的InnoDB表小大约83%。ARCHIVE存储引擎采用了行级锁。该ARCHIVE引擎支持 AUTO_INCRENENT列属性。AUTO_INCREMENT列可以具有唯一索引或非唯一索引。尝试在任何其他列上创建索引会导致错误。Archive表适合日志和数据采集(档案)类应用;适合存储大量的独立的作为历史记录的数据。拥有很高的插入速度,但是对查询的支持较差。下表展示了ARCHIVE存储引擎功能特征支持B树索引不支持备份/时间点恢复(在服务器中实现,而不是在存储引擎中)支持集群数据库支持不支持聚集索引不支持压缩数据支持数据缓存不支持加密数据(加密功能在服务器中实现)支持外键支持不支持全文检索索引不支持地理空间数据类型支持支持哈希索引不支持索引缓存不支持锁粒度行锁MVCC不支持存储限制没有任何限制交易不支持更新数据字典的统计信息支持5.4.4、Blackhole 引擎:丢弃写操作,读操作会返回空内容Blackhole引擎没有实现任何存储机制Ⅰ它会丢弃所有插入的数据,不做任何保存。但服务器会记录Blackhole表的日志,所以可以用于复制数据到备库,或者简单地记录到日志。但这种应用方式会碰到很多问题,因此并不推荐。5.4.5、CSV 引擎:存储数据时,以逗号分隔各个数据项CSV引擎可以将普通的CSV文件作为MySQL的表来处理,但不支持索引。CSv引擎可以作为一种数据交换的机制,非常有用。CSv存储的数据直接可以在操作系统里,用文本编辑器,或者excel读取对于数据的快速导入、导出是有明显优势的。

创建CSV表时,服务器会创建一个纯文本数据文件,其名称以表名开头并带有.cSV扩展名。当你将数据存储到表中时,存储引擎将其以逗号分隔值格式保存到数据文件中。

使用案例如下

mysql> CREATE TABLE test (i INT NOT NULL,c CHAR(10)NOT NULL)ENGINE = CSv;Query 0K,0 rows affected (0.06 sec)mysql> INSERT INTO test VALUES( 1 , ' record one ' ),(2,'record two ' ) ;Query oK,2 rows affected (0.05 sec)Records : 2 Duplicates: 0warnings : 0mysql> SELECT * FROM test ;+---+-----------+| i | c |+---+-----------+| 1 | record one|| 2 | record two|+---+-----------+2rows in set (0.00 sec)

创建CSV表还会创建相应的元文件,用于存储表的状态和表中存在的行数了。此文件的名称与表的名称相同,后缀为CSM。

MySQL高级篇

打开如下:

"1", "record one""2", "record two"5.4.6、Memory 引擎:置于内存的表

Memory采用的逻辑介质是内存,响应速度很快,但是当mysqld守护进程崩溃的时候数据会丢失。另外,要求存储的数据是数据长度不变的格式,比如,Blob和Text类型的数据不可用(长度不固定的)。

主要特征:

Memory同时支持哈希(HASH)索引和B+树索引。哈希索引相等的比较快,但是对于范围的比较慢很多。默认使用哈希(HASH)索引,其速度要比使用B型树(BTREE)索引快如果希望使用B树索引,可以在创建索引时选择使用。Memory表至少比MyISAM表要快一个数量级。MEMORY表的大小是受到限制的。表的大小主要取决于两个参数,分别是max_rows和max_heap_table_size。其中,max_rows可以在创建表时指定; max_heap_table_size的大小默认为16MB,可以按需要进行扩大。数据文件与索引文件分开存储。每个基于MEMORY存储引擎的表实际对应一个磁盘文件,该文件的文件名与表名相同,类型为frm类型,该文件中只存储表的结构,而其数据文件都是存储在内存中的。这样有利于数据的快速处理,提供整个表的处理效率。缺点:其数据易丢失,生命周期短。基于这个缺陷,选择MEMORY存储引擎时需要特别小心。

使用Memory存储引擎的场景:

目标数据比较小,而且非常频繁的进行访问,在内存中存放数据,如果太大的数据会造成内存溢出。可以通过参数max_heap_table_size控制Memory表的大小,限制Memory表的最大的大小。如果数据是临时的,而且必须立即可用得到,那么就可以放在内存中。存储在Memory表中的数据如果突然间丢失的话也没有太大的关系。5.4.7、Federated 引擎:访问远程表Federated引擎是访问其他MysQL服务器的一个代理,尽管该引擎看起来提供了一种很好的跨服务器的灵活性,但也经常带来问题,因此默认是禁用的。5.4.8、Merge引擎:管理多个MyISAM表构成的表集合5.4.9、NDB引擎:MySQL集群专用存储引擎

也叫做NDB Cluster存储引擎,主要用于MysQL cluster 分布式集群环境,类似于Oracle的 RAC集群。

5.5、MyISAM和InnoDB

MySQL5.5之前的默认存储引擎是MylSAM,5.5之后改为了InnoDB。

首先对于InnoDB存储引擎,提供了良好的事务管理、崩溃修复能力和并发控制。因为InnoDB存储引擎支持事务所以对于要求事务完整性的场合需要选择lnnoDB,比如数据操作除了插入和查询以外还包含有很多更新、删除操作,像财务系统等对数据准确性要求较高的系统。缺点是其读写效率稍差,占用的数据空间相对比较大。

其次对于MylSAM存储引擎,如果是小型应用,系统以读操作和插入操作为主,只有很少的更新、删除操作,并且对事务的要求没有那么高,则可以选择这个存储引擎。MylISAM存储引擎的优势在于占用空间小,处理速度快﹔缺点是不支持事务的完整性和并发性。

这两种引擎各有特点,当然你也可以在MysQL中,针对不同的数据表,可以选择不同的存储引擎。

对比项MyISAMInnoDB外键不支持支持事务不支持支持行表锁表锁,即使操作一条记录也会锁住整个表,不适合高并发的操作行锁,操作时只锁某一行,不对其它行有影响,适合高并发的操作缓存只缓存索引,不缓存真实数据不仅缓存索引还要缓存真实数据,对内存要求较高,而且内存大小对性能有决定性的影响自带系统表使用YN关注点性能:节省资源、消耗少、简单业务事务:并发写、事务、更大资源默认安装YY默认使用NY六、索引的数据结构6.1、为什么使用索引

索引是存储引擎用于快速找到数据记录的一种数据结构,就好比一本教课书的目录部分,通过目录中找到对应文章的页码,便可快速定位到需要的文章。MySQL中也是一样的道理,进行数据查找时,首先查看查询条件是否命中某条索引,符合则通过索引查找相关数据,如果不符合则需要全表扫描,即需要一条一条地查找记录,直到找到与条件符合的记录。

MySQL高级篇

如上图所示,数据库没有索引的情况下,数据分布在硬盘不同的位置上面,读取数据时,摆臂需要前后摆动查找数据,这样操作非常消耗时间。如果数据顺序摆放,那么也需要从1到6行按顺序读取,这样就相当于进行了6次I0操作,依旧非常耗时。如果我们不借助任何索引结构帮助我们快速定位数据的话,我们查找Col2=89这条记录,就要逐行去查找、去比较。从Col 2=34开始,进行比较,发现不是,继续下一行。我们当前的表只有不到10行数据,但如果表很大的话,有上千万条数据,就意味着要做很多很多次磁盘I/0才能找到。现在要查找Col 2=89这条记录。CPU必须先去磁盘查找这条记录,找到之后加载到内存,再对数据进行处理。这个过程最耗时间的就是磁盘I/O(涉及到磁盘的旋转时间(速度快)),磁头的寻道时间(速度慢-费时)

假如给数据使用二叉树这样的数据结构进行存储

MySQL高级篇

对字段Col2添加了索引,就相当于在硬盘上为Col 2维护了一个索引的数据结构,即这个二叉搜索树。二叉搜索树的每个结点存储的是(K,V)结构,key是Col 2,value是该key所在行的文件指针(地址)。比如:该二叉搜索树的根节点就是:(34,0x07)。现在对Col 2添加了索引,这时再去查找Col 2=89这条记录的时候会先去查找该二叉搜索树〈二叉树的遍历查找)。读34到内存,89>34;继续右侧数据,读89到内存,89 == 89;找到数据返回。找到之后就根据当前结点的value快速定位到要查找的记录对应的地址。我们可以发现,只需要查找两次就可以定位到记录的地址,查询速度就提高了。

这就是我们为什么要建索引,目的就是为了减少磁盘I/O的次数,加快查询速率。

6.2、索引及其优缺点6.2.1 、索引概述

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

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

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

6.2.2、优点类似大学图书馆建书目索引,提高数据检索的效率,降低数据库的IO成本,这也是创建索引最主要的原因。通过创建唯一索引,可以保证数据库表中每一行数据的唯一性。在实现数据的参考完整性方面,可以加速表和表之间的连接。换句话说,对于有依赖关系的子表和父表联合查询时,可以提高查询速度。在使用分组和排序子句进行数据查询时,可以显著减少查询中分组和排序的时间,降低了CPU的消耗。6.2.3、缺点创建索引和维护索引要耗费时间,并且随着数据量的增加,所耗费的时间也会增加。索引需要占磁盘空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间存储在磁盘上,如果有大量的索引,索引文件就可能比数据文件更快达到最大文件尺寸。虽然索引大大提高了查询速度,同时却会降低更新表的速度。当对表中的数据进行增加、删除和修改的时候,索引也要动态地维护,这样就降低了数据的维护速度。

提示:索引可以提高查询的速度,但是会影响插入记录的速度。这种情况下,最好的办法是先删除表中的索引,然后插入数据,插入完成后在创建索引。

6.3、InnoDB中索引的推演6.3.1、索引之前的查找SELECT [列名列表] FROM 表名 WHERE 列名 = xxx;

1.在一个页中的查找

假设目前表中的记录比较少,所有的记录都可以被存放在一个页中,在查找记录的时候可以根据搜索条件的不同分为两种情况:

以主键为搜索条件可以在页目录中使用二分法快速定位到对应的槽,然后再遍历该槽对应分组中的记录即可快速找到指定的记录。以其他列作为搜索条件因为在数据页中并没有对非主键建立所谓的页目录,所以我们无法通过二分法快速定位相应的槽。这种情况下只能从最小记录开始依次遍历单链表中的每条记录,然后对比每条记录是不是符合搜索条件。很显然,这种查找的效率是非常低的。

2.在很多页中查找

大部分情况下我们表中存放的记录都是非常多的,需要好多的数据页来存储这些记录。在很多页中查找记录的话可以分为两个步骤:

定位到记录所在的页。从所在的页内查找相应的记录。

在没有索引的情况下,不论是根据主键列或者其他列的值进行查找,由于我们并不能快速的定位到记录所在的页,所以只能从第一个页沿着双向链表一直往下找,在每一个页中根据我们上面的查找方式去查找指定的记录。因为要遍历所有的数据页,所以这种方式显然是超级耗时的。

6.3.2、设计索引# 建一个表mysql> CREATE TABLE index_demo(-> c1 INT,-> c2 INT,-> c3 CHAR(1),-> PRIMARY KEY(c1)-> ) ROW_FORMAT = Compact;

这个新建的index_demo表中有2个INT类型的列,1个CHAR(1)类型的列,而且我们规定了c1列为主键,这个表使用Compact行格式来实际存储记录的。这里我们简化了index_demo表的行格式示意图:

MySQL高级篇

record_type:记录头信息的一项属性,表示记录的类型,0表示普通记录、1表示目录项记录、2表示最小记录、3表示最大记录。next_record:记录头信息的一项属性,表示下一条地址相对于本条记录的地址偏移量,我们用箭头来表明下一条记录是谁。各个列的值:这里只记录在index_demo表中的三个列,分别是c1、c2和c3。其他信息:除了上述3种信息以外的所有信息,包括其他隐藏列的值以及记录的额外信息。

将记录格式示意图的其他信息项暂时去掉并把它竖起来的效果就是这样:

MySQL高级篇

把一些记录放到页里的示意图就是:

MySQL高级篇

6.3.2.1、一个简单的索引设计方案

我们在根据某个搜索条件查找一些记录时为什么要遍历所有的数据页呢?因为各个页中的记录并没有规律,我们并不知道我们的搜索条件匹配哪些页中的记录,所以不得不依次遍历所有的数据页。所以如果我们想快速的定位到需要查找的记录在哪些数据页中该咋办?我们可以为快速定位记录所在的数据页而建立一个目录,建这个目录必须完成下边这些事:

下一个数据页中用户记录的主键值必须大于上一个页中用户记录的主键值。

假设:每个数据页最多能存放3条记录(实际上一个数据页非常大,可以存放下好多记录)。有了这个假设之后我们向index_demo表插入3条记录

mysql> INSERT INTO index_demo VALUES(1,4, 'u'),(3,9,'d'),(5,3,'y');Query OK, 3 rows affected(0.01 sec)Records: 3 Duplicates: 0 Warnings: 0

那么这些记录已经按照主键值的大小串联成一个单向链表了,

MySQL高级篇

从图中可以看出来,index_demo 表中的3条记录都被插入到编号为10的数据页中了。此时我们再来插入一条记录:

mysql> INSERT INTO index_demo VALUES(4,4,'a');

因为页10最多只能放3条记录,所以我们不得不再分配一个新页

MySQL高级篇

注意,新分配的数据页编号可能并不是连续的。它们只是通过维护着上一个页和下一个页的编号而建立了链表关系。另外,页10中用户记录最大的主键值是5,而页28中有一条记录的主键值是4,因为5>4,所以这就不符合下一个数据页中用户记录的主键值必须大于上一个页中用户记录的主键值的要求,所以在插入主键值为4的记录的时候需要伴随着一次记录移动,也就是把主键值为5的记录移动到页28中,然后再把主键值为4的记录插入到页10中,这个过程的示意图如下:

MySQL高级篇

这个过程表明了在对页中的记录进行增删改操作的过程中,我们必须通过一些诸如记录移动的操作来始终保证这个状态一直成立:下一个数据页中用户记录的主键值必须大于上一个页中用户记录的主键值。这个过程我们称为页分裂。

给所有的页建立一个目录项。

由于数据页的编号可能是不连续的,所以在向index_demo表中插入许多条记录后,可能是这样的效果:

MySQL高级篇

因为这些16KB的页在物理存储上是不连续的,所以如果想从这么多页中根据主键值快速定位某些记录所在页,我们需要给它们做个目录,每个页对应一个目录项,每个目录项包括下边两个部分:

页的用户记录中最小的主键值,我们用key来表示。页号,我们用page_no表示。

所有我们为上边几个页做好的目录就像这个样子:

MySQL高级篇

以页28为例,它对应目录项2,这个目录项中包含着该页的页号28以及该页中用户记录的最小主键值5。我们只需要把几个目录项在物理存储器上连续存储(比如:数组),就可以实现根据主键值快速查找某条记录的功能了。比如:查找主键值为20的记录,具体查找过程分两步:

先从目录项中根据二分法快速确定出主键值为20的记录在目录项3中(因为 12 < 20 < 209 ),它对应的页是页9。

再根据前边说的在页中查找记录的方式去页9中定位具体的记录。

至此,针对数据页做的简易目录就搞定了。这个目录有一个别名,称为索引。

6.3.2.2、InnoDB中的索引方案

① 迭代1次:目录项纪录的页

# 上边称为一个简易的索引方案,是因为我们为了在根据主键值进行查找时使用二分法快速定位具体的目录项而假设所有目录项都可以在物理存储器上连续存储,但是这样做有几个问题:- InnoDB是使用页来作为管理存储空间的基本单位,最多能保证16KB的连续存储空间,而随着表中记录数量增多,需要非常大的连续的存储空间才能把所有的目录项都放下,这对记录数量非常多的表是不现实的。- 我们时常会对记录进行增删,假设我们把页28中的记录都删除了,那意味着目录项2也就没有存在的必要了,这就需要把目录项2后的目录项都向前移动一下,这样牵一发而动全身的操作效率很差。

所以,我们需要一种可以灵活管理所有目录项的方式。我们发现目录项其实长得跟我们的用户记录差不多,只不过目录项中的两个列是主键和页号而已,为了和用户记录做一下区分,我们把这些用来表示目录项的记录称为目录项己录。那InnoDB怎么区分一条记录是普通的 用户记录还是目录项记录呢?使用记录头信息里的 record_type 属性,它的各个取值代表的意思如下:

0:普通的用户记录1:目录项记录2:最小记录3:最大记录

把前边使用到的目录项放到数据页中:

MySQL高级篇

从图中可以看出来,我们新分配了一个编号为30的页来专门存储目录项记录。这里再次强调目录项记录和普通的用户记录的 不同点 :

目录项记录的record_type值是1,而普通用户记录的record_type值是0。目录项记录只有主键值和页的编号两个列,而普通的用户记录的列是用户自己定义的,可能包含很多列,另外还有InnoDB自己添加的隐藏列。了解:记录头信息里还有一个叫min_rec_mask的属性,只有在存储目录项记录的页中的主键值最小的目录项记录的min_rec_mask值为1,其他别的记录的min_rec_mask值都是0。

相同点: 两者用的是一样的数据页,都会为主键值生成Page Directory(页目录),从而在按照主键值进行查找时可以使用二分法来加快查询速度。

现在以查找主键为20的记录为例,根据某个主键值去查找记录的步骤就可以大致拆分成下边两步:

先到存储目录项记录的页,也就是页30中通过二分法快速定位到对应目录项,因为 12 < 20 < 209 ,所以定位到对应的记录所在的页就是页9。

再到存储用户记录的页9中根据二分法快速定位到主键值为20的用户记录。

② 迭代2次:多个目录项纪录的页

虽然 目录项记录 中只存储主键值和对应的页号,比用户记录需要的存储空间小多了,但是不论怎么说一个页有16KB大小,能存放的目录项记录也是有限的,如果表中的数据太多,以至于一个数据页不足以存放所有的目录项记录,如何处理呢?

这里假设一个存储目录项记录的页最多只能存放4条目录项记录,如果此时我们再向上图中插入一条主键值为320的用户记录的话,那就需要分配一个新的存储目录项记录的页:

MySQL高级篇

从图中可以看出,我们插入了一条主键值为320的用户记录之后需要两个新的数据页:

为存储该用户记录而新生成了页31。因为原先存储目录项记录的页30的容量已满(我们前边假设只能存储4条目录项记录),所以不得不需要一个新的页32来存放页31对应的目录项。

现在因为存储目录项记录的页不止一个,所以如果我们想根据主键值查找一条用户记录大致需要3个步骤,以查找主键值为20的记录为例:

确定目录项记录页我们现在的存储目录项记录的页有两个,即页30和页32,又因为页30表示的目录项的主键值的范围是 [1, 320) ,页32表示的目录项的主键值不小于 320 ,所以主键值为20的记录对应的目录项记录在页30中。

通过目录项记录页确定用户记录真实所在的页。在一个存储目录项记录的页中通过主键值定位一条目录项记录的方式说过了。

在真实存储用户记录的页中定位到具体的记录。

③ 迭代3次:目录项记录页的目录页

如图,我们生成了一个存储更高级目录项的页33,这个页中的两条记录分别代表页30和页32,如果用户记录的主键值在[1, 320)之间,则到页30中查找更详细的目录项记录,如果主键值不小于320的话,就到页32中查找更详细的目录项记录。

MySQL高级篇

随着表中记录的增加,这个目录的层级会继续增加,如果简化一下,就是下面这种图

MySQL高级篇

这个数据结构,它的名称是B+树。

④ B+Tree

不论是存放用户记录的数据页,还是存放目录项记录的数据页,我们都把它们存放到B+树这个数据结构中了,所以我们也称这些数据页为节点。从图中可以看出,我们的实际用户记录其实都存放在B+树的最底层的节点上,这些节点也被称为叶子节点,其余用来存放目录项的节点称为非叶子节点或者内节点,其中B+树最上边的那个节点也称为根节点。

一个B+树的节点其实可以分成好多层,规定最下边的那层,也就是存放我们用户记录的那层为第0层,之后依次往上加。真实环境中一个页存放的记录数量是非常大的,假设所有存放用户记录的叶子节点代表的数据页可以存放100条用户记录,所有存放目录项记录的内节点代表的数据页可以存放1000条目录项记录,那么:

如果B+树只有1层,也就是只有1个用于存放用户记录的节点,最多能存放100条记录。如果B+树有2层,最多能存放1000×100=10,0000条记录。如果B+树有3层,最多能存放1000×1000×100=1,0000,0000条记录。如果B+树有4层,最多能存放1000×1000×1000×100=1000,0000,0000条记录。相当多的记录!!!

所以一般情况下,我们用到的B+树都不会超过4层,那通过主键值去查找某条记录最多只需要做4个页面内的查找(查找3个目录项页和一个用户记录页),又因为在每个页面内有所谓的Page Directory(页目录),所以在页面内也可以通过二分法实现快速定位记录。

6.3.3、常见索引概念

1、聚簇索引

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

特点:

使用记录主键值的大小进行记录和页的排序,这包括三个方面的含义:

页内的记录是按照主键的大小顺序排成一个单向链表。

各个存放用户记录的页也是根据页中用户记录的主键大小顺序排成一个双向链表。

存放目录项记录的页分为不同的层次,在同一层次中的页也是根据页中目录项记录的主键大小顺序排成一个双向链表。

B+树的叶子节点存储的是完整的用户记录。

所谓完整的用户记录,就是指这个记录中存储了所有列的值(包括隐藏列)。

优点:

数据访问更快,因为聚簇索引将索引和数据保存在同一个B+树中,因此从聚簇索引中获取数据比非聚簇索引更快聚簇索引对于主键的排序查找和范围查找速度非常快按照聚簇索引排列顺序,查询显示一定范围数据的时候,由于数据都是紧密相连,数据库不用从多个数据块中提取数据,所以节省了大量的io操作。

缺点:

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

限制:

对于Mysql数据库目前只有InnoDB数据引擎支持聚簇索引,而MyISAM并不支持聚簇索引。由于数据物理存储排序方式只有一种,所以每个MySQL的 表只能有一个聚簇索引。 一般情况下就是该表的主键。如果没有定义主键,InnoDB会选择 非空的唯一所以 代替。如果没有这样的索引,InnoDB会隐式的定义一个主键来作为聚簇索引。为了充分利用聚簇索引的聚簇的特性,索引InnoDB表的主键列尽量选用有序的顺序id,而不建议用无序的id,比如UUID,MD5,HASH、字符串列作为主键无法保证数据的顺序增长。

2、二级索引(辅助索引、非聚簇索引)

聚簇索引只能在搜索条件为主键值时才能发挥作用,因为B+树中的数据都是按照主键进行排序的。如果想以别的列作为搜索条件该怎么办?

答案:可以多建几棵B+树,不同的B+树中的数据采用不同的排序规则。

MySQL高级篇

这个B+树与聚簇索引有几处不同:

使用记录C2列的大小进行记录和页的排序,这包括三个方面的含义:页内的记录是按照c2列的大小顺序排成一个 单向链表。各个存放 用户记录的页 也是根据页中记录的c2列大小顺序排成一个 双向链表。存放 目录项记录的页 分为不同的层次,在同一层次中的页也是根据页中目录项记录的c2列大小顺序排成一个 双向链表。B+ 树的叶子节点存储的并不是完整的用户记录,而只是c2列 + 主键这两个列的值。目录项记录中不再是 主键+页号 的搭配,而变成了 C2列+页号的搭配。

所有如果想通过c2列的值查找某些记录的话就可以使用刚刚建好的这个B+树。

# 以查找c2列的值为4的记录为例,查找过程如下:1. 确定目录项记录页根据 根页面,也就是页44,可以快速定位到 目录项记录 所在的页为 页42(因为2< 4const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL

SQL性能优化的目标:至少要达到 range级别,要求是ref级别,最好是const级别。(阿里巴巴开发手册要求)

possible_keys和key

在EXPLAIN语句输出的执行计划中, possible_keys列表示在某个查询语句中,对某个表执行单表查询时可能用到的索引有哪些。一般查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询使用。key列表示实际用到的索引有哪些,如果为NULL,则没有使用索引。

key_len(重点)

key_len的长度计算公式:

varchar(10)变长字段且允许NULL = 10 * ( character set: utf8=3,gbk=2,latin1=1)+1(NULL)+2(变长字段) varchar(10)变长字段且不允许NULL = 10 * ( character set:utf8=3,gbk=2,latin1=1)+2(变长字段)char(10)固定字段且允许NULL = 10 * ( character set:utf8=3,gbk=2,latin1=1)+1(NULL) char(10)固定字段且不允许NULL = 10 * ( character set:utf8=3,gbk=2,latin1=1)

ref

显示索引的哪一列被使用了,如果可能的话,是一个常数。哪些列或常量被用于查找索引列上的值。

当使用索引列等值匹配的条件去执行查询时,也就是在访问方法是 const、eq_ref、ref、ref_of_null、unique_subquery、index_subquery其中之一时,ref 列展示的就是与索引列作等值匹配的结构是什么,比如只是一个常数或者是某个列。

rows(重点)

预估的需要读取的记录条数。。。值越小越好

filtered

某个表经过搜索条件过滤后剩余记录条数的百分比

对个单表查询来说,这个filtered列的值没有什么意义,我们更关注连接查询

Extra

Extra列是用来说明一些额外信息的,包含不适合在其他列中显示但十分重要的额外信息。我们可以通过这些额外信息来更准确的理解MySQL到底将如何执行给定的查询语句。

No tables used ——当查询语句没有 ‘from’ 子句时将会提示该额外信息Impossible WHERE ——查询语句的 where 子句永远为 false 时将会提示该额外信息Using where ——当使用全表扫描来执行对给某个表的查询,并且该语句的 where 子句中有针对该表的搜索条件时,会提示该额外信息;当使用索引访问来执行对某个表的查询,并且该语句的 where 子句中有除了该索引包含的列之外的其他搜索条件时,也会提示该额外信息No matching min/max row ——当查询列表处有 MIN 或 MAX 聚合函数,但是并没有符合 WHERE 子句中的搜索条件的记录时Using index ——当查询列表以及搜索条件中只包含某个索引的列,也就是在可以使用覆盖索引的情况下,将提示该信息。比方说下边这个查询中只需要用到 idx_key1 而不需要回表操作。Using index condition ——有些搜索条件中虽然出现了索引列,但却不能使用到索引。称为索引条件下推。Using join buffer ——在连接查询执行过程中,当被驱动表不能有效的利用索引加快访问速度,MySQL一般会为其分配一块名叫 ‘join buffer’ 的内存块来加快查询速度,也就是我们所讲的 ‘基于块的嵌套循环算法’Not exists ——当我们使用左(外)连接时,如果 ‘WHERE’ 子句中包含要求被驱动表的某个列等于 ‘NULL’ 值的搜索条件,而且那个列又是不允许存储 ‘NULL’ 值的,将提示该额外信息。Using intersect(…) ——说明准备使用 Intersect 索引合并的方式执行查询,括号中的 ‘…’ 表示需要进行索引合并的索引名称;Using union(…) ——说明准备使用 Union 索引合并的方式执行查询;Using sort_union(…),说明准备使用 Sort-Union 索引合并的方式执行查询。Zero limit ——当 limit 子句的参数为 0 时,表示压根儿不打算从表中读取任何记录,将提示该额外信息Using filesort ——很多情况下排序操作无法使用到索引,只能在内存中(记录较少的时候)或者磁盘中(记录较多的时候)进行排序,MySQL把这种在内存中或者磁盘上进行排序的方式统称为文件排序。如果某个查询需要文件排序的方式执行查询,将提示该信息Using temporary ——在许多查询执行过程中,MySQL可能会借助临时表来完成一些功能,比如去重、排序之类的,比如我们在执行许多包含 ‘DISTINCT’,‘GROUP BY’, 'UNION’等子句的查询过程中,如果不能有效利用索引来完成查询,MySQL很有可能寻求通过建立内部的临时表来执行查询。如果查询中使用到了内部的临时表,将提示该信息。

小结:

EXPLAIN不考虑各种cacheEXPLAIN不能显示MySQL在执行查询时所作的优化工作EXPLAIN不会告诉你关于触发器、存储过程的信息或用户自定文函数对查询的影响情况部分统计信息是估算的,并非精确值9.7、EXPLAIN的进一步使用9.7.1、EXPLAIN四种输出格式

这里谈谈EXPLAIN的输出格式。EXPLAIN可以输出四种格式:传统格式,JSON格式,TREE格式以及可视化输出。用户可以根据需要选择适用于自己的格式。

传统格式

JSON格式

JSON格式:在EXPLAIN单词和真正的查询语句中间加上FORMAT=JSON。用于查看执行成本cost_info

TREE格式

TREE格式是8.0.16版本之后引入的新格式,主要根据查询的各个部分之间的关系和各部分的执行顺序来描述如何查询。

可视化输出

可视化输出,可以通过MySQL Workbench可视化查看MySQL的执行计划。

9.7.2、SHOW WARNINGS的使用mysql> EXPLAIN SELECT s1.key1, s2.key1 FROM s1 LEFT JOIN s2 ON s1.key1 = s2.key1 WHERE s2.common_field IS NOT NULL;# 查看优化后的执行语句mysql> SHOW WARNINGS\G9.8、分析优化器执行计划:trace# 开启SET optimizer_trace="enabled=on",end_markers_in_json=on; # 设置大小set optimizer_trace_max_mem_size=1000000;# 使用select * from student where id 0 or tmp_disk_tables >0 order by (tmp_tables+tmp_disk_tables) desc;

IO相关

#1. 查看消耗磁盘IO的文件 select file,avg_read,avg_write,avg_read+avg_write as avg_iofrom sys.io_global_by_file_by_bytes order by avg_read limit 10;

Innodb、相关

#1. 行锁阻塞情况 select * from sys.innodb_lock_waits;第10章 索引优化与查询优化1. 索引失效案例

MySQL中提高性能的一个最有效的方式是对数据表设计合理的索引。索引提供了访问高效数据的方法,并且加快查询的速度,因此索引对查询的速度有着至关重要的影响。

使用索引可以快速地定位表中的某条记录,从而提高数据库查询的速度,提高数据库的性能。如果查询时没有使用索引,查询语句就会扫描表中的所有记录。在数据量大的情况下,这样查询的速度会很慢。

大多数情况下都(默认)采用B+树来构建索引。只是空间列类型的索引使用R-树,并且MEMORY表还支持hash索引。

其实,用不用索引,最终都是优化器说了算。优化器是基于什么的优化器?基于cost开销(CostBaseOptimizer),它不是基于规则(Rule-BasedOptimizer),也不是基于语义。怎么样开销小就怎么来。另外,**SQL语句是否使用索引,跟数据库版本、数据量、数据选择度都有关系。

1.1、全值匹配我最爱1.2、最佳左前缀法则

在MySQL建立联合索引时会遵守最佳左前缀匹配原则,即最左优先,在检索数据时从联合索引的最左边开始匹配。

结论:MySQL可以为多个字段创建索引,一个索引可以包括16个字段。对于多列索引, 过滤条件要使用索引必须按照索引建立时的顺序,依次满足,一旦跳过某个字段,索引后面的字段都无法被使用。 如果查询条件中没有使用这些字段中第1个字段时,多列(或联合)索引不会被使用。

1.3、主键插入顺序

对于一个使用InnoDB存储引擎的表来说,在我们没有显示的创建索引时,表中的数据实际上都是存储在聚簇索引的叶子节点的。而记录又存储在数据页中的,数据页和记录又是按照记录主键值从小到大的顺序进行排序,所以如果我们插入的记录的主键值是依次增大的话,那我们每插满一个数据页就换到下一个数据页继续插,而如果我们插入的主键值忽小忽大的话,则可能会造成页面分裂和记录移位。

1.4、计算、函数、类型转换(自动或手动)导致索引失效1.5、类型转换导致索引失效1.6、范围条件右边的列索引失效

应用开发中范围查询,例如:金额查询,日期查询往往都是范围查询。应将查询条件放置where语句最后。(创建的联合索引中,务必把范围涉及到的字段写在最后)

1.7、不等于(!= 或者)索引失效1.8 is null可以使用索引,is not null无法使用索引

结论:最好在设计数据表的时候就将字段设置为 NOT NULL 约束,比如你可以将INT类型的字段,默认值设置为0。将字符类型的默认值设置为空字符串(‘’)

拓展:同理,在查询中使用not like也无法使用索引,导致全表扫描

1.9 like以通配符%开头索引失效

拓展:Alibaba《Java开发手册》

【强制】页面搜索严禁左模糊或者全模糊,如果需要请走搜索引擎来解决。

1.10 OR、前后存在非索引的列,索引失效

在WHERE子句中,如果在OR前的条件列进行了索引,而在OR后的条件列没有进行索引,那么索引会失效。也就是说,**OR前后的两个条件中的列都是索引时,查询中才使用索引。

1.11、数据库和表的字符集统一使用utf8mb4**

统一使用utf8mb4( 5.5.3版本以上支持)兼容性更好,统一字符集可以避免由于字符集转换产生的乱码。不同的字符集进行比较前需要进行转换会造成索引失效。

2. 关联查询优化

结论1:对于内连接来说,查询优化器可以决定谁来作为驱动表,谁作为被驱动表出现

结论2:对于内连接来讲,如果表的连接条件中只能有一个字段有索引,则有索引的字段所在的表会被作为被驱动表

结论3:对于内连接来说,在两个表的连接条件都存在索引的情况下,会选择小表作为驱动表。小表驱动大表

2.1 Index Nested-Loop Join(索引嵌套循环连接)

Index Nested-Loop Join其优化的思路主要是为了减少内层表数据的匹配次数,所以要求被驱动表上必须有索引才行。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cr395amv-1661608724710)(https://cdn.jsdelivr.net/gh/aoshihuankong/cloudimg@master/img/202204011826671.png)]

2.2 Block Nested-Loop Join(块嵌套循环连接)

如果存在索引,那么会使用index的方式进行join,如果join的列没有索引,被驱动表要扫描的次数太多了。每次访问被驱动表,其表中的记录都会被加载到内存中,然后再从驱动表中取一条与其匹配,匹配结束后清除内存,然后再从驱动表中加载一条记录,然后把被驱动表的记录再加载到内存匹配,这样周而复始,大大增加了IO的次数。为了减少被驱动表的IO次数,就出现了Block Nested-Loop Join的方式。

不再是逐条获取驱动表的数据,而是一块一块的获取,引入了join buffer缓冲区,将驱动表join相关的部分数据列(大小受join buffer的限制)缓存到join buffer中,然后全表扫描被驱动表,被驱动表的每一条记录一次性和join buffer中的所有驱动表记录进行匹配(内存中操作),将简单嵌套循环中的多次比较合并成一次,降低了被驱动表的访问频率。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FCIhBdvX-1661608724711)(https://cdn.jsdelivr.net/gh/aoshihuankong/cloudimg@master/img/202204011833000.png)]

2.3 Hash Join

从MySQL的8.0.20版本开始将废弃BNLJ,因为从MySQL8.0.18版本开始就加入了hash join默认都会使用hash join**

Nested Loop:对于被连接的数据子集较小的情况下,Nested Loop是个较好的选择。Hash Join是做大数据集连接时的常用方式,优化器使用两个表中较小(相对较小)的表利用Join Key在内存中建立散列值,然后扫描较大的表并探测散列值,找出与Hash表匹配的行。这种方式适用于较小的表完全可以放入内存中的情况,这样总成本就是访问两个表的成本之和。在表很大的情况下并不能完全放入内存,这时优化器会将它分割成若干不同的分区,不能放入内存的部分就把该分区写入磁盘的临时段,此时要求有较大的临时段从而尽量提高I/O的性能。它能够很好的工作于没有索引的大表和并行查询的环境中,并提供最好的性能。Hash Join只能应用于等值连接,这是由Hash的特点决定的。3. 子查询优化

子查询是 **MySQL、的一项重要的功能,可以帮助我们通过一个 **SQL、语句实现比较复杂的查询。但是,子查询的执行效率不高。 原因:

① 执行子查询时,MySQL需要为内层查询语句的查询结果建立一个临时表,然后外层查询语句从临时表中查询记录。查询完毕后,再撤销这些临时表。这样会消耗过多的CPU和IO资源,产生大量的慢查询。

② 子查询的结果集存储的临时表,不论是内存临时表还是磁盘临时表都不会存在索引,所以查询性能会受到一定的影响。

③ 对于返回结果集比较大的子查询,其对查询性能的影响也就越大。

在MySQL中,可以使用连接(JOIN)查询来替代子查询。 连接查询不需要建立临时表,其速度比子查询要快,如果查询中使用索引的话,性能就会更好。

结论:尽量不要使用NOT IN 或者 NOT EXISTS,用LEFT JOIN xxx ON xx WHERE xx IS NULL替代

4. 排序优化

SQL 中,可以在 WHERE 子句和 ORDER BY 子句中使用索引,目的是在 WHERE 子句中 避免全表扫描,在 ORDER BY 子句避免使用 FileSort 排序。当然,某些情况下全表扫描,或者 FileSort 排序不一定比索引慢。但总的来说,我们还是要避免,以提高查询效率。

尽量使用 Index 完成 ORDER BY 排序。如果 WHERE 和 ORDER BY 后面是相同的列就使用单索引列;如果不同就使用联合索引。

无法使用 Index 时,需要对 FileSort 方式进行调优。

5. GROUP BY优化group by 使用索引的原则几乎跟order by一致 ,group by 即使没有过滤条件用到索引,也可以直接使用索引。group by 先排序再分组,遵照索引建的最佳左前缀法则当无法使用索引列,可以增大max_length_for_sort_data和sort_buffer_size参数的设置where效率高于having,能写在where限定的条件就不要写在having中了减少使用order by,和业务沟通能不排序就不排序,或将排序放到程序端去做。Order by、group by、distinct这些语句较为耗费CPU,数据库的CPU资源是极其宝贵的。包含了order by、group by、distinct这些查询的语句,where条件过滤出来的结果集请保持在1000行以内,否则SQL会很慢。6. 优化分页查询

优化思路一

在索引上完成排序分页操作,最后根据主键关联回原表查询所需要的其他列内容。

EXPLAIN SELECT * FROM student t,(SELECT id FROM student ORDER BY id LIMIT 2000000,10) aWHERE t.id = a.id;

优化思路二

该方案适用于主键自增的表,可以把Limit 查询转换成某个位置的查询。

EXPLAIN SELECT * FROM student WHERE id > 2000000 LIMIT 10;7. 优先考虑覆盖索引7.1、什么是覆盖索引?

理解方式一 :索引是高效找到行的一个方法,但是一般数据库也能使用索引找到一个列的数据,因此它不必读取整个行。毕竟索引叶子节点存储了它们索引的数据;当能通过读取索引就可以得到想要的数据,那就不需要读取行了。 一个索引包含了满足查询结果的数据就叫做覆盖索引。

理解方式二 :非聚簇复合索引的一种形式,它包括在查询里的SELECT、JOIN和WHERE子句用到的所有列(即建索引的字段正好是覆盖查询条件中所涉及的字段)。

简单说就是,索引列+主键包含SELECT 到 FROM之间查询的列。

7.2、覆盖索引的利弊

好处:

避免Innodb表进行索引的二次查询(回表)

可以把随机IO变成顺序IO加快查询效率

弊端:

索引字段的维护总是有代价的。因此,在建立冗余索引来支持覆盖索引时就需要权衡考虑了。这是业务DBA,或者称为业务数据架构师的工作。

8. 索引条件下推8.1、使用前后的扫描过程

在不使用ICP索引扫描的过程:

storage层:只将满足index key条件的索引记录对应的整行记录取出,返回给server层

server 层:对返回的数据,使用后面的where条件过滤,直至返回最后一行。

使用ICP扫描的过程:

storage层:首先将index key条件满足的索引记录区间确定,然后在索引上使用index filter进行过滤。将满足的index filter条件的索引记录才去回表取出整行记录返回server层。不满足index filter条件的索引记录丢弃,不回表、也不会返回server层。

server 层:对返回的数据,使用table filter条件做最后的过滤。

9. 其它查询优化策略9.1 EXISTS、和 **IN、的区分

索引是个前提,其实选择与否还会要看表的大小。你可以将选择的标准理解为小表驱动大表。

9.2 COUNT(*)与COUNT(具体字段)效率

环节1: COUNT(*)和COUNT(1)都是对所有结果进行COUNT,COUNT(*)和COUNT(1)本质上并没有区别(二者执行时间可能略有差别,不过你还是可以把它俩的执行效率看成是相等的)。如果有WHERE子句,则是对所有符合筛选条件的数据行进行统计;如果没有WHERE子句,则是对数据表的数据行数进行统计。

环节2: 如果是MyISAM存储引擎,统计数据表的行数只需要O(1)的复杂度,这是因为每张MyISAM的数据表都有一个meta信息存储了row_count值,而一致性则是由表级锁来保证的。

如果是InnoDB存储引擎,因为InnoDB支持事务,采用行级锁和MVCC机制,所以无法像MyISAM一样,维护一个row_count变量,因此需要采用扫描全表,是O(n)的复杂度,进行循环+计数的方式来完成统计。

环节3: 在InnoDB引擎中,如果采用COUNT(具体字段)来统计数据行数,要尽量采用二级索引。因为主键采用的索引是聚簇索引,聚簇索引包含的信息多,明显会大于二级索引(非聚簇索引)。对于COUNT(*)和COUNT(1)来说,它们不需要查找具体的行,只是统计行数,系统会自动采用占用空间更小的二级索引来进行统计。

如果有多个二级索引,会使用key_len小的二级索引进行扫描。当没有二级索引的时候,才会采用主键索引来进行统计。

9.3、关于SELECT(*)

在表查询中,建议明确字段,不要使用 * 作为查询的字段列表,推荐使用SELECT查询。原因:

① MySQL 在解析的过程中,会通过查询数据字典将"*"按序转换成所有列名,这会大大的耗费资源和时间。

② 无法使用覆盖索引

9.4 LIMIT 1、对优化的影响

针对的是会扫描全表的 SQL 语句,如果你可以确定结果集只有一条,那么加上LIMIT 1的时候,当找到一条结果的时候就不会继续扫描了,这样会加快查询速度。

如果数据表已经对字段建立了唯一索引,那么可以通过索引进行查询,不会全表扫描的话,就不需要加上LIMIT 1了。

9.5、多使用COMMIT**

只要有可能,在程序中尽量多使用 COMMIT,这样程序的性能得到提高,需求也会因为 COMMIT 所释放的资源而减少。

COMMIT 所释放的资源:

回滚段上用于恢复数据的信息

被程序语句获得的锁

redo / undo log buffer 中的空间

管理上述 3 种资源中的内部花费

第11章 数据库的设计规范1. 范 式1.1、范式简介

在关系型数据库中,关于数据表设计的基本原则、规则就称为范式。 可以理解为,一张数据表的设计结构需要满足的某种设计标准的级别。要想设计一个结构合理的关系型数据库,必须满足一定的范式。

1.2、范式都包括哪些

目前关系型数据库有六种常见范式,按照范式级别,从低到高分别是: 第一范式(1NF)、第二范式(2NF)、第三范式(3NF)、巴斯-科德范式(BCNF)、第四范式(4NF)和第五范式(5NF,又称完美范式) 。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kRGwPnEO-1661608724713)(https://cdn.jsdelivr.net/gh/aoshihuankong/cloudimg@master/img/202204030928295.png)]

1.3、键和相关属性的概念

这里有两个表:

球员表(player):球员编号 | 姓名 | 身份证号 | 年龄 | 球队编号

球队表(team):球队编号 | 主教练 | 球队所在地

超键:对于球员表来说,超键就是包括球员编号或者身份证号的任意组合,比如(球员编号)(球员编号,姓名)(身份证号,年龄)等。候选键:就是最小的超键,对于球员表来说,候选键就是(球员编号)或者(身份证号)。主键:我们自己选定,也就是从候选键中选择一个,比如(球员编号)。外键:球员表中的球队编号。主属性、非主属性:在球员表中,主属性是(球员编号)(身份证号),其他的属性(姓名)(年龄)(球队编号)都是非主属性。1.4、第一范式(1st NF)

第一范式主要是确保数据表中每个字段的值必须具有原子性,也就是说数据表中每个字段的值为不可再次拆分的最小数据单位。

1.5、第二范式(2nd NF)

第二范式要求,在满足第一范式的基础上,还要 满足数据表里的每一条数据记录,都是可唯一标识的。而且所有非主键字段,都必须完全依赖主键,不能只依赖主键的一部分。 如果知道主键的所有属性的值,就可以检索到任何元组(行)的任何属性的任何值。

1.6、第三范式(3rd NF)

第三范式是在第二范式的基础上,确保数据表中的每一个非主键字段都和主键字段直接相关,也就是说, 要求数据表中的所有非主键字段不能依赖于其他非主键字段。 (即,不能存在非主属性A依赖于非主属性B,非主属性B依赖于主键C的情况,即存在"A–>B–>C"的决定关系)通俗地讲,该规则的意思是所有非主键属性之间不能有依赖关系,必须相互独立。

1.7 小结

关于数据表的设计,有三个范式要遵循。

(1)第一范式(1NF),确保每列保持原子性

数据库的每一列都是不可分割的原子数据项,不可再分的最小数据单元,而不能是集合、数组、记录等非原子数据项。

(2)第二范式(2NF),确保每列都和主键完全依赖

尤其在复合主键的情况向下,非主键部分不应该依赖于部分主键。

(3)第三范式(3NF),确保每列都和主键直接相关,而不是间接相关

范式的优点: 数据的标准化有助于消除数据库中的数据冗余,第三范式(3NF)通常被认为在性能、拓展性和数据完整性方面达到了最好的平衡。

范式的缺点: 范式的使用,可能降低查询的效率。因为范式等级越高,设计出来的数据表就越多、越精细,数据的冗余度就越低,进行数据查询的时候就可能需要关联多张表,这不但代价昂贵,也可能使一些索引策略无效。

范式只是提出了设计的标准,实际上设计数据表时,未必一定要符合这些标准。开发中,我们会出现为了性能和读取效率违反范式化的原则,通过增加少量的冗余或重复的数据来提高数据库的读性能,减少关联查询,join表的次数,实现空间换取时间的目的。因此在实际的设计过程中要理论结合实际,灵活运用。

2. 反范式化2.1、概述

规范化 **vs、性能

为满足某种商业目标 , 数据库性能比规范化数据库更重要

在数据规范化的同时 , 要综合考虑数据库的性能

通过在给定的表中添加额外的字段,以大量减少需要从中搜索信息所需的时间

通过在给定的表中插入计算列,以方便查询

2.2、反范式的新问题存储空间变大了一个表中字段做了修改,另一个表中冗余的字段也需要做同步修改,否则数据不一致若采用存储过程来支持数据的更新、删除等额外操作,如果更新频繁,会非常消耗系统资源在数据量小的情况下,反范式不能体现性能的优势,可能还会让数据库的设计更加复杂2.3、反范式的适用场景

当冗余信息有价值或者能大幅度提高查询效率的时候,我们才会采取反范式的优化。

增加冗余字段的建议

1)这个冗余字段不需要经常进行修改

2)这个冗余字段查询的时候不可或缺

历史快照、历史数据的需要

在现实生活中,我们经常需要一些冗余信息,比如订单中的收货人信息,包括姓名、电话和地址等。每次发生的订单收货信息都属于历史快照,需要进行保存,但用户可以随时修改自己的信息,这时保存这些冗余信息是非常有必要的。

反范式优化也常用在数据仓库的设计中,因为数据仓库通常存储历史数据,对增删改的实时性要求不强,对历史数据的分析需求强。这时适当允许数据的冗余度,更方便进行数据分析。

3. BCNF(巴斯范式)

主属性(仓库名)对于候选键(管理员,物品名)是部分依赖的关系,这样就有可能导致异常情况。因此引入BCNF, 它在 3NF、的基础上消除了主属性对候选键的部分依赖或者传递依赖关系 。

如果在关系R中,U为主键,A属性是主键的一个属性,若存在A->Y,Y为主属性,则该关系不属于BCNF。

4. ER模型

ER模型也叫做实体关系模型,是用来描述现实生活中客观存在的事物、事物的属性,以及事物之间关系的一种数据模型。 在开发基于数据库的信息系统的设计阶段,通常使用ER模型来描述信息需要和信息特性,帮助我们理清业务逻辑,从而设计出优秀的数据库。

4.1 ER 模型包括那些要素?

**ER、模型中有三个要素,分别是实体、属性和关系 。

实体,可以看做是数据对象,往往对应于现实生活中的真实存在的个体。在 ER 模型中,用矩形来表示。实体分为两类,分别是强实体和弱实体。强实体是指不依赖于其他实体的实体;弱实体是指对另一个实体有很强的依赖关系的实体。

属性,则是指实体的特性。比如超市的地址、联系电话、员工数等。在 ER 模型中用椭圆形来表示。

关系,则是指实体之间的联系。比如超市把商品卖给顾客,就是一种超市与顾客之间的联系。在 ER 模型中用菱形来表示。

注意:实体和属性不容易区分。这里提供一个原则:我们要从系统整体的角度出发去看, 可以独立存在的是实体,不可再分的是属性 。也就是说,属性不能包含其他属性。

4.2、关系的类型

在 ER 模型的 3 个要素中,关系又可以分为 3 种类型,分别是 一对一、一对多、多对多。

一对一:指实体之间的关系是一一对应的

一对多:指一边的实体通过关系,可以对应多个另外一边的实体。相反,另外一边的实体通过这个关系,则只能对应唯一的一边的实体

多对多:指关系两边的实体都可以通过关系对应多个对方的实体

5. 数据表的设计原则

数据表设计的一般原则:“三少一多”

数据表的个数越少越好

数据表中的字段个数越少越好

数据表中联合主键的字段个数越少越好

使用主键和外键越多越好

注意:这个原则并不是绝对的,有时候我们需要牺牲数据的冗余度来换取数据处理的效率。

6. 数据库对象编写建议6.1、关于库

【强制】库的名称必须控制在32个字符以内,只能使用英文字母、数字和下划线,建议以英文字母开头。

【强制】库名中英文一律小写,不同单词采用下划线分割。须见名知意。

【强制】库的名称格式:业务系统名称_子系统名。

【强制】库名禁止使用关键字(如type,order等)。

【强制】创建数据库时必须显式指定字符集,并且字符集只能是utf8或者utf8mb4。创建数据库SQL举例:CREATE DATABASE crm_fund DEFAULT CHARACTER SET 'utf8';

【建议】对于程序连接数据库账号,遵循权限最小原则。使用数据库账号只能在一个DB下使用,不准跨库。程序使用的账号原则上不准有drop权限。

【建议】临时库以tmp_为前缀,并以日期为后缀;备份库以bak_为前缀,并以日期为后缀。

6.2、关于表、列

【强制】表和列的名称必须控制在32个字符以内,表名只能使用英文字母、数字和下划线,建议以英文字母开头。

【强制】 表名、列名一律小写,不同单词采用下划线分割。须见名知意。

【强制】表名要求有模块名强相关,同一模块的表名尽量使用统一前缀。比如:crm_fund_item

【强制】创建表时必须显式指定字符集为utf8或utf8mb4。

【强制】表名、列名禁止使用关键字(如type,order等)。

【强制】创建表时必须显式指定表存储引擎类型。如无特殊需求,一律为InnoDB。

【强制】建表必须有comment。

【强制】字段命名应尽可能使用表达实际含义的英文单词或缩写。如:公司 ID,不要使用 corporation_id, 而用corp_id 即可。

【强制】布尔值类型的字段命名为is_描述。如member表上表示是否为enabled的会员的字段命名为 is_enabled。

【强制】禁止在数据库中存储图片、文件等大的二进制数据。通常文件很大,短时间内造成数据量快速增长,数据库进行数据库读取时,通常会进行大量的随机IO操作,文件很大时,IO操作很耗时。通常存储于文件服务器,数据库只存储文件地址信息。

【建议】建表时关于主键:表必须有主键 (1)强制要求主键为id,类型为int或bigint,且为auto_increment 建议使用unsigned无符号型。 (2)标识表里每一行主体的字段不要设为主键,建议设为其他字段如user_id,order_id等,并建立unique key索引。因为如果设为主键且主键值为随机插入,则会导致innodb内部页分裂和大量随机I/O,性能下降。

【建议】核心表(如用户表)必须有行数据的创建时间字段(create_time)和最后更新时间字段(update_time),便于查问题。

【建议】表中所有字段尽量都是NOT NULL属性,业务可以根据需要定义DEFAULT值。 因为使用NULL值会存在每一行都会占用额外存储空间、数据迁移容易出错、聚合函数计算结果偏差等问题。

【建议】所有存储相同数据的列名和列类型必须一致(一般作为关联列,如果查询时关联列类型不一致会自动进行数据类型隐式转换,会造成列上的索引失效,导致查询效率降低)。

【建议】中间表(或临时表)用于保留中间结果集,名称以tmp_开头。备份表用于备份或抓取源表快照,名称以bak_开头。中间表和备份表定期清理。

【示范】一个较为规范的建表语句:

CREATE TABLE user_info ( `id` int unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主键', `user_id` bigint(11) NOT NULL COMMENT '用户id', `username` varchar(45) NOT NULL COMMENT '真实姓名', `email` varchar(30) NOT NULL COMMENT '用户邮箱', `nickname` varchar(45) NOT NULL COMMENT '昵称', `birthday` date NOT NULL COMMENT '生日', `sex` tinyint(4) DEFAULT '0' COMMENT '性别', `short_introduce` varchar(150) DEFAULT NULL COMMENT '一句话介绍自己,最多50个汉字', `user_resume` varchar(300) NOT NULL COMMENT '用户提交的简历存放地址', `user_register_ip` int NOT NULL COMMENT '用户注册时的源ip', `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', `user_review_status` tinyint NOT NULL COMMENT '用户资料审核状态,1为通过,2为审核中,3为未 通过,4为还未提交审核',PRIMARY KEY (`id`), UNIQUE KEY `uniq_user_id` (`user_id`), KEY `idx_username`(`username`), KEY `idx_create_time_status`(`create_time`,`user_review_status`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='网站用户基本信息'【建议】创建表时,可以使用可视化工具。这样可以确保表、字段相关的约定都能设置上。实际上,我们通常很少自己写 DDL 语句,可以使用一些可视化工具来创建和操作数据库和数据表。可视化工具除了方便,还能直接帮我们将数据库的结构定义转化成 SQL 语言,方便数据库和数据表结构的导出和导入。6.3、关于索引

【强制】InnoDB表必须主键为id int/bigint auto_increment,且主键值禁止被更新。

【强制】InnoDB和MyISAM存储引擎表,索引类型必须为BTREE。

【建议】主键的名称以pk_开头,唯一键以uni_或uk_开头,普通索引以idx_开头,一律使用小写格式,以字段的名称或缩写作为后缀。

【建议】多单词组成的columnname,取前几个单词首字母,加末单词组成column_name。如: sample 表 member_id 上的索引:idx_sample_mid。

【建议】单个表上的索引个数不能超过6个。

【建议】在建立索引时,多考虑建立联合索引,并把区分度最高的字段放在最前面。

【建议】在多表 JOIN 的SQL里,保证被驱动表的连接列上有索引,这样JOIN 执行效率最高。

【建议】建表或加索引时,保证表里互相不存在冗余索引。 比如:如果表里已经存在key(a,b), 则key(a)为冗余索引,需要删除。

6.4 SQL编写

【强制】程序端SELECT语句必须指定具体字段名称,禁止写成 *。

【建议】程序端insert语句指定具体字段名称,不要写成INSERT INTO t1 VALUES(…)。

【建议】除静态表或小表(100行以内),DML语句必须有WHERE条件,且使用索引查找。

【建议】INSERT INTO…VALUES(XX),(XX),(XX)… 这里XX的值不要超过5000个。 值过多虽然上线很快,但会引起主从同步延迟。

【建议】SELECT语句不要使用UNION,推荐使用UNION ALL,并且UNION子句个数限制在5个以内。

【建议】线上环境,多表 JOIN 不要超过5个表。

【建议】减少使用ORDER BY,和业务沟通能不排序就不排序,或将排序放到程序端去做。ORDER BY、GROUP BY、DISTINCT 这些语句较为耗费CPU,数据库的CPU资源是极其宝贵的。

【建议】包含了ORDER BY、GROUP BY、DISTINCT 这些查询的语句,WHERE 条件过滤出来的结果集请保持在1000行以内,否则SQL会很慢。

【建议】对单表的多次alter操作必须合并为一次。对于超过100W行的大表进行alter table,必须经过DBA审核,并在业务低峰期执行,多个alter需整合在一起。 因为alter table会产生表锁,期间阻塞对于该表的所有写入,对于业务可能会产生极大影响。

【建议】批量操作数据时,需要控制事务处理间隔时间,进行必要的sleep。

【建议】事务里包含SQL不超过5个。因为过长的事务会导致锁数据较久,MySQL内部缓存、连接消耗过多等问题。

【建议】事务里更新语句尽量基于主键或UNIQUE KEY,如UPDATE… WHERE id=XX;否则会产生间隙锁,内部扩大锁定范围,导致系统性能下降,产生死锁。

第12章 数据库其它调优策略1. 数据库调优的措施1.1、调优的目标尽可能节省系统资源,以便系统可以提供更大负荷的服务。(吞吐量更大)合理的结构设计和参数调整,以提高用户操 响应的速度。(响应速度更快)减少系统的瓶颈,提高MySQL数据库整体的性能。1.2、如何定位调优问题用户的反馈(主要)日志分析(主要)服务器资源使用监控数据库内部状况监控其它1.3、调优的维度和步骤

第1步:选择适合的 DBMS

第2步:优化表设计

第3步:优化逻辑查询

第4步:优化物理查询

物理查询优化是在确定了逻辑查询优化之后,采用物理优化技术(比如索引等),通过计算代价模型对各种可能的访问路径进行估算,从而找到执行方式中代价最小的作为执行计划。

第5步:使用 **Redis、或 **Memcached、作为缓存

第6步:库级优化

1、读写分离

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2xhOe6Lt-1661608724714)(https://cdn.jsdelivr.net/gh/aoshihuankong/cloudimg@master/img/202204031025279.png)]

2、数据分片

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gZlOArON-1661608724714)(https://cdn.jsdelivr.net/gh/aoshihuankong/cloudimg@master/img/202204031026743.png)]

2. 优化MySQL服务器2.1、优化服务器硬件

服务器的硬件性能直接决定着MySQL数据库的性能。 硬件的性能瓶颈直接决定MySQL数据库的运行速度和效率。针对性能瓶颈提高硬件配置,可以提高MySQL数据库查询、更新的速度。

(1)配置较大的内存

(2)配置高速磁盘系统

(3)合理分布磁盘I/O

(4)配置多处理器

2.2、优化MySQL的参数innodb_buffer_pool_size:这个参数是Mysql数据库最重要的参数之一,表示InnoDB类型的表和索引的最大缓存。它不仅仅缓存索引数据,还会缓存表的数据。这个值越大,查询的速度就会越快。但是这个值太大会影响操作系统的性能。key_buffer_size:表示索引缓冲区的大小。索引缓冲区是所有的线程共享。增加索引缓冲区可以得到更好处理的索引(对所有读和多重写)。当然,这个值不是越大越好,它的大小取决于内存的大小。如果这个值太大,就会导致操作系统频繁换页,也会降低系统性能。对于内存在4GB左右的服务器该参数可设置为256M或384M。table_cache:表示同时打开的表的个数。这个值越大,能够同时打开的表的个数越多。物理内存越大,设置就越大。默认为2402,调到512-1024最佳。这个值不是越大越好,因为同时打开的表太多会影响操作系统的性能。query_cache_size:表示查询缓冲区的大小。可以通过在MySQL控制台观察,如果Qcache_lowmem_prunes的值非常大,则表明经常出现缓冲不够的情况,就要增加Query_cache_size的值;如果Qcache_hits的值非常大,则表明查询缓冲使用非常频繁,如果该值较小反而会影响效率,那么可以考虑不用查询缓存;Qcache_free_blocks,如果该值非常大,则表明缓冲区中碎片很多。MySQL8.0之后失效。该参数需要和query_cache_type配合使用。query_cache_type的值是0时,所有的查询都不使用查询缓存区。但是query_cache_type=0并不会导致MySQL释放query_cache_size所配置的缓存区内存。当query_cache_type=1时,所有的查询都将使用查询缓存区,除非在查询语句中指定SQL_NO_CACHE,如SELECT SQL_NO_CACHE * FROM tbl_name。当query_cache_type=2时,只有在查询语句中使用SQL_CACHE关键字,查询才会使用查询缓存区。使用查询缓存区可以提高查询的速度,这种方式只适用于修改操作少且经常执行相同的查询操作的情况。sort_buffer_size:表示每个需要进行排序的线程分配的缓冲区的大小。增加这个参数的值可以提高ORDER BY或GROUP BY操作的速度。默认数值是2 097 144字节(约2MB)。对于内存在4GB左右的服务器推荐设置为6-8M,如果有100个连接,那么实际分配的总共排序缓冲区大小为100 × 6 = 600MB。join_buffer_size = 8M:表示联合查询操作所能使用的缓冲区大小,和sort_buffer_size一样,该参数对应的分配内存也是每个连接独享。read_buffer_size:表示每个线程连续扫描时为扫描的每个表分配的缓冲区的大小(字节)。当线程从表中连续读取记录时需要用到这个缓冲区。SET SESSION read_buffer_size=n可以临时设置该参数的值。默认为64K,可以设置为4M。innodb_flush_log_at_trx_commit:表示何时将缓冲区的数据写入日志文件,并且将日志文件写入磁盘中。该参数对于innoDB引擎非常重要。该参数有3个值,分别为0、1和2。该参数的默认值为1。值为0时,表示每秒1次的频率将数据写入日志文件并将日志文件写入磁盘。每个事务的commit并不会触发前面的任何操作。该模式速度最快,但不太安全,mysqld进程的崩溃会导致上一秒钟所有事务数据的丢失。值为1时,表示每次提交事务时将数据写入日志文件并将日志文件写入磁盘进行同步。该模式是最安全的,但也是最慢的一种方式。因为每次事务提交或事务外的指令都需要把日志写入(flush)硬盘。值为2时,表示每次提交事务时将数据写入日志文件,每隔1秒将日志文件写入磁盘。该模式速度较快,也比0安全,只有在操作系统崩溃或者系统断电的情况下,上一秒钟所有事务数据才可能丢失。innodb_log_buffer_size:这是 InnoDB 存储引擎的事务日志所使用的缓冲区。为了提高性能,也是先将信息写入 Innodb Log Buffer 中,当满足 innodb_flush_log_trx_commit 参数所设置的相应条件(或者日志缓冲区写满)之后,才会将日志写到文件(或者同步到磁盘)中。max_connections:表示 允许连接到MySQL数据库的最大数量 ,默认值是 151 。如果状态变量connection_errors_max_connections 不为零,并且一直增长,则说明不断有连接请求因数据库连接数已达到允许最大值而失败,这是可以考虑增大max_connections 的值。在Linux 平台下,性能好的服务器,支持 500-1000 个连接不是难事,需要根据服务器性能进行评估设定。这个连接数 不是越大 越好 ,因为这些连接会浪费内存的资源。过多的连接可能会导致MySQL服务器僵死。back_log:用于控制MySQL监听TCP端口时设置的积压请求栈大小。如果MySql的连接数达到max_connections时,新来的请求将会被存在堆栈中,以等待某一连接释放资源,该堆栈的数量即back_log,如果等待连接的数量超过back_log,将不被授予连接资源,将会报错。5.6.6 版本之前默认值为 50 , 之后的版本默认为 50 + (max_connections / 5), 对于Linux系统推荐设置为小于512的整数,但最大不超过900。如果需要数据库在较短的时间内处理大量连接请求, 可以考虑适当增大back_log 的值。thread_cache_size:线程池缓存线程数量的大小,当客户端断开连接后将当前线程缓存起来,当在接到新的连接请求时快速响应无需创建新的线程 。这尤其对那些使用短连接的应用程序来说可以极大的提高创建连接的效率。那么为了提高性能可以增大该参数的值。默认为60,可以设置为120。wait_timeout:指定一个请求的最大连接时间,对于4GB左右内存的服务器可以设置为5-10。interactive_timeout:表示服务器在关闭连接前等待行动的秒数。3. 优化数据库结构3.1、拆分表:冷热数据分离3.2、增加中间表3.3、增加冗余字段3.4、优化数据类型

情况1:对整数类型数据进行优化。

遇到整数类型的字段可以用INT 型。这样做的理由是,INT 型数据有足够大的取值范围,不用担心数据超出取值范围的问题。刚开始做项目的时候,首先要保证系统的稳定性,这样设计字段类型是可以的。但在数据量很大的时候,数据类型的定义,在很大程度上会影响到系统整体的执行效率。

对于非负型的数据(如自增ID、整型IP)来说,要优先使用无符号整型UNSIGNED来存储。因为无符号相对于有符号,同样的字节数,存储的数值范围更大。如tinyint有符号为-128-127,无符号为0-255,多出一倍的存储空间。

情况2:既可以使用文本类型也可以使用整数类型的字段,要选择使用整数类型 。

跟文本类型数据相比,大整数往往占用更少的存储空间,因此,在存取和比对的时候,可以占用更少的内存空间。所以,在二者皆可用的情况下,尽量使用整数类型,这样可以提高查询的效率。如:将IP地址转换成整型数据。

情况3:避免使用TEXT、BLOB数据类型

情况4:避免使用ENUM类型

情况5:使用TIMESTAMP存储时间

情况6:用DECIMAL代替FLOAT和DOUBLE存储精确浮点数

总之,遇到数据量大的项目时,一定要在充分了解业务需求的前提下,合理优化数据类型,这样才能充分发挥资源的效率,使系统达到最优 。

3.5、优化插入记录的速度MyISAM引擎的表:

① 禁用索引

② 禁用唯一性检查

③ 使用批量插入

④ 使用LOAD DATA INFILE 批量导入

InnoDB引擎的表:

① 禁用唯一性检查

② 禁用外键检查

③ 禁止自动提交

3.6、使用非空约束

在设计字段的时候,如果业务允许,建议尽量使用非空约束

3.7、分析表、检查表与优化表分析表ANALYZE [LOCAL | NO_WRITE_TO_BINLOG] TABLE tbl_name[,tbl_name]…

默认的,MySQL服务会将 ANALYZE TABLE语句写到binlog中,以便在主从架构中,从服务能够同步数据。可以添加参数LOCAL 或者 NO_WRITE_TO_BINLOG取消将语句写到binlog中。

使用ANALYZE TABLE分析表的过程中,数据库系统会自动对表加一个只读锁。在分析期间,只能读取表中的记录,不能更新和插入记录。ANALYZE TABLE语句能够分析InnoDB和MyISAM类型的表,但是不能作用于视图。

ANALYZE TABLE分析后的统计结果会反应到cardinality的值,该值统计了表中某一键所在的列不重复的值的个数。 该值越接近表中的总行数,则在表连接查询或者索引查询时,就越优先被优化器选择使用。

检查表CHECK TABLE tbl_name [, tbl_name] ... [option] ... option = {QUICK | FAST | MEDIUM | EXTENDED | CHANGED}

MySQL中可以使用CHECK TABLE语句来检查表。CHECK TABLE语句能够检查InnoDB和MyISAM类型的表是否存在错误。CHECK TABLE语句在执行过程中也会给表加上只读锁。

优化表OPTIMIZE [LOCAL | NO_WRITE_TO_BINLOG] TABLE tbl_name [, tbl_name] ...

MySQL中使用OPTIMIZE TABLE语句来优化表。但是,OPTILMIZE TABLE语句只能优化表中的VARCHAR、BLOB或TEXT类型的字段。一个表使用了这些字段的数据类型,若已经删除了表的一大部分数据,或者已经对含有可变长度行的表(含有VARCHAR、BLOB或TEXT列的表)进行了很多更新,则应使用OPTIMIZE TABLE来重新利用未使用的空间,并整理数据文件的碎片。

OPTIMIZE TABLE 语句对InnoDB和MyISAM类型的表都有效。该语句在执行过程中也会给表加上只读锁。

第13章 事务基础知识1. 数据库事务概述1.1、基本概念

事务: 一组逻辑操作单元,使数据从一种状态变换到另一种状态。

事务处理的原则: 保证所有事务都作为一个工作单元来执行,即使出现了故障,都不能改变这种执行方式。当在一个事务中执行多个操作时,要么所有的事务都被提交(commit),那么这些修改就永久地保存下来;要么数据库管理系统将放弃所作的所有修改,整个事务回滚(rollback)到最初状态。

1.2、事务的ACID特性原子性(atomicity):

原子性是指事务是一个不可分割的工作单位,要么全部提交,要么全部失败回滚。

一致性(consistency):

一致性是指事务执行前后,数据从一个合法性状态变换到另外一个合法性状态。这种状态是语义上的而不是语法上的,跟具体的业务有关。

隔离型(isolation):

事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。

持久性(durability):

持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响。

持久性是通过事务日志来保证的。日志包括了重做日志和回滚日志。当我们通过事务对数据进行修改的时候,首先会将数据库的变化信息记录到重做日志中,然后再对数据库中对应的行进行修改。这样做的好处是,即使数据库系统崩溃,数据库重启后也能找到没有更新到数据库系统中的重做日志,重新执行,从而使事务具有持久性。

1.3、事务的状态活动的(active)

事务对应的数据库操作正在执行过程中时,我们就说该事务处在活动的状态。

部分提交的(partially committed)

当事务中的最后一个操作执行完成,但由于操作都在内存中执行,所造成的影响并没有刷新到磁盘时,我们就说该事务处在部分提交的状态。

失败的(failed)

当事务处在活动的或者部分提交的状态时,可能遇到了某些错误(数据库自身的错误、操作系统错误或者直接断电等)而无法继续执行,或者人为的停止当前事务的执行,我们就说该事务处在失败的状态。

中止的(aborted)

如果事务执行了一部分而变为失败的状态,那么就需要把已经修改的事务中的操作还原到事务执行前的状态。换句话说,就是要撤销失败事务对当前数据库造成的影响。我们把这个撤销的过程称之为回滚。当回滚操作执行完毕时,也就是数据库恢复到了执行事务之前的状态,我们就说该事务处在了中止的状态。

提交的(committed)

当一个处在部分提交的状态的事务将修改过的数据都同步到磁盘上之后,我们就可以说该事务处在了提交的状态。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ghOSx4cS-1661608724715)(https://cdn.jsdelivr.net/gh/aoshihuankong/cloudimg@master/img/202204031104063.png)]

2. 如何使用事务2.1、显式事务

步骤1: START TRANSACTION或者BEGIN,作用是显式开启一个事务。

sql> BEGIN; #或者 sql> START TRANSACTION;

START TRANSACTION语句相较于BEGIN特别之处在于,后边能跟随几个修饰符:

①READ ONLY:标识当前事务是一个只读事务,也就是属于该事务的数据库操作只能读取数据,而不能修改数据。

②READ WRITE:标识当前事务是一个读写事务,也就是属于该事务的数据库操作既可以读取数据,也可以修改数据。

③WITH CONSISTENT SNAPSHOT:启动一致性读。

步骤2: 一系列事务中的操作(主要是DML,不含DDL)

步骤3: 提交事务 或 中止事务(即回滚事务)

# 提交事务。当提交事务后,对数据库的修改是永久性的。mysql> COMMIT;# 回滚事务。即撤销正在进行的所有没有提交的修改 mysql> ROLLBACK; # 将事务回滚到某个保存点。 mysql> ROLLBACK TO [SAVEPOINT]

其中关于SAVEPOINT相关操作有:

# 在事务中创建保存点,方便后续针对保存点进行回滚。一个事物中可以存在多个保存点。SAVEPOINT 保存点名称;# 删除某个保存点RELEASE SAVEPOINT 保存点名称;2.2、隐式事务显式的的使用START TRANSACTION或者BEGIN语句开启一个事务。这样在本次事务提交或者回滚前会暂时关闭掉自动提交的功能。把系统变量autocommit的值设置为OFF2.3、隐式提交数据的情况

数据定义语言(Data definition language,缩写为:DDL)

隐式使用或修改mysql数据库中的表

事务控制或关于锁定的语句

当我们在一个事务还没提交或者回滚时就又使用START TRANSACTION或者BEGIN语句开启了另一个事务时,会隐式的提交上一个事务。当前的autocommit系统变量的值为OFF,我们手动把它调为ON时,也会隐式的提交前边语句所属的事务。使用LOCK TABLES、UNLOCK TABLES等关于锁定的语句也会隐式的提交前边语句所属的事务。3. 事务隔离级别3.1、数据并发问题脏写( Dirty Write )

对于两个事务 Session A、Session B,如果事务Session A修改了另一个未提交事务Session B修改过的数据,那就意味着发生了脏写

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ssTpltbB-1661608724716)(https://cdn.jsdelivr.net/gh/aoshihuankong/cloudimg@master/img/202204031124086.png)]

脏读( Dirty Read )

对于两个事务 Session A、Session B,Session A读取了已经被 Session B更新但还没有被提交的字段。之后若 Session B回滚,Session A读取的内容就是临时且无效的。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6BR1bIhV-1661608724717)(https://cdn.jsdelivr.net/gh/aoshihuankong/cloudimg@master/img/202204031124136.png)]

不可重复读( Non-Repeatable Read )

对于两个事务Session A、Session B,Session A读取了一个字段,然后 Session B更新了该字段。 之后Session A再次读取同一个字段,值就不同了。那就意味着发生了不可重复读。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1v6cvYWu-1661608724718)(https://cdn.jsdelivr.net/gh/aoshihuankong/cloudimg@master/img/202204031124331.png)]

幻读( Phantom )

对于两个事务Session A、Session B, Session A 从一个表中读取了一个字段, 然后 Session B 在该表中插入了一些新的行。 之后, 如果 Session A再次读取同一个表, 就会多出几行。那就意味着发生了幻读。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RPLkJgD4-1661608724719)(https://cdn.jsdelivr.net/gh/aoshihuankong/cloudimg@master/img/202204031125847.png)]

注意1:

有的同学会有疑问,那如果Session B中剔除了一些符合studentno > 0的记录而不是插入新记录,那么Session A之后再根据studentno > 0的条件读取的记录变少了,这种现象算不算幻读呢?这种现象不属于幻读,幻读强调的是一个事物按照某个相同条件多次读取记录时,后读取时读到了之前没有读到的记录。

注意2:

那对于先前已经读到的记录,之后又读取不到这种情况,算啥呢?这相当于对每一条记录都发生了不可重复读的现象。幻读只是重点强调了读取到之前读取没有获取到的记录。

3.2 SQL中的四种隔离级别

SQL标准中设立了4个隔离级别:

READ UNCOMMITTED:读未提交,在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。不能避免脏读、不可重复读、幻读。READ COMMITTED:读已提交,它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。可以避免脏读,但不可重复读、幻读问题仍然存在。REPEATABLE READ:可重复读,事务A在读到一条数据之后,此时事务B对该数据进行了修改并提交,那么事务A再读该数据,读到的还是原来的内容。可以避免脏读、不可重复读,但幻读问题仍然存在。这是MySQL的默认隔离级别。SERIALIZABLE:可串行化,确保事务可以从一个表中读取相同的行。在这个事务持续期间,禁止其他事务对该表执行插入、更新和删除操作。所有的并发问题都可以避免,但性能十分低下。能避免脏读、不可重复读和幻读。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kJOsKCAa-1661608724719)(https://cdn.jsdelivr.net/gh/aoshihuankong/cloudimg@master/img/202204031127542.png)]

3.3、如何设置事务的隔离级别SET [GLOBAL|SESSION] TRANSACTION ISOLATION LEVEL 隔离级别; #其中,隔离级别格式: > READ UNCOMMITTED > READ COMMITTED > REPEATABLE READ > SERIALIZABLE

或者:

SET [GLOBAL|SESSION] TRANSACTION_ISOLATION = '隔离级别' #其中,隔离级别格式: > READ-UNCOMMITTED > READ-COMMITTED > REPEATABLE-READ > SERIALIZABLE第14章 MySQL事务日志

事务有4种特性:原子性、一致性、隔离性和持久性。那么事务的四种特性到底是基于什么机制实现呢?

事务的隔离性由锁机制实现。而事务的原子性、一致性和持久性由事务的 redo 日志和undo 日志来保证。REDO LOG 称为重做日志,提供再写入操作,恢复提交事务修改的页操作,用来保证事务的持久性。UNDO LOG 称为回滚日志,回滚行记录到某个特定版本,用来保证事务的原子性、一致性。1. redo日志1.1、为什么需要REDO日志

一方面,缓冲池可以帮助我们消除CPU和磁盘之间的鸿沟,checkpoint机制可以保证数据的最终落盘,然而由于checkpoint并不是每次变更的时候就触发的,而是master线程隔一段时间去处理的。所以最坏的情况就是事务提交后,刚写完缓冲池,数据库宕机了,那么这段数据就是丢失的,无法恢复。

另一方面,事务包含持久性的特性,就是说对于一个已经提交的事务,在事务提交后即使系统发生了崩溃,这个事务对数据库中所做的更改也不能丢失。

那么如何保证这个持久性呢?一个简单的做法:在事务提交完成之前把该事务所修改的所有页面都刷新到磁盘,但是这个简单粗暴的做法有些问题

另一个解决的思路:我们只是想让已经提交了的事务对数据库中数据所做的修改永久生效,即使后来系统崩溃,在重启后也能把这种修改恢复出来。所以我们其实没有必要在每次事务提交时就把该事务在内存中修改过的全部页面刷新到磁盘,只需要把修改了哪些东西记录一下就好。比如,某个事务将系统表空间中第10号页面中偏移量为100处的那个字节的值1改成2。我们只需要记录一下:将第0号表空间的10号页面的偏移量为100处的值更新为 2 。

1.2 REDO日志的好处、特点好处

**redo日志降低了刷盘频率

**redo日志占用的空间非常小

特点

**redo日志是顺序写入磁盘的

事务执行过程中,redo log不断记录

1.3 redo的组成

Redo log可以简单分为以下两个部分:

重做日志的缓冲 (redo log buffer) ,保存在内存中,是易失的。

参数设置:innodb_log_buffer_size:

redo log buffer 大小,默认16M,最大值是4096M,最小值为1M。

重做日志文件 (redo log file),保存在硬盘中,是持久的。1.4 redo的整体流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1HHd9LE0-1661608724720)(https://cdn.jsdelivr.net/gh/aoshihuankong/cloudimg@master/img/202204031147714.png)]

第1步:先将原始数据从磁盘中读入内存中来,修改数据的内存拷贝

第2步:生成一条重做日志并写入redo log buffer,记录的是数据被修改后的值

第3步:当事务commit时,将redo log buffer中的内容刷新到 redo log file,对 redo log file采用追加写的方式

第4步:定期将内存中修改的数据刷新到磁盘中

Write-Ahead Log(预先日志持久化):在持久化一个数据页之前,先将内存中相应的日志页持久化。

1.5 redo log的刷盘策略

redo log buffer刷盘到redo log file的过程并不是真正的刷到磁盘中去,只是刷入到文件系统缓存(page cache)中去(这是现代操作系统为了提高文件写入效率做的一个优化),真正的写入会交给系统自己来决定(比如page cache足够大了)。那么对于InnoDB来说就存在一个问题,如果交给系统来同步,同样如果系统宕机,那么数据也丢失了(虽然整个系统宕机的概率还是比较小的)。

针对这种情况,InnoDB给出innodb_flush_log_at_trx_commit参数,该参数控制 commit提交事务时,如何将 redo log buffer 中的日志刷新到 redo log file 中。它支持三种策略:

设置为0:表示每次事务提交时不进行刷盘操作。(系统默认master thread每隔1s进行一次重做日志的同步)设置为1:表示每次事务提交时都将进行同步,刷盘操作(默认值)设置为2:表示每次事务提交时都只把 redo log buffer 内容写入 page cache,不进行同步。由os自己决定什么时候同步到磁盘文件。1.6、不同刷盘策略演示流程图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r3SjIoEI-1661608724721)(https://cdn.jsdelivr.net/gh/aoshihuankong/cloudimg@master/img/202204031152952.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9xvRVwqT-1661608724722)(https://cdn.jsdelivr.net/gh/aoshihuankong/cloudimg@master/img/202204031152607.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Xpef2H9c-1661608724723)(https://cdn.jsdelivr.net/gh/aoshihuankong/cloudimg@master/img/202204031153931.png)]

1.7、写入redo log buffer过程补充概念:Mini-Transaction**

一个事务可以包含若干条语句,每一条语句其实是由若干个mtr组成,每一个mtr又可以包含若干条redo日志

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7AGV22jA-1661608724723)(https://cdn.jsdelivr.net/gh/aoshihuankong/cloudimg@master/img/202204040912119.png)]

redo、日志写入log buffer**

不同的事务可能是并发执行的,所以事务T1、事务T2之间的mtr可能是交替执行的。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wi35RHky-1661608724723)(https://cdn.jsdelivr.net/gh/aoshihuankong/cloudimg@master/img/202204040915708.png)]

1.8 redo log file**相关参数设置

innodb_log_group_home_dir:指定 redo log 文件组所在的路径,默认值为./,表示在数据库的数据目录下。MySQL的默认数据目录(var/lib/mysql)下默认有两个名为ib_logfile0和ib_logfile1的文件,log buffer中的日志默认情况下就是刷新到这两个磁盘文件中。此redo日志文件位置还可以修改。

innodb_log_files_in_group:指明redo log file的个数,命名方式如:ib_logfile0,ib_logfile1… ib_logfilen。默认2个,最大100个。

innodb_flush_log_at_trx_commit:控制 redo log 刷新到磁盘的策略,默认为1。

innodb_log_file_size:单个 redo log 文件设置大小,默认值为 48M 。最大值为512G,注意最大值指的是整个 redo log 系列文件之和,即(innodb_log_files_in_group * innodb_log_file_size )不能大于最大值512G。

日志文件组

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DYCx1R7M-1661608724724)(https://cdn.jsdelivr.net/gh/aoshihuankong/cloudimg@master/img/202204040920532.png)]

checkpoint**

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9FJvP4Se-1661608724725)(https://cdn.jsdelivr.net/gh/aoshihuankong/cloudimg@master/img/202204040921715.png)]

如果 write pos 追上 checkpoint ,表示 日志文件组 满了,这时候不能再写入新的 redo log记录,MySQL 得停下来,清空一些记录,把 checkpoint 推进一下。

2. Undo日志

redo log是事务持久性的保证,undo log是事务原子性的保证。在事务中更新数据的前置操作其实是要先写入一个 undo log 。

2.1、如何理解Undo日志

事务需要保证原子性,也就是事务中的操作要么全部完成,要么什么也不做。但有时候事务执行到一半会出现一些情况,比如:

情况一:事务执行过程中可能遇到各种错误,比如服务器本身的错误,操作系统错误,甚至是突然断电导致的错误。情况二:程序员可以在事务执行过程中手动输入ROLLBACK语句结束当前事务的执行。

以上情况出现,我们需要把数据改回原先的样子,这个过程称之为回滚,这样就可以造成一个假象:这个事务看起来什么都没做,所以符合原子性要求。

2.2 Undo日志的作用作用1:回滚数据作用2:MVCC(详情看第16章)2.3 undo的存储结构回滚段与undo页

InnoDB对undo log的管理采用段的方式,也就是回滚段(rollback segment)。每个回滚段记录了1024个undo log segment,而在每个undo log segment段中进行undo页的申请。

回滚段与事务

每个事务只会使用一个回滚段,一个回滚段在同一时刻可能会服务于多个事务。

当一个事务开始的时候,会制定一个回滚段,在事务进行的过程中,当数据被修改时,原始的数据会被复制到回滚段。

在回滚段中,事务会不断填充盘区,直到事务结束或所有的空间被用完。如果当前的盘区不够用,事务会在段中请求扩展下一个盘区,如果所有已分配的盘区都被用完,事务会覆盖最初的盘区或者在回滚段允许的情况下扩展新的盘区来使用。

回滚段存在于undo表空间中,在数据库中可以存在多个undo表空间,但同一时刻只能使用一个undo表空间。

当事务提交时,InnoDB存储引擎会做以下两件事情:

将undo log放入列表中,以供之后的purge操作判断undo log所在的页是否可以重用,若可以分配给下个事务使用

回滚段中的数据分类

未提交的回滚数据(uncommitted undo information)

已经提交但未过期的回滚数据(committed undo information)

事务已经提交并过期的数据(expired undo information)

2.4 undo的类型

在InnoDB存储引擎中,undo log分为:

insert undo logupdate undo log2.5 undo log的生命周期简要生成过程

只有Buffer Pool的流程:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vy1iYKbj-1661608724726)(https://cdn.jsdelivr.net/gh/aoshihuankong/cloudimg@master/img/202204040937761.png)]

有了Redo Log和Undo Log之后:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qKGPNJh9-1661608724727)(https://cdn.jsdelivr.net/gh/aoshihuankong/cloudimg@master/img/202204040938657.png)]

详细生成过程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZnBpMAYj-1661608724728)(https://cdn.jsdelivr.net/gh/aoshihuankong/cloudimg@master/img/202204040939231.png)]

当我们执行INSERT时:

begin; INSERT INTO user (name) VALUES ("tom");

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6fZKTSrw-1661608724728)(https://cdn.jsdelivr.net/gh/aoshihuankong/cloudimg@master/img/202204040940543.png)]

当我们执行UPDATE时:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r2I5BGlz-1661608724729)(https://cdn.jsdelivr.net/gh/aoshihuankong/cloudimg@master/img/202204040941624.png)]

UPDATE user SET id=2 WHERE id=1;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2erF7dCz-1661608724729)(https://cdn.jsdelivr.net/gh/aoshihuankong/cloudimg@master/img/202204040941449.png)]

undo log是如何回滚的

以上面的例子来说,假设执行rollback,那么对应的流程应该是这样:

通过undo no=3的日志把id=2的数据删除

通过undo no=2的日志把id=1的数据的deletemark还原成0

通过undo no=1的日志把id=1的数据的name还原成Tom

通过undo no=0的日志把id=1的数据删除

undo log的删除

针对于insert undo log

因为insert操作的记录,只对事务本身可见,对其他事务不可见。故该undo log可以在事务提交后直接删除,不需要进行purge操作。

针对于update undo log

该undo log可能需要提供MVCC机制,因此不能在事务提交时就进行删除。提交时放入undo log链表,等待purge线程进行最后的删除。

2.6、小结

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6ltyofEH-1661608724729)(https://cdn.jsdelivr.net/gh/aoshihuankong/cloudimg@master/img/202204040944948.png)]

undo log是逻辑日志,对事务回滚时,只是将数据库逻辑地恢复到原来的样子。

redo log是物理日志,记录的是数据页的物理变化,undo log不是redo log的逆过程。

第15章 锁1. 概述

在数据库中,除传统的计算资源(如CPU、RAM、I/O等)的争用以外,数据也是一种供许多用户共享的资源。为保证数据的一致性,需要对并发操作进行控制,因此产生了锁。同时锁机制也为实现MySQL的各个隔离级别提供了保证。 锁冲突 也是影响数据库并发访问性能的一个重要因素。所以锁对数据库而言显得尤其重要,也更加复杂。

2. MySQL并发事务访问相同记录2.1、读-读情况

读-读情况,即并发事务相继读取相同的记录。读取操作本身不会对记录有任何影响,并不会引起什么问题,所以允许这种情况的发生。

2.2、写-写情况

写-写情况,即并发事务相继对相同的记录做出改动。

在这种情况下会发生脏写的问题,任何一种隔离级别都不允许这种问题的发生。所以在多个未提交事务相继对一条记录做改动时,需要让它们排队执行,这个排队的过程其实是通过锁来实现的。

2.3、读-写或写-读情况

读-写或写-读,即一个事务进行读取操作,另一个进行改动操作。这种情况下可能发生脏读、不可重复读、幻读的问题。

2.4、并发问题的解决方案

怎么解决脏读、不可重复读、幻读这些问题呢?其实有两种可选的解决方案:

方案一:读操作利用多版本并发控制(MVCC,下章讲解),写操作进行加锁。

所谓的MVCC,就是生成一个ReadView,通过ReadView找到符合条件的记录版本(历史版本由undo日志构建)。查询语句只能读到在生成ReadView之前已提交事务所做的更改,在生成ReadView之前未提交的事务或者之后才开启的事务所做的更改是看不到的。而写操作肯定针对的是最新版本的记录,读记录的历史版本和改动记录的最新版本本身并不冲突,也就是采用MVCC时,读-写操作并不冲突。

普通的SELECT语句在READ COMMITTED和REPEATABLE READ隔离级别下会使用到MVCC读取记录。

在READ COMMITTED隔离级别下,一个事务在执行过程中每次执行SELECT操作时都会生成一个ReadView,ReadView的存在本身就保证了事务不可以读取到未提交的事务所做的更改,也就是避免了脏读现象;在REPEATABLE READ隔离级别下,一个事务在执行过程中只有第一次执行SELECT操作 才会生成一个ReadView,之后的SELECT操作都复用这ReadView,这样也就避免了不可重复读和幻读的问题。

方案二:读、写操作都采用加锁的方式。

小结对比发现:

采用MVCC方式的话,读-写操作彼此并不冲突,性能更高。采用加锁方式的话,读-写操作彼此需要排队执行,影响性能。

一般情况下我们当然愿意采用MVCC来解决读-写操作并发执行的问题,但是业务在某些特殊情况下,要求必须采用加锁的方式执行。

3. 锁的不同角度分类

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SzzcUVwl-1661608724730)(https://cdn.jsdelivr.net/gh/aoshihuankong/cloudimg@master/img/202204050933339.png)]

3.1、从数据操作的类型划分:读锁、写锁读锁:也称为共享锁、英文用S表示。针对同一份数据,多个事务的读操作可以同时进行而不会互相影响,相互不阻塞的。写锁:也称为排他锁、英文用X表示。当前写操作没有完成前,它会阻断其他写锁和读锁。这样就能确保在给定的时间里,只有一个事务能执行写入,并防止其他用户读取正在写入的同一资源。

需要注意的是对于 **InnoDB、引擎来说,读锁和写锁可以加在表上,也可以加在行上。

锁定读

在采用加锁方式解决脏读、不可重复读、幻读这些问题时,读取一条记录时需要获取该记录的S锁,其实是不严谨的,有时候需要在读取记录时就获取记录的X锁,来禁止别的事务读写该记录,为此MySQL提出了两种比较特殊的SELECT语句格式:

对读取的记录加S锁:SELECT ... LOCK IN SHARE MODE;# 或SELECT ... FOR SHARE; #(8.0新增语法)对读取的记录加X锁:SELECT ... FOR UPDATE;

**MySQL8.0新特性:

在5.7及之前的版本,SELECT … FOR UPDATE,如果获取不到锁,会一直等待,直到innodb_lock_wait_timeout超时。在8.0版本中,SELECT … FOR UPDATE, SELECT … FOR SHARE 添加NOWAIT、SKIP LOCKED语法,跳过锁等待,或者跳过锁定。

NOWAIT:如果查询的行已经加锁,会立即报错返回

SKIP LOCKED:如果查询的行已经加锁,只返回结果中不包含被锁定的行

写操作

DELETE:对一条记录做DELETE操作的过程其实是先在B+树中定位到这条记录的位置,然后获取这条记录的X锁,再执行delete mark操作。

UPDATE:在对一条记录做UPDATE操作时分为三种情况:

情况1:未修改该记录的键值,并且被更新的列占用的存储空间在修改前后未发生变化。则先在B+树中定位到这条记录的位置,然后再获取一下记录的X锁,最后在原纪录的位置进行修改操作。情况2:未修改该记录的键值,并且至少有一个被更新的列占用的存储空间在修改前后发生变化。则先在B+树中定位到这条记录的位置,然后获取一下记录的X锁,将该记录彻底删除掉(就是把记录彻底移入垃圾链表),最后再插入一条新记录。新插入的记录由INSERT操作提供的隐式锁进行保护。情况3:修改该记录的键值,则相当于在原纪录上做DELECT操作之后再来一次INSERT操作。

INSERT:一般情况下,新插入一条记录的操作并不加锁,通过一种称之为隐式锁的结构来保护这条新插入的记录在本事务提交前不被别的事务访问。

3.2、从数据操作的粒度划分:表级锁、页级锁、行锁表锁(Table Lock)

该锁会锁定整张表,它是MySQL中最基本的锁策略,并不依赖于存储引擎,并且表锁是开销最少的策略。由于表级锁一次会将整个表锁定,所以可以很好的避免死锁的问题。当然,锁的粒度大所带来最大的负面影响就是出现锁资源争用的概率也会最高,导致并发率大打折扣。

① 表级别的S锁、X锁

在对某个表执行SELECT、INSERT、DELETE、UPDATE语句时,InnoDB存储引擎是不会为这个表添加表级别的S锁或者X锁的。在对某个表执行一些诸如ALTER TABLE、DROP TABLE这类的DDL语句时,其他事务对这个表并发执行诸如SELECT、INSERT、DELETE、UPDATE的语句会发生阻塞。同理,某个事务中对某个表执行SELECT、INSERT、DELETE、UPDATE语句时,在其他会话中对这个表执行DDL语句也会发生阻塞。这个过程其实是通过在server层使用一种称之为元数据锁(英文名:Metadata Locks,简称MDL)结构来实现的。

一般情况下,不会使用InnoDB存储引擎提供的表级别的S锁和X锁。只会在一些特殊情况下,比方说崩溃恢复过程中用到。比如,在系统变量autocommit=0,innodb_table_locks = 1时,手动获取InnoDB存储引擎提供的表t 的S锁或者X锁可以这么写:

LOCK TABLES t READ:InnoDB存储引擎会对表t加表级别的S锁。

LOCK TABLES t WRITE:InnoDB存储引擎会对表t加表级别的X锁。

总结:MyISAM在执行查询语句(SELECT)前,会给涉及的所有表加读锁,在执行增删改操作前,会给涉及的表加写锁。InnoDB存储引擎是不会为这个表添加表级别的读锁或者写锁的。

② 意向锁 (intention lock)

InnoDB 支持多粒度锁(multiple granularity locking),它允许行级锁与表级锁共存,而 意向锁 就是其中的一种表锁。

1、意向锁的存在是为了协调行锁和表锁的关系,支持多粒度(表锁与行锁)的锁并存。

2、意向锁是一种不与行级锁冲突的表级锁,这一点非常重要。

3、表明“某个事务正在某些行持有了锁或该事务准备去持有锁”

意向锁分为两种:

意向共享锁 (intention shared lock, IS):事务有意向对表中的某些行加 共享锁 (S锁)-- 事务要获取某些行的 S 锁,必须先获得表的 IS 锁。 SELECT column FROM table ... LOCK IN SHARE MODE;意向排他锁 (intention exclusive lock, IX):事务有意向对表中的某些行加 排他锁 (X锁)-- 事务要获取某些行的 X 锁,必须先获得表的 IX 锁。 SELECT column FROM table ... FOR UPDATE;

即:意向锁是由存储引擎自己维护的,用户无法手动操作意向锁,在为数据行加共享 / 排他锁之前,InooDB 会先获取该数据行所在数据表的对应意向锁。

意向锁要解决的问题

现在有两个事务,分别是T1和T2,其中T2试图在该表级别上应用共享或排它锁,如果没有意向锁存在,那么T2就需要去检查各个页或行是否存在锁;如果存在意向锁,那么此时就会受到由T1控制的表级别意向锁的阻塞。T2在锁定该表前不必检查各个页或行锁,而只需检查表上的意向锁。简单来说就是给更大一级级别的空间示意里面是否已经上过锁。

在数据表的场景中, 如果我们给某一行数据加上了排它锁,数据库会自动给更大一级的空间,比如数据页或数据表加上意向锁,告诉其他人这个数据页或数据表已经有人上过排它锁了 ,这样当其他人想要获取数据表排它锁的时候,只需要了解是否有人已经获取了这个数据表的意向排它锁即可。

如果事务想要获取数据表中某些记录的共享锁,就需要在数据表上添加意向共享锁如果事务想要获取数据表中某些记录的排它锁,就需要在数据表上添加意向排他锁

这时,意向锁会告诉其他事务已经有人锁定了表中的某些记录。

③ 自增锁(AUTO-INC锁)

“Simple inserts” (简单插入)

可以预先确定要插入的行数(当语句被初始处理时)的语句。包括没有嵌套子查询的单行和多行INSERT...VALUES()和REPLACE语句。

“Bulk inserts” (批量插入)

事先不知道要插入的行数(和所需自动递增值的数量)的语句。比如INSERT ... SELECT,REPLACE ... SELECT和LOAD DATA语句,但不包括纯INSERT。 InnoDB在每处理一行,为AUTO_INCREMENT列分配一个新值。

“Mixed-mode inserts” (混合模式插入)

这些是“Simple inserts”语句但是指定部分新行的自动递增值。例如INSERT INTO teacher (id,name) VALUES (1,'a'), (NULL,'b'), (5,'c'), (NULL,'d');只是指定了部分id的值。另一种类型的“混合模式插入”是INSERT ... ON DUPLICATE KEY UPDATE。

对于上面数据插入的案例,MySQL采用了自增锁的方式来实现,**AUTO-INT锁是当向使用含有AUTO_INCREMENT列的表中插入数据时需要获取的一种特殊的表级锁 ,在执行插入语句时就在表级别加一个AUTO-INT锁,然后为每条待插入记录的AUTO_INCREMENT修饰的列分配递增的值,在该语句执行结束后,再把AUTO-INT锁释放掉。 一个事务在持有AUTO-INC锁的过程中,其他事务的插入语句都要被阻塞 ,可以保证一个语句中分配的递增值是连续的。也正因为此,其并发性显然并不高, 当我们向一个有AUTO_INCREMENT关键字的主键插入值的时候,每条语句都要对这个表锁进行竞争 ,这样的并发潜力其实是很低下的,所以innodb通过innodb_autoinc_lock_mode的不同取值来提供不同的锁定机制,来显著提高SQL语句的可伸缩性和性能。

innodb_autoinc_lock_mode有三种取值,分别对应与不同锁定模式:

(1)innodb_autoinc_lock_mode = 0(“传统”锁定模式)

在此锁定模式下,所有类型的insert语句都会获得一个特殊的表级AUTO-INC锁,用于插入具有AUTO_INCREMENT列的表。这种模式其实就如我们上面的例子,即每当执行insert的时候,都会得到一个表级锁(AUTO-INC锁),使得语句中生成的auto_increment为顺序,且在binlog中重放的时候,可以保证master与slave中数据的auto_increment是相同的。因为是表级锁,当在同一时间多个事务中执行insert的时候,对于AUTO-INC锁的争夺会限制并发能力。

(2)innodb_autoinc_lock_mode = 1(“连续”锁定模式)

在 MySQL 8.0 之前,连续锁定模式是默认的。

在这个模式下,“bulk inserts”仍然使用AUTO-INC表级锁,并保持到语句结束。这适用于所有INSERT … SELECT,REPLACE … SELECT和LOAD DATA语句。同一时刻只有一个语句可以持有AUTO-INC锁。

对于“Simple inserts”(要插入的行数事先已知),则通过在mutex(轻量锁)的控制下获得所需数量的自动递增值来避免表级AUTO-INC锁, 它只在分配过程的持续时间内保持,而不是直到语句完成。不使用表级AUTO-INC锁,除非AUTO-INC锁由另一个事务保持。如果另一个事务保持AUTO-INC锁,则“Simple inserts”等待AUTO-INC锁,如同它是一个“bulk inserts”。

(3)innodb_autoinc_lock_mode = 2(“交错”锁定模式)

从 MySQL 8.0 开始,交错锁模式是默认设置。

在这种锁定模式下,所有类INSERT语句都不会使用表级AUTO-INC锁,并且可以同时执行多个语句。这是最快和最可拓展的锁定模式,但是当使用基于语句的复制或恢复方案时, 从二进制日志重播SQL语句时,这是不安全的。

在此锁定模式下,自动递增值保证在所有并发执行的所有类型的insert语句中是唯一且单调递增的。但是,由于多个语句可以同时生成数字(即,跨语句交叉编号), 为任何给定语句插入的行生成的值可能不是连续的。

④ 元数据锁(MDL锁)

MySQL5.5引入了meta data lock,简称MDL锁,属于表锁范畴。MDL 的作用是,保证读写的正确性。比如,如果一个查询正在遍历一个表中的数据,而执行期间另一个线程对这个表结构做变更,增加了一列,那么查询线程拿到的结果跟表结构对不上,肯定是不行的。

因此, 当对一个表做增删改查操作的时候,加MDL读锁;当要对表做结构变更操作的时候,加MDL写锁。

InnoDB中的行锁

行锁(Row Lock)也称为记录锁,顾名思义,就是锁住某一行(某条记录row)。需要注意的是,MySQL服务器层并没有实现行锁机制, 行级锁只在存储引擎层实现。

优点: 锁定力度小,发生锁冲突概率低,可以实现的并发度高

缺点: 对于锁的开销比较大,加锁会比较慢,容易出现死锁情况

InnoDB与MyISAM的最大不同有两点:一是支持事务;二是采用了行级锁。

① 记录锁(Record Locks)

记录锁也就是仅仅把一条记录锁上,官方的类型名称为:LOCK_REC_NOT_GAP。

记录锁是有S锁和X锁之分的,称之为S型记录锁和X型记录锁。

当一个事务获取了一条记录的S型记录锁后,其他事务也可以继续获取该记录的S型记录锁,但不可以继续获取X型记录锁;当一个事务获取了一条记录的X型记录锁后,其他事务既不可以继续获取该记录的S型记录锁,也不可以继续获取X型记录锁。

② 间隙锁(Gap Locks)

MySQL在REPEATABLE READ隔离级别下是可以解决幻读问题的,解决方案有两种,可以使用MVCC方案解决,也可以采用加锁方案解决。但是在使用加锁方案解决时有个大问题,就是事务在第一次执行读取操作时,那些幻影记录尚不存在,我们无法给这些幻影记录加上记录锁。InnoDB提出了一种称之为Gap Locks的锁,官方的类型名称为:LOCK_GAP,我们可以简称为gap锁。

**gap锁的提出仅仅是为了防止插入幻影记录而提出的 。虽然有共享gap锁和独占gap锁这样的说法,但是它们起到的作用是相同的。而且如果对一条记录加了gap锁(不论是共享gap锁还是独占gap锁),并不会限制其他事务对这条记录加记录锁或者继续加gap锁。

③ 临键锁(Next-Key Locks)

有时候我们既想锁住某条记录,又想阻止其他事务在该记录前边的间隙插入新记录,所以InnoDB就提出了一种称之为Next-Key Locks的锁,官方的类型名称为:LOCK_ORDINARY,我们也可以简称为next-key锁。Next-Key Locks是在存储引擎innodb、事务级别在可重复读的情况下使用的数据库锁,innodb默认的锁就是Next-Key locks。

begin; select * from student where id3 for update;

④ 插入意向锁(Insert Intention Locks)

我们说一个事务在插入一条记录时需要判断一下插入位置是不是被别的事务加了gap锁(next-key锁也包含gap锁),如果有的话,插入操作需要等待,直到拥有gap锁的那个事务提交。但是**InnoDB规定事务在等待的时候也需要在内存中生成一个锁结构 ,表明有事务想在某个间隙中插入新记录,但是现在在等待。InnoDB就把这种类型的锁命名为Insert Intention Locks,官方的类型名称为:LOCK_INSERT_INTENTION,我们称为插入意向锁。插入意向锁是一种Gap锁,不是意向锁,在insert操作时产生。

插入意向锁是在插入一条记录行前,由INSERT 操作产生的一种间隙锁。

事实上 插入意向锁并不会阻止别的事务继续获取该记录上任何类型的锁。

页锁

页锁就是在页的粒度上进行锁定,锁定的数据资源比行锁要多,因为一个页中可以有多个行记录。当我们使用页锁的时候,会出现数据浪费的现象,但这样的浪费最多也就是一个页上的数据行。 页锁的开销介于表锁和行锁之间,会出现死锁。锁定粒度介于表锁和行锁之间,并发度一般。

每个层级的锁数量是有限制的,因为锁会占用内存空间,锁空间的大小是有限的。当某个层级的锁数量超过了这个层级的阈值时,就会进行锁升级。锁升级就是用更大粒度的锁替代多个更小粒度的锁,比如InnoDB 中行锁升级为表锁,这样做的好处是占用的锁空间降低了,但同时数据的并发度也下降了。

3.3、从对待锁的态度划分:乐观锁、悲观锁

从对待锁的态度来看锁的话,可以将锁分成乐观锁和悲观锁,从名字中也可以看出这两种锁是两种看待数据并发的思维方式。需要注意的是,乐观锁和悲观锁并不是锁,而是锁的设计思想。

悲观锁(Pessimistic Locking)

悲观锁总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁( 共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程 )。比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁,当其他线程想要访问数据时,都需要阻塞挂起。Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。

注意:**select … for update 语句执行过程中所有扫描的行都会被锁上,因此在MySQL中用悲观锁必须确定使用了索引,而不是全表扫描,否则将会把整个表锁住。

乐观锁(Optimistic Locking)

乐观锁认为对同一数据的并发操作不会总发生,属于小概率事件,不用每次都对数据上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,也就是 不采用数据库自身的锁机制,而是通过程序来实现 。在程序上,我们可以采用版本号机制或者CAS机制实现。 乐观锁适用于多读的应用类型,这样可以提高吞吐量 。在Java中java.util.concurrent.atomic包下的原子变量类就是使用了乐观锁的一种实现方式:CAS实现的。

乐观锁的版本号机制

在表中设计一个版本字段 version,第一次读的时候,会获取 version 字段的取值。然后对数据进行更新或删除操作时,会执行UPDATE ... SET version=version+1 WHERE version=version。此时如果已经有事务对这条数据进行了更改,修改就不会成功。

乐观锁的时间戳机制

时间戳和版本号机制一样,也是在更新提交的时候,将当前数据的时间戳和更新之前取得的时间戳进行比较,如果两者一致则更新成功,否则就是版本冲突。

你能看到乐观锁就是程序员自己控制数据并发操作的权限,基本是通过给数据行增加一个戳(版本号或者时间戳),从而证明当前拿到的数据是否最新。

两种锁的适用场景

从这两种锁的设计思想中,我们总结一下乐观锁和悲观锁的适用场景:

乐观锁适合读操作多的场景,相对来说写的操作比较少。它的优点在于程序实现,不存在死锁问题,不过适用场景也会相对乐观,因为它阻止不了除了程序以外的数据库操作。

悲观锁适合写操作多的场景,因为写的操作具有排它性。采用悲观锁的方式,可以在数据库层面阻止其他事务对该数据的操作权限,防止读 - 写和写 - 写的冲突。

3.4、按加锁的方式划分:显式锁、隐式锁隐式锁

情景一: 对于聚簇索引记录来说,有一个trx_id隐藏列,该隐藏列记录着最后改动该记录的事务id。那么如果在当前事务中新插入一条聚簇索引记录后,该记录的trx_id隐藏列代表的的就是当前事务的事务id,如果其他事务此时想对该记录添加S锁或者X锁时,首先会看一下该记录的trx_id隐藏列代表的事务是否是当前的活跃事务,如果是的话,那么就帮助当前事务创建一个X锁(也就是为当前事务创建一个锁结构,is_waiting属性是false),然后自己进入等待状态(也就是为自己也创建一个锁结构,is_waiting属性是true)。

情景二: 对于二级索引记录来说,本身并没有trx_id隐藏列,但是在二级索引页面的Page Header部分有一个PAGE_MAX_TRX_ID属性,该属性代表对该页面做改动的最大的事务id,如果PAGE_MAX_TRX_ID属性值小于当前最小的活跃事务id,那么说明对该页面做修改的事务都已经提交了,否则就需要在页面中定位到对应的二级索引记录,然后回表找到它对应的聚簇索引记录,然后再重复情景一的做法。

即:一个事务对新插入的记录可以不显示的加锁(生成一个锁结构),但是由于事务id的存在,相当于加了一个隐式锁。别的事务在对这条记录加S锁或者X锁时,由于隐式锁的存在,会先帮助当前事务生成一个锁结构,然后自己再生成一个锁结构后进入等待状态。隐式锁是一种延迟加锁的机制,从而来减少加锁的数量。

显式锁

通过特定的语句进行加锁,我们一般称之为显示加锁。

3.5、其它锁之:全局锁

全局锁就是对整个数据库实例加锁。当你需要让整个库处于只读状态的时候,可以使用这个命令,之后其他线程的以下语句会被阻塞:数据更新语句(数据的增删改)、数据定义语句(包括建表、修改表结构等)和更新类事务的提交语句。全局锁的典型使用场景是:做全库逻辑备份。

全局锁的命令:

Flush tables with read lock3.6、其它锁之:死锁

死锁是指两个或多个事务在同一资源上相互占用,并请求锁定对方占用的资源,从而导致恶性循环。

如何处理死锁

方式1: 等待,直到超时(innodb_lock_wait_timeout=50s)

即当两个事务互相等待时,当一个事务等待时间超过设置的阈值时,就将其回滚,另外事务继续进行。

方式2: 使用死锁检测进行死锁处理

发起死锁检测,发现死锁后,主动回滚死锁链条中的某一个事务(将持有最少行级排他锁的事务进行回滚),让其他事务得以继续执行。

4. 锁的内存结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-COchqSka-1661608724731)(https://cdn.jsdelivr.net/gh/aoshihuankong/cloudimg@master/img/202204051514736.png)]

结构解析:

1. 锁所在的事务信息:

不论是表锁还是行锁,都是在事务执行过程中生成的,哪个事务生成了这个 锁结构 ,这里就记录这个事务的信息。

此锁所在的事务信息在内存结构中只是一个指针,通过指针可以找到内存中关于该事务的更多信息,比方说事务id等。

2. 索引信息:

对于行锁来说,需要记录一下加锁的记录是属于哪个索引的。这里也是一个指针。

3. 表锁/行锁信息:

表锁结构和行锁结构在这个位置的内容是不同的:

表锁:记载着是对哪个表加的锁,还有其他的一些信息。行锁:记载了三个重要的信息:Space ID :记录所在表空间。Page Number :记录所在页号。n_bits :对于行锁来说,一条记录就对应着一个比特位,一个页面中包含很多记录,用不同的比特位来区分到底是哪一条记录加了锁。为此在行锁结构的末尾放置了一堆比特位,这个n_bits 属性代表使用了多少比特位。

n_bits的值一般都比页面中记录条数多一些。主要是为了之后在页面中插入了新记录后也不至于重新分配锁结构

4. type_mode:

这是一个32位的数,被分成了lock_mode、lock_type和rec_lock_type三个部分,如图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EF1HEX9D-1661608724731)(https://cdn.jsdelivr.net/gh/aoshihuankong/cloudimg@master/img/202204051518659.png)]

锁的模式(lock_mode),占用低4位,可选的值如下:LOCK_IS(十进制的0):表示共享意向锁,也就是IS锁。LOCK_IX(十进制的1):表示独占意向锁,也就是IX锁。LOCK_S(十进制的2):表示共享锁,也就是S锁。LOCK_X(十进制的3):表示独占锁,也就是X锁。LOCK_AUTO_INC(十进制的4):表示AUTO-INC锁。

在InnoDB存储引擎中,LOCK_IS,LOCK_IX,LOCK_AUTO_INC都算是表级锁的模式,LOCK_S和 LOCK_X既可以算是表级锁的模式,也可以是行级锁的模式。

锁的类型(lock_type),占用第5~8位,不过现阶段只有第5位和第6位被使用:LOCK_TABLE(十进制的16),也就是当第5个比特位置为1时,表示表级锁。LOCK_REC(十进制的32),也就是当第6个比特位置为1时,表示行级锁。行锁的具体类型(rec_lock_type),使用其余的位来表示。只有在 lock_type的值为LOCK_REC时,也就是只有在该锁为行级锁时,才会被细分为更多的类型:LOCK_ORDINARY(十进制的0):表示next-key锁。LOCK_GAP(十进制的512):也就是当第10个比特位置为1时,表示gap锁。LOCK_REC_NOT_GAP(十进制的1024):也就是当第11个比特位置为1时,表示正经记录锁。LOCK_INSERT_INTENTION(十进制的2048):也就是当第12个比特位置为1时,表示插入意向锁。其他的类型:还有一些不常用的类型我们就不多说了。is_waiting属性呢?基于内存空间的节省,所以把 is_waiting 属性放到了 type_mode 这个32位的数字中:LOCK_WAIT(十进制的256) :当第9个比特位置为1时,表示is_waiting为true,也就是当前事务尚未获取到锁,处在等待状态;当这个比特位为0时,表示is_waiting为false,也就是当前事务获取锁成功。

5. 其他信息:

为了更好的管理系统运行过程中生成的各种锁结构而设计了各种哈希表和链表。

6. 一堆比特位:

如果是行锁结构的话,在该结构末尾还放置了一堆比特位,比特位的数量是由上边提到的n_bits属性表示的。InnoDB数据页中的每条记录在记录头信息中都包含一个 heap_no 属性,伪记录Infimum的heap_no值为0,Supremum的heap_no值为1,之后每插入一条记录,heap_no值就增1。锁结构最后的一堆比特位就对应着一个页面中的记录,一个比特位映射一个heap_no,即一个比特位映射到页内的一条记录。

5. 锁监控mysql> show status like 'innodb_row_lock%';Innodb_row_lock_current_waits:当前正在等待锁定的数量;Innodb_row_lock_time:从系统启动到现在锁定总时间长度;(等待总时长)Innodb_row_lock_time_avg:每次等待所花平均时间;(等待平均时长)Innodb_row_lock_time_max:从系统启动到现在等待最常的一次所花的时间;Innodb_row_lock_waits:系统启动后到现在总共等待的次数;(等待总次数)

其他监控方法:

MySQL把事务和锁的信息记录在了information_schema库中,涉及到的三张表分别是INNODB_TRX、INNODB_LOCKS和INNODB_LOCK_WAITS。

MySQL5.7及之前,可以通过information_schema.INNODB_LOCKS查看事务的锁情况,但只能看到阻塞事务的锁;如果事务并未被阻塞,则在该表中看不到该事务的锁情况。

MySQL8.0删除了information_schema.INNODB_LOCKS,添加了performance_schema.data_locks,可以通过performance_schema.data_locks查看事务的锁情况,和MySQL5.7及之前不同,performance_schema.data_locks不但可以看到阻塞该事务的锁,还可以看到该事务所持有的锁。

同时,information_schema.INNODB_LOCK_WAITS也被performance_schema.data_lock_waits所代替。

第16章 多版本并发控制1. 什么是MVCC**

MVCC (Multiversion Concurrency Control),多版本并发控制。顾名思义,MVCC 是通过数据行的多个版本管理来实现数据库的并发控制。这项技术使得在InnoDB的事务隔离级别下执行一致性读操作有了保证。换言之,就是为了查询一些正在被另一个事务更新的行,并且可以看到它们被更新之前的值,这样在做查询的时候就不用等待另一个事务释放锁。

2. 快照读与当前读

MVCC在MySQL InnoDB中的实现主要是为了提高数据库并发性能,用更好的方式去处理读-写冲突,做到即使有读写冲突时,也能做到不加锁,非阻塞并发读,而这个读指的就是快照读, 而非当前读。当前读实际上是一种加锁的操作,是悲观锁的实现。而MVCC本质是采用乐观锁思想的一种方式。

2.1、快照读

快照读又叫一致性读,读取的是快照数据。 不加锁的简单的 **SELECT、都属于快照读 ,即不加锁的非阻塞读。

之所以出现快照读的情况,是基于提高并发性能的考虑,快照读的实现是基于MVCC,它在很多情况下,避免了加锁操作,降低了开销。

既然是基于多版本,那么快照读可能读到的并不一定是数据的最新版本,而有可能是之前的历史版本。

快照读的前提是隔离级别不是串行级别,串行级别下的快照读会退化成当前读。

2.2、当前读

当前读读取的是记录的最新版本(最新数据,而不是历史版本的数据),读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。加锁的 SELECT,或者对数据进行增删改都会进行当前读。

3. 复习3.1、再谈隔离级别

我们知道事务有 4 个隔离级别,可能存在三种并发问题:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-q8DXskeO-1661608724732)(https://cdn.jsdelivr.net/gh/aoshihuankong/cloudimg@master/img/202204051536648.png)]

另图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e6x0cuWY-1661608724732)(https://cdn.jsdelivr.net/gh/aoshihuankong/cloudimg@master/img/202204051536125.png)]

3.2、隐藏字段、Undo Log版本链

回顾一下undo日志的版本链,对于使用InnoDB存储引擎的表来说,它的聚簇索引记录中都包含两个必要的隐藏列。

trx_id:每次一个事务对某条聚簇索引记录进行改动时,都会把该事务的事务id赋值给trx_id 隐藏列。roll_pointer:每次对某条聚簇索引记录进行改动时,都会把旧的版本写入到 undo日志 中,然后这个隐藏列就相当于一个指针,可以通过它来找到该记录修改前的信息。4. MVCC实现原理之ReadView**

MVCC 的实现依赖于: 隐藏字段、Undo Log、Read View**。

4.1、什么是ReadView**

ReadView就是事务在使用MVCC机制进行快照读操作时产生的读视图。当事务启动时,会生成数据库系统当前的一个快照,InnoDB为每个事务构造了一个数组,用来记录并维护系统当前活跃事务的ID(“活跃”指的就是,启动了但还没提交)。

4.2、设计思路

使用READ UNCOMMITTED隔离级别的事务,由于可以读到未提交事务修改过的记录,所以直接读取记录的最新版本就好了。

使用SERIALIZABLE隔离级别的事务,InnoDB规定使用加锁的方式来访问记录。

使用READ COMMITTED和REPEATABLE READ隔离级别的事务,都必须保证读到已经提交了的事务修改过的记录。假如另一个事务已经修改了记录但是尚未提交,是不能直接读取最新版本的记录的,核心问题就是需要判断一下版本链中的哪个版本是当前事务可见的,这是ReadView要解决的主要问题。

这个ReadView中主要包含4个比较重要的内容,分别如下:

creator_trx_id,创建这个 Read View 的事务 ID。

说明:只有在对表中的记录做改动时(执行INSERT、DELETE、UPDATE这些语句时)才会为事务分配事务id,否则在一个只读事务中的事务id值都默认为0。

trx_ids,表示在生成ReadView时当前系统中活跃的读写事务的事务id列表。

up_limit_id,活跃的事务中最小的事务 ID。

low_limit_id,表示生成ReadView时系统中应该分配给下一个事务的id值。low_limit_id 是系统最大的事务id值,这里要注意是系统中的事务id,需要区别于正在活跃的事务ID。

注意:low_limit_id并不是trx_ids中的最大值,事务id是递增分配的。比如,现在有id为1, 2,3这三个事务,之后id为3的事务提交了。那么一个新的读事务在生成ReadView时,trx_ids就包括1和2,up_limit_id的值就是1,low_limit_id的值就是4。

4.3 ReadView的规则

有了这个ReadView,这样在访问某条记录时,只需要按照下边的步骤判断记录的某个版本是否可见。

如果被访问版本的trx_id属性值与ReadView中的creator_trx_id值相同,意味着当前事务在访问它自己修改过的记录,所以该版本可以被当前事务访问。如果被访问版本的trx_id属性值小于ReadView中的up_limit_id值,表明生成该版本的事务在当前事务生成ReadView前已经提交,所以该版本可以被当前事务访问。如果被访问版本的trx_id属性值大于或等于ReadView中的low_limit_id值,表明生成该版本的事务在当前事务生成ReadView后才开启,所以该版本不可以被当前事务访问。如果被访问版本的trx_id属性值在ReadView的up_limit_id和low_limit_id之间,那就需要判断一下trx_id属性值是不是在 trx_ids 列表中。如果在,说明创建ReadView时生成该版本的事务还是活跃的,该版本不可以被访问。如果不在,说明创建ReadView时生成该版本的事务已经被提交,该版本可以被访问。4.4 MVCC整体操作流程

了解了这些概念之后,我们来看下当查询一条记录的时候,系统如何通过MVCC找到它:

首先获取事务自己的版本号,也就是事务 ID;

获取 ReadView;

查询得到的数据,然后与 ReadView 中的事务版本号进行比较;

如果不符合 ReadView 规则,就需要从 Undo Log 中获取历史快照;

最后返回符合规则的数据。

在隔离级别为读已提交(Read Committed)时,一个事务中的每一次 SELECT 查询都会重新获取一次Read View。

如表所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sLf75PE9-1661608724732)(https://cdn.jsdelivr.net/gh/aoshihuankong/cloudimg@master/img/202204051549618.png)]

注意,此时同样的查询语句都会重新获取一次 Read View,这时如果 Read View 不同,就可能产生不可重复读或者幻读的情况。

当隔离级别为可重复读的时候,就避免了不可重复读,这是因为一个事务只在第一次 SELECT 的时候会获取一次 Read View,而后面所有的 SELECT 都会复用这个 Read View,如下表所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yEK0sIzr-1661608724733)(https://cdn.jsdelivr.net/gh/aoshihuankong/cloudimg@master/img/202204051550072.png)]

5. 举例说明5.1 READ COMMITTED隔离级别下

READ COMMITTED、:每次读取数据前都生成一个ReadView。

5.2 REPEATABLE READ隔离级别下

使用REPEATABLE READ隔离级别的事务来说,只会在第一次执行查询语句时生成一个 ReadView ,之后的查询就不会重复生成了。

5.3、如何解决幻读

假设现在表 student 中只有一条数据,数据内容中,主键 id=1,隐藏的 trx_id=10,它的 undo log 如下图所示。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JRo14W5B-1661608724733)(https://cdn.jsdelivr.net/gh/aoshihuankong/cloudimg@master/img/202204051556631.png)]

假设现在有事务 A 和事务 B 并发执行,事务 A的事务 id 为20,事务 B的事务 id 为30。

步骤1:事务 A 开始第一次查询数据,查询的 SQL 语句如下。

select * from student where id >= 1;

在开始查询之前,MySQL 会为事务 A 产生一个 ReadView,此时 ReadView 的内容如下:trx_ids= [20,30],up_limit_id=20,low_limit_id=31,creator_trx_id=20。

由于此时表 student 中只有一条数据,且符合 where id>=1 条件,因此会查询出来。然后根据 ReadView机制,发现该行数据的trx_id=10,小于事务 A 的 ReadView 里 up_limit_id,这表示这条数据是事务 A 开启之前,其他事务就已经提交了的数据,因此事务 A 可以读取到。

结论:事务 A 的第一次查询,能读取到一条数据,id=1。

步骤2:接着事务 B(trx_id=30),往表 student 中新插入两条数据,并提交事务。

insert into student(id,name) values(2,'李四'); insert into student(id,name) values(3,'王五');

此时表student 中就有三条数据了,对应的 undo 如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HPOAtAJK-1661608724734)(https://cdn.jsdelivr.net/gh/aoshihuankong/cloudimg@master/img/202204051559345.png)]

步骤3:接着事务 A 开启第二次查询,根据可重复读隔离级别的规则,此时事务 A 并不会再重新生成ReadView。此时表 student 中的 3 条数据都满足 where id>=1 的条件,因此会先查出来。然后根据ReadView 机制,判断每条数据是不是都可以被事务 A 看到。

1)首先 id=1 的这条数据,前面已经说过了,可以被事务 A 看到。

2)然后是 id=2 的数据,它的 trx_id=30,此时事务 A 发现,这个值处于 up_limit_id 和 low_limit_id 之间,因此还需要再判断 30 是否处于 trx_ids 数组内。由于事务 A 的 trx_ids=[20,30],因此在数组内,这表示 id=2 的这条数据是与事务 A 在同一时刻启动的其他事务提交的,所以这条数据不能让事务 A 看到。

3)同理,id=3 的这条数据,trx_id 也为 30,因此也不能被事务 A 看见。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ngfCVL9b-1661608724734)(https://cdn.jsdelivr.net/gh/aoshihuankong/cloudimg@master/img/202204051559867.png)]

结论:最终事务 A 的第二次查询,只能查询出 id=1 的这条数据。这和事务 A 的第一次查询的结果是一样的,因此没有出现幻读现象,所以说在 MySQL 的可重复读隔离级别下,不存在幻读问题。

6. 总结

这里介绍了MVCC在READ COMMITTD、REPEATABLE READ这两种隔离级别的事务在执行快照读操作时访问记录的版本链的过程。这样使不同事务的读-写、写-读操作并发执行,从而提升系统性能。

核心点在于 ReadView 的原理,READ COMMITTD、REPEATABLE READ这两个隔离级别的一个很大不同就是生成ReadView的时机不同:

READ COMMITTD在每一次进行普通SELECT操作前都会生成一个ReadViewREPEATABLE READ只在第一次进行普通SELECT操作前生成一个ReadView,之后的查询操作都重复使用这个ReadView就好了。第17章 其它数据库日志1. MySQL支持的日志1.1、日志类型

MySQL有不同类型的日志文件,用来存储不同类型的日志,分为二进制日志、错误日志、通用查询日志和慢查询日志,这也是常用的4种。MySQL 8又新增两种支持的日志:中继日志和数据定义语句日志。使用这些日志文件,可以查看MySQL内部发生的事情。

慢查询日志: 记录所有执行时间超过long_query_time的所有查询,方便我们对查询进行优化。

通用查询日志: 记录所有连接的起始时间和终止时间,以及连接发送给数据库服务器的所有指令,对我们复原操作的实际场景、发现问题,甚至是对数据库操作的审计都有很大的帮助。

错误日志: 记录MySQL服务的启动、运行或停止MySQL服务时出现的问题,方便我们了解服务器的状态,从而对服务器进行维护。

二进制日志: 记录所有更改数据的语句,可以用于主从服务器之间的数据同步,以及服务器遇到故障时数据的无损失恢复。

中继日志: 用于主从服务器架构中,从服务器用来存放主服务器二进制日志内容的一个中间文件。从服务器通过读取中继日志的内容,来同步主服务器上的操作。

数据定义语句日志: 记录数据定义语句执行的元数据操作。

除二进制日志外,其他日志都是文本文件。默认情况下,所有日志创建于MySQL数据目录中。

1.2、日志的弊端

日志功能会降低MySQL数据库的性能。

日志会占用大量的磁盘空间。

2. 通用查询日志(general query log)

通用查询日志用来记录用户的所有操作,包括启动和关闭MySQL服务、所有用户的连接开始时间和截止时间、发给 MySQL 数据库服务器的所有 SQL 指令等。当我们的数据发生异常时, 查看通用查询日志,还原操作时的具体场景 ,可以帮助我们准确定位问题。

2.1、查看当前状态mysql> SHOW VARIABLES LIKE '%general%';2.2、启动日志

方式1:永久性方式

[mysqld] general_log=ON general_log_file=[path[filename]] #日志文件所在目录路径,filename为日志文件名

方式2:临时性方式

SET GLOBAL general_log=on; # 开启通用查询日志SET GLOBAL general_log_file=’path/filename’; # 设置日志文件保存位置SET GLOBAL general_log=off; # 关闭通用查询日志SHOW VARIABLES LIKE 'general_log%'; # 查看设置后情况2.3、停止日志

方式1:永久性方式

[mysqld] general_log=OFF

方式2:临时性方式

SET GLOBAL general_log=off;SHOW VARIABLES LIKE 'general_log%';3.错误日志(error log)3.1、启动日志

在MySQL数据库中,错误日志功能是默认开启的。而且,错误日志无法被禁止。

[mysqld] log-error=[path/[filename]] #path为日志文件所在的目录路径,filename为日志文件名3.2、查看日志mysql> SHOW VARIABLES LIKE 'log_err%';3.3、删除\刷新日志install -omysql -gmysql -m0644 /dev/null /var/log/mysqld.logmysqladmin -uroot -p flush-logs4. 二进制日志(bin log)4.1、查看默认情况mysql> show variables like '%log_bin%';4.2、日志参数设置

方式1:永久性方式

[mysqld] #启用二进制日志 log-bin=atguigu-bin binlog_expire_logs_seconds=600 max_binlog_size=100M

设置带文件夹的bin-log日志存放目录

[mysqld] log-bin="/var/lib/mysql/binlog/atguigu-bin"

注意:新建的文件夹需要使用mysql用户,使用下面的命令即可。

chown -R -v mysql:mysql binlog

方式2:临时性方式

# global 级别 mysql> set global sql_log_bin=0; ERROR 1228 (HY000): Variable 'sql_log_bin' is a SESSION variable and can`t be used with SET GLOBAL # session级别 mysql> SET sql_log_bin=0; Query OK, 0 rows affected (0.01 秒)4.3、查看日志mysqlbinlog -v "/var/lib/mysql/binlog/atguigu-bin.000002"# 不显示binlog格式的语句mysqlbinlog -v --base64-output=DECODE-ROWS "/var/lib/mysql/binlog/atguigu-bin.000002"# 可查看参数帮助 mysqlbinlog --no-defaults --help # 查看最后100行 mysqlbinlog --no-defaults --base64-output=decode-rows -vv atguigu-bin.000002 |tail -100 # 根据position查找 mysqlbinlog --no-defaults --base64-output=decode-rows -vv atguigu-bin.000002 |grep -A20 '4939002'

上面这种办法读取出binlog日志的全文内容比较多,不容易分辨查看到pos点信息,下面介绍一种更为方便的查询命令:

mysql> show binlog events [IN 'log_name'] [FROM pos] [LIMIT [offset,] row_count];IN 'log_name':指定要查询的binlog文件名(不指定就是第一个binlog文件)FROM pos:指定从哪个pos起始点开始查起(不指定就是从整个文件首个pos点开始算)LIMIT [offset]:偏移量(不指定就是0)row_count:查询总条数(不指定就是所有行)mysql> show binlog events in 'atguigu-bin.000002';4.4、使用日志恢复数据

mysqlbinlog恢复数据的语法如下:

mysqlbinlog [option] filename|mysql –uuser -ppass;filename:是日志文件名。option:可选项,比较重要的两对option参数是–start-date、–stop-date 和 --start-position、-- stop-position。--start-date 和 --stop-date:可以指定恢复数据库的起始时间点和结束时间点。--start-position和--stop-position:可以指定恢复数据的开始位置和结束位置。

注意:使用mysqlbinlog命令进行恢复操作时,必须是编号小的先恢复,例如atguigu-bin.000001必须在atguigu-bin.000002之前恢复。

4.5、删除二进制日志PURGE MASTER LOGS:删除指定日志文件PURGE {MASTER | BINARY} LOGS TO ‘指定日志文件名’ PURGE {MASTER | BINARY} LOGS BEFORE ‘指定日期’5. 再谈二进制日志(binlog)5.1、写入机制

binlog的写入时机也非常简单,事务执行过程中,先把日志写到binlog cache,事务提交的时候,再把binlog cache写到binlog文件中。因为一个事务的binlog不能被拆开,无论这个事务多大,也要确保一次性写入,所以系统会给每个线程分配一个块内存作为binlog cache。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V7hQZpvJ-1661608724735)(https://cdn.jsdelivr.net/gh/aoshihuankong/cloudimg@master/img/202204051630535.png)]

write和fsync的时机,可以由参数sync_binlog控制,默认是 0。为0的时候,表示每次提交事务都只write,由系统自行判断什么时候执行fsync。虽然性能得到提升,但是机器宕机,page cache里面的binglog 会丢失。如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z5O1q7L9-1661608724735)(https://cdn.jsdelivr.net/gh/aoshihuankong/cloudimg@master/img/202204051631346.png)]

为了安全起见,可以设置为1,表示每次提交事务都会执行fsync,就如同**redo log、刷盘流程 一样。最后还有一种折中方式,可以设置为N(N>1),表示每次提交事务都write,但累积N个事务后才fsync。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eHbPR7I0-1661608724736)(https://cdn.jsdelivr.net/gh/aoshihuankong/cloudimg@master/img/202204051632526.png)]

在出现IO瓶颈的场景里,将sync_binlog设置成一个比较大的值,可以提升性能。同样的,如果机器宕机,会丢失最近N个事务的binlog日志。

5.2 binlog与redolog对比redo log 它是物理日志,记录内容是“在某个数据页上做了什么修改”,属于 InnoDB 存储引擎层产生的。而 binlog 是逻辑日志,记录内容是语句的原始逻辑,类似于“给 ID=2 这一行的 c 字段加 1”,属于MySQL Server 层。虽然它们都属于持久化的保证,但是侧重点不同。redo log 让InnoDB存储引擎拥有了崩溃恢复能力。binlog保证了MySQL集群架构的数据一致性5.3、两阶段提交

在执行更新语句过程,会记录redo log与binlog两块日志,以基本的事务为单位,redo log在事务执行过程中可以不断写入,而binlog只有在提交事务时才写入,所以redo log与binlog的写入时机不一样。

为了解决两份日志之间的逻辑一致问题,InnoDB存储引擎使用 两阶段提交 方案。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OESavHVq-1661608724736)(https://cdn.jsdelivr.net/gh/aoshihuankong/cloudimg@master/img/202204051637390.png)]

使用 两阶段提交 后,写入binlog时发生异常也不会有影响

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-h7PqS0DH-1661608724737)(https://cdn.jsdelivr.net/gh/aoshihuankong/cloudimg@master/img/202204051639192.png)]

另一个场景,redo log设置commit阶段发生异常,那会不会回滚事务呢?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1SdwvqW9-1661608724737)(https://cdn.jsdelivr.net/gh/aoshihuankong/cloudimg@master/img/202204051639403.png)]

并不会回滚事务,它会执行上图框住的逻辑,虽然redo log是处于prepare阶段,但是能通过事务id找到对应的binlog日志,所以MySQL认为是完整的,就会提交事务恢复数据。

6. 中继日志(relay log)6.1、介绍

中继日志只在主从服务器架构的从服务器上存在 。从服务器为了与主服务器保持一致,要从主服务器读取二进制日志的内容,并且把读取到的信息写入本地的日志文件中,这个从服务器本地的日志文件就叫中继日志。然后,从服务器读取中继日志,并根据中继日志的内容对从服务器的数据进行更新,完成主从服务器的数据同步。

6.2、恢复的典型错误

如果从服务器宕机,有的时候为了系统恢复,要重装操作系统,这样就可能会导致你的服务器名称与之前不同。而中继日志里是包含从服务器名的。在这种情况下,就可能导致你恢复从服务器的时候,无法从宕机前的中继日志里读取数据,以为是日志文件损坏了,其实是名称不对了。

解决的方法也很简单,把从服务器的名称改回之前的名称。

第18章 主从复制1. 主从复制概述1.1、如何提升数据库并发能力

一般应用对数据库而言都是“读多写少”,也就说对数据库读取数据的压力比较大,有一个思路就是采用数据库集群的方案,做主从架构、进行读写分离,这样同样可以提升数据库的并发处理能力。但并不是所有的应用都需要对数据库进行主从架构的设置,毕竟设置架构本身是有成本的。

如果我们的目的在于提升数据库高并发访问的效率,那么首先考虑的是如何优化SQL和索引,这种方式简单有效;其次才是采用缓存的策略,比如使用 Redis将热点数据保存在内存数据库中,提升读取的效率;最后才是对数据库采用主从架构,进行读写分离。

1.2、主从复制的作用

第1个作用:读写分离。

第2个作用就是数据备份。

第3个作用是具有高可用性。

2. 主从复制的原理2.1、原理剖析

三个线程

实际上主从同步的原理就是基于 binlog 进行数据同步的。在主从复制过程中,会基于3 个线程来操作,一个主库线程,两个从库线程。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ozg3BtR9-1661608724737)(https://cdn.jsdelivr.net/gh/aoshihuankong/cloudimg@master/img/202204051646097.png)]

二进制日志转储线程(Binlog dump thread)是一个主库线程。当从库线程连接的时候, 主库可以将二进制日志发送给从库,当主库读取事件(Event)的时候,会在 Binlog 上加锁,读取完成之后,再将锁释放掉。

从库 I/O 线程会连接到主库,向主库发送请求更新 Binlog。这时从库的 I/O 线程就可以读取到主库的二进制日志转储线程发送的 Binlog 更新部分,并且拷贝到本地的中继日志 (Relay log)。

从库 SQL 线程会读取从库中的中继日志,并且执行日志中的事件,将从库中的数据与主库保持同步。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7WFjYIcV-1661608724738)(https://cdn.jsdelivr.net/gh/aoshihuankong/cloudimg@master/img/202204051647759.png)]

复制三步骤

步骤1:Master将写操作记录到二进制日志(binlog)。

步骤2:Slave将Master的binary log events拷贝到它的中继日志(relay log);

步骤3:Slave重做中继日志中的事件,将改变应用到自己的数据库中。 MySQL复制是异步的且串行化的,而且重启后从接入点开始复制。

复制的问题

复制的最大问题:延时

2.2、复制的基本原则

每个Slave只有一个Master

每个Slave只能有一个唯一的服务器ID

每个Master可以有多个Slave

3. 同步数据一致性问题

主从同步的要求:

读库和写库的数据一致(最终一致);

写数据必须写到写库;

读数据必须到读库(不一定);

3.1、理解主从延迟问题

进行主从同步的内容是二进制日志,它是一个文件,在进行网络传输的过程中就一定会存在主从延迟(比如 500ms),这样就可能造成用户在从库上读取的数据不是最新的数据,也就是主从同步中的数据不一致性问题。

3.2、主从延迟问题原因

在网络正常的时候,日志从主库传给从库所需的时间是很短的,即T2-T1的值是非常小的。即,网络正常情况下,主备延迟的主要来源是备库接收完binlog和执行完这个事务之间的时间差。

主备延迟最直接的表现是,从库消费中继日志(relay log)的速度,比主库生产binlog的速度要慢。 造成原因:

1、从库的机器性能比主库要差

2、从库的压力大

3、大事务的执行

3.3、如何减少主从延迟

若想要减少主从延迟的时间,可以采取下面的办法:

降低多线程大事务并发的概率,优化业务逻辑

优化SQL,避免慢SQL,减少批量操作,建议写脚本以update-sleep这样的形式完成。

提高从库机器的配置,减少主库写binlog和从库读binlog的效率差。

尽量采用短的链路,也就是主库和从库服务器的距离尽量要短,提升端口带宽,减少binlog传输的网络延时。

实时性要求的业务读强制走主库,从库只做灾备,备份。

3.4、如何解决一致性问题

读写分离情况下,解决主从同步中数据不一致的问题, 就是解决主从之间 数据复制方式 的问题,如果按照数据一致性 从弱到强 来进行划分,有以下 3 种复制方式。

方法 1:异步复制

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NWHx1OJB-1661608724738)(https://cdn.jsdelivr.net/gh/aoshihuankong/cloudimg@master/img/202204051654133.png)]

方法 2:半同步复制

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TKqEyo1p-1661608724739)(https://cdn.jsdelivr.net/gh/aoshihuankong/cloudimg@master/img/202204051655175.png)]

方法 3:组复制

首先我们将多个节点共同组成一个复制组,在执行读写(RW)事务的时候,需要通过一致性协议层(Consensus 层)的同意,也就是读写事务想要进行提交,必须要经过组里“大多数人”(对应 Node 节点)的同意,大多数指的是同意的节点数量需要大于 (N/2+1),这样才可以进行提交,而不是原发起方一个说了算。而针对只读(RO)事务则不需要经过组内同意,直接 COMMIT 即可。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-A8sNgGU4-1661608724739)(https://cdn.jsdelivr.net/gh/aoshihuankong/cloudimg@master/img/202204051656560.png)]

第19章 数据库备份与恢复1. 物理备份与逻辑备份

物理备份 :备份数据文件,转储数据库物理文件到某一目录。物理备份恢复速度比较快,但占用空间比较大,MySQL中可以用xtrabackup工具来进行物理备份。

逻辑备份 :对数据库对象利用工具进行导出工作,汇总入备份文件内。逻辑备份恢复速度慢,但占用空间小,更灵活。MySQL 中常用的逻辑备份工具为mysqldump。逻辑备份就是备份sql语句,在恢复的时候执行备份的sql语句实现数据库数据的重现。

2. mysqldump实现逻辑备份2.1、备份一个数据库mysqldump –u 用户名称 –h 主机名称 –p密码 待备份的数据库名称[tbname, [tbname...]]> 备份文件名 称.sqlmysqldump -uroot -p atguigu>atguigu.sql #备份文件存储在当前目录下mysqldump -uroot -p atguigudb1 > /var/lib/mysql/atguigu.sql2.2、备份全部数据库mysqldump -uroot -pxxxxxx --all-databases > all_database.sql mysqldump -uroot -pxxxxxx -A > all_database.sql2.3、备份部分数据库mysqldump –u user –h host –p --databases [数据库的名称1 [数据库的名称2...]] > 备份文件名 称.sqlmysqldump -uroot -p --databases atguigu atguigu12 >two_database.sqlmysqldump -uroot -p -B atguigu atguigu12 > two_database.sql2.4、备份部分表mysqldump –u user –h host –p 数据库的名称 [表名1 [表名2...]] > 备份文件名称.sqlmysqldump -uroot -p atguigu book> book.sql#备份多张表 mysqldump -uroot -p atguigu book account > 2_tables_bak.sql2.5、备份单表的部分数据mysqldump -uroot -p atguigu student --where="id < 10 " > student_part_id10_low_bak.sql2.6、排除某些表的备份mysqldump -uroot -p atguigu --ignore-table=atguigu.student > no_stu_bak.sql2.7、只备份结构或只备份数据只备份结构mysqldump -uroot -p atguigu --no-data > atguigu_no_data_bak.sql只备份数据mysqldump -uroot -p atguigu --no-create-info > atguigu_no_create_info_bak.sql2.8、备份中包含存储过程、函数、事件mysqldump -uroot -p -R -E --databases atguigu > fun_atguigu_bak.sql3. mysql命令恢复数据mysql –u root –p [dbname]source class_structure.sql; Query OK, 0 rows affected, 1 warning (0.00 sec) mysql> source class_data.sql; Query OK, 1 row affected (0.01 sec)4. 表的导出与导入4.1、表的导出使用SELECT…INTO OUTFILE导出文本文件SHOW GLOBAL VARIABLES LIKE '%secure%';SELECT * FROM account INTO OUTFILE "/var/lib/mysql-files/account.txt";使用mysqldump命令导出文本文件mysqldump -uroot -p -T "/var/lib/mysql-files/" atguigu account# 或mysqldump -uroot -p -T "/var/lib/mysql-files/" atguigu account --fields-terminated- by=',' --fields-optionally-enclosed-by='\"'使用mysql命令导出文本文件mysql -uroot -p --execute="SELECT * FROM account;" atguigu> "/var/lib/mysql-files/account.txt"4.2、表的导入使用LOAD DATA INFILE方式导入文本文件LOAD DATA INFILE '/var/lib/mysql-files/account_0.txt' INTO TABLE atguigu.account;# 或LOAD DATA INFILE '/var/lib/mysql-files/account_1.txt' INTO TABLE atguigu.account FIELDS TERMINATED BY ',' ENCLOSED BY '\"';使用mysqlimport方式导入文本文件mysqlimport -uroot -p atguigu '/var/lib/mysql-files/account.txt' --fields-terminated- by=',' --fields-optionally-enclosed-by='\"'
启动mysql服务所使用的命令是通过dos命令启动mysql服务启动mysql服务的指令是通过windows服务管理器启动mysql服务通过命令行打开mysql通过命令行启动mysql命令行开启mysql服务如何通过命令行打开mysql可以通过命令来启动mysql服务可以通过什么命令来启动mysql服务用于启动MySQL服务的命令为( )启动mysql服务的方法用命令行启动mysql命令行启动mysql服务启动不了启动MySQL服务的命令启动mysql服务命令 linux启动MySQL的命令启动mysql的命令文鸯七进七出是多少集同源重组克隆引物设计方法废旧轮胎综合利用黄花蟛蜞草的功效与作用此生若能安逸,谁愿颠沛流离雪中悍刀行电视剧27上下篇崇祯十七年十一月初 朱由检白酒的品牌排行榜国家游泳冠军运动员是谁记忆训练卡片对应的数字大主宰免费阅读完结小说