This commit is contained in:
luoxiang 2019-08-19 08:00:25 +08:00
parent d43b22578c
commit 283f13b371
17 changed files with 734 additions and 54 deletions

View File

@ -0,0 +1,181 @@
# PXC 集群搭建
## 一、PXC 集群
## 二、创建PXC集群
### 2.1 准备安装
**1. 删除 MariaDB**
1.这里我的服务器版本为 Centos 7.x ,需要删除自带的 MariaDB 数据库,避免对后面安装造成影响:
```shell
yum -y remove mari*
```
**2. 开放访问墙端口**
开放以下四个防火墙端口,其作用和开放命令分别如下:
```shell
# MySQL 对外提供服务的端口
firewall-cmd --zone=public --add-port=3306/tcp --permanent
# 请求进行全量同步(SST)的端口
firewall-cmd --zone=public --add-port=4444/tcp --permanent
# 集群中各个节点间的通讯端口
firewall-cmd --zone=public --add-port=4567/tcp --permanent
# 请求进行增量同步(IST)的端口
firewall-cmd --zone=public --add-port=4568/tcp --permanent
```
**3. 关闭SELINUX**
SELinux 安全模块可能会限制对 Percona XtraDB Cluster 的数据访问。此时可以在运行时使用 `setenforce 0` 命令进行更改,也可以通过修改 `/etc/selinux/config` 文件中的 SELINUX 属性的值为 disabled ,从而进行永久性的更改:
```shell
vi /etc/selinux/config
# 修改
SELINUX=disabled
```
修改该文件后需要重启才能生效:
```shell
reboot
```
### 2.2 在线安装
配置 yum 源后,直接使用 yum 命令进行在线安装,命令如下:
```shell
sudo yum install https://repo.percona.com/yum/percona-release-latest.noarch.rpm
sudo yum install Percona-XtraDB-Cluster-57
```
安装完成后,通过以下命令启动数据库服务:
```shell
sudo systemctl start mysql
```
### 2.3 修改密码
使用以下命令获取安装时自动生成的 root 账户的密码:
```shell
sudo grep 'temporary password' /var/log/mysqld.log
```
使用 root 账户登录,并修改其账户密码:
```shell
# 登录服务
mysql -u root -p
# 修改密码
mysql> ALTER USER 'root'@'localhost' IDENTIFIED BY '123456';
# 退出服务
mysql> exit
```
之后使用以下命令停止 MySQL 服务:
```shell
sudo systemctl stop mysql
```
### 2.4 搭建集群
以上安装过程在三台主机上均相同执行,之后就可以开始集群的搭建。首先需要修改每个服务器上的 `/etc/my.cnf` 文件,第一个节点上需要增加的内容如下:
```properties
# PXC集群中每个MySQL实例的唯一标识不能重复
server-id=1
wsrep_provider=/usr/lib64/galera3/libgalera_smm.so
# PXC集群的名称和所有服务地址
wsrep_cluster_name=pxc-cluster
wsrep_cluster_address=gcomm://192.168.0.226,192.168.0.227,192.168.0.228
# 当前节点的名称和服务地址
wsrep_node_name=pxc1
wsrep_node_address=192.168.0.226
# 指定同步方法和账户信息,这个用户在下文会进行创建
wsrep_sst_method=xtrabackup-v2
wsrep_sst_auth= sstuser:123456
#开启严厉模式,它会阻止用户执行 Percona XtraDB Cluster 所不支持的功能。
pxc_strict_mode=ENFORCING
# 指定二进制日志的格式
binlog_format=ROW
# 指定默认的存储引擎
default_storage_engine=InnoDB
# Galera 仅支持 InnoDB 的 interleaved(2) 锁定模式:
# 该模式下所有 INSERT SQL 都不会有表级 AUTO-INC 锁,多个语句可以同时执行
innodb_autoinc_lock_mode=2
```
第二、三个节点的配置与上面基本相同,但是需要修改 server id 以及当前节点的名称和地址,具体如下:
```properties
server-id=2
wsrep_node_name=pxc2
wsrep_node_address=192.168.0.227
```
```properties
server-id=3
wsrep_node_name=pxc3
wsrep_node_address=192.168.0.228
```
### 2.5 启动集群
第一个节点需要以引导模式启动:
```shell
sudo systemctl start mysql@bootstrap.service
```
在将其他节点添加到群集之前,需要登录当前节点,来为 SST 创建用户并提供权限,命令如下:
```shell
# 创建用户
CREATE USER 'sstuser'@'localhost' IDENTIFIED BY '123456';
# 授予权限
GRANT RELOAD, LOCK TABLES, PROCESS, REPLICATION CLIENT ON *.* TO 'sstuser'@'localhost';
# 刷新权限
FLUSH PRIVILEGES;
```
接着在第二和第三个节点上正常启动数据库服务,命令如下:
```shell
sudo systemctl start mysql
```
启动后,可以登录任意节点并使用以下命令查看集群状态。输出中的 wsrep_cluster_size 的值应该为 3 ,即集群中节点总数为 3此时代表所有节点都已成功加入集群至此集群已经搭建完成。
```shell
show status like 'wsrep%';
```
### 2.6 节点下线
PXC 集群允许动态下线节点,但需要注意的是节点的启动命令和关闭命令必须一致,如以引导模式启动的第一个节点必须以引导模式来进行关闭:
```shell
systemctl stop mysql@bootstrap.service
```
其他节点则可以按照正常方式关闭:
```shell
service stop mysql
```
由于所有节点都是对等的,所以下线第一个节点和下线其他节点在效果上都是相同的。

View File

@ -1,29 +1,44 @@
# MySQL 基础
## 一、常见存储引擎
### 1.1 InnoDB
InnoDB 是 MySQL 5.5 之后默认的存储引擎,它是一种兼具高可靠和高性能的存储引擎,主要具备以下优势:
+ DML 操作完全遵循 ACID 模型,支持事务,支持崩溃恢复,能够极大的保护用户的数据安全;
+ InnoDB 支持多版本并发控制,它会保存旧版本的数据信息,以支持并发和事务的回滚;
+ 支持行级锁,支持类似 Oracle 的一致性读的特性,从而可以承受高并发地访问;
+ InnoDB 组织数据时默认按照主键进行聚簇从而可以提高主键查找的效率。对于频繁访问的数据InnoDB 还会为其自建立哈希索引,从而提高等值查询的效率,这称为自适应哈希索引。
+ InnoDB 基于磁盘进行存储,所有存储记录按**页**的方式进行管理。为弥补 CPU 速度与磁盘速度之间的鸿沟InnoDB 引用缓存池 (Buffer Pool) 来提高数据的整体性能。查询时,会将目标页读取缓存中;修改时,会先修改缓冲池中的页,然后再遵循 CheckPoint 机制将页刷回磁盘。所有缓存页通过最近最少使用原则 ( LRU ) 来进行定期清理。
+ InnoDB 支持两次写 (DoubleWrite) ,从而可以保证数据的安全,提高系统的可靠性。
一个 InnoDB 引擎完整的内存结构和磁盘结构如下图所示:
![innodb-architecture](D:\Full-Stack-Notes\pictures\innodb-architecture.png)
### 1.2 MyISAM
MyISAM 是 MySQL 5.5 之前默认的存储引擎。在创建 MyISAM 表时会创建两个同名的文件:
MyISAM 是 MySQL 5.5 之前默认的存储引擎。创建 MyISAM 表时会创建两个同名的文件:
+ 扩展名为 `.MYD``MYData`):用于存储表数据;
+ 扩展名为 `.MYI` `MYIndex` 用于存储表的索引信息。
在 MySQL 8.0 之后,就只会创建上述两个同名文件,这时表结构的定义存储在 MySQL 数据字典中,但在 MySQL 8.0 之前,还会存在一个扩展名为 `.frm` 的扩展文件用于存储表结构信息。MyISAM 与 InnoDB 主要的区别其只支持表级锁,不支持行级锁,不支持事务。
在 MySQL 8.0 之后,只会创建上述两个同名文件,因为在该版本中表结构的定义存储在 MySQL 数据字典中,但在 MySQL 8.0 之前,还会存在一个扩展名为 `.frm` 的扩展文件用于存储表结构信息。MyISAM 与 InnoDB 主要的区别其只支持表级锁,不支持行级锁,不支持事务,不支持自动崩溃恢复,但可以使用内置的 mysqlcheck 和 myisamchk 工具来进行检查和修复
### 1.3 MEMORY
MEMORY 存储引擎(又称为 HEAP)会将表中的数据存储在内存中,它具有以下特征:
MEMORY 存储引擎(又称为 HEAP 存储引擎)通常用于将表中的数据存储在内存中,它具有以下特征:
- MEMORY 表的表定义信息存储在 MySQL 数据字典中,而实际的数据则存储在内存空间中,并以块为单位进行划分;因此当服务器重启后,表本身并不会被删除,只是表中的所有数据都会丢失。
- MEMORY 存储引擎支持 HASH 索引和 BTREE 索引,默认采用 HASH 索引。
- MEMORY 存储引擎支持 HASH 索引和 BTREE 索引,默认采用 HASH 索引。
- MEMORY 表使用固定长度的行存储格式,即便是 VARCHAR 类型也会使用固定长度进行存储。
- MEMORY 支持 AUTO_INCREMENT 列,但不支持 BLOB 列或 TEXT 列。
- MEMORY 表和 MySQL 内部临时表的区别在于:两者默认都采用内存进行存储,但 MEMORY 表不受存储转换的影响,而内部临时表则会在达到阈值时自动转换为磁盘存储。
基于以上特性MEMORY 表适合于存储临时数据 ,如会话状态、实时位置等信息。
基于以上特性MEMORY 表主要适合于存储临时数据 ,如会话状态、实时位置等信息。
### 1.4 CSV
@ -37,12 +52,12 @@ CSV 存储引擎使用逗号分隔值的格式将数据存储在文本文件中
ARCHIVE 默认采用 zlib 无损数据压缩算法进行数据压缩能够利用极小的空间存储大量的数据。创建ARCHIVE 表时,存储引擎会创建与表同名的 `ARZ` 文件,用于存储数据。它还具有以下特点:
+ ARCHIVE 引擎支持 INSERTREPLACE 和 SELECT但不支持 DELETE 或 UPDATE。
+ ARCHIVE 引擎支持 AUTO_INCREMENT 属性,并支持在其对应的列上建立索引,如果尝试在其他不具有 AUTO_INCREMENT 属性的列上建立索引,则会抛出异常。
+ ARCHIVE 引擎不支持分区。
+ ARCHIVE 引擎支持 AUTO_INCREMENT 属性,并支持在其对应的列上建立索引,如果尝试在不具有 AUTO_INCREMENT 属性的列上建立索引,则会抛出异常。
+ ARCHIVE 引擎不支持分区操作
### 1.6 MEGRE
MERGE 存储引擎,也称为 MRG_MyISAM 引擎,是一组相同 MyISAM 表的集合。 ”相同” 表示所有表必须具有相同的列数据类型和索引信息。要创建 MERG E表必须指定 UNION =list-of-tables选项,如下:
MERGE 存储引擎,也称为 MRG_MyISAM 引擎,是一组相同 MyISAM 表的集合。 ”相同” 表示所有表必须具有相同的列数据类型和索引信息。可以通过 UNION =list-of-tables选项来创建 MERGE 表,如下:
```sql
mysql> CREATE TABLE t1 ( a INT NOT NULL AUTO_INCREMENT PRIMARY KEY, message CHAR(20)) ENGINE=MyISAM;
@ -53,7 +68,7 @@ mysql> CREATE TABLE total (a INT NOT NULL AUTO_INCREMENT,message CHAR(20), INDEX
ENGINE=MERGE UNION=(t1,t2) INSERT_METHOD=LAST;
```
创建表时你还可以通过 INSERT_METHOD 选项来控制 MERGE 表的插入:使用 FIRST 或 LAST 分别表示在第一个或最后一个基础表中进行插入;如果未指定 INSERT_METHOD 或者设置值为 NO ,则表示不允许在 MERGE 表上执行插入操作。除此之外你还可以在MERGE表上使用 SELECTDELETEUPDATE 语句,示例如下:
创建表时通过 INSERT_METHOD 选项来控制 MERGE 表的插入:使用 FIRST 或 LAST 分别表示在第一个或最后一个基础表中进行插入;如果未指定 INSERT_METHOD 或者设置值为 NO ,则表示不允许在 MERGE 表上执行插入操作。MERGE 表支持 SELECTDELETEUPDATE 和 DELETE 语句,示例如下:
```sql
mysql> SELECT * FROM total;
@ -71,43 +86,71 @@ mysql> SELECT * FROM total;
## 二、索引
### 2.1 B+ tree 索引
### 2.1 B+ tree 数据结构
B-Tree 适用于以下类型的查找:
如果没有特殊说明,通常大多数数据库采用的索引都是 B+ tree 索引,它是基于 B+ tree 这种数据结构构建的。为什么采用 B+ tree 而不是平衡二叉树 (AVL) 或红黑树等数据结构?这里假设索引为 1-16 的自增数据,各类数据结构的表现如下:
**平衡二叉树数据结构**
![avl-tree](D:\Full-Stack-Notes\pictures\avl-tree.png)
**红黑树数据结构**
![red-black-tree](D:\Full-Stack-Notes\pictures\red-black-tree.png)
**Btree 数据结构**
![btree](D:\Full-Stack-Notes\pictures\btree.png)
**B+ Tree 数据结构**
![B+Trees](D:\Full-Stack-Notes\pictures\B+Trees.png)
从上面的图示中我们可以看出 B+ Tree 树具有以下优点:
+ B+ Tree 树的所有非叶子节点 (如 003007),都会在叶子节点冗余一份,所有叶子节点都按照链表的方式进行组织,这样带来的好处是在范围查询中,只需要通过遍历叶子节点就可以获取到所有的索引信息。
+ B+ Tree 的所有非叶子节点都可以存储多个数据值,这取决于节点的大小,在 MySQL 中每个节点的大小为 16K ,因此其具备更大的出度,即在相同的数据量下,其树的高度更低。
+ 所有非叶子节点都只存储索引值,不存储实际的数据,只有叶子节点才会存储指针信息或数据信息。按照每个节点为 16K 的大小计算,对于千万级别的数据,其树的高度通常都在 3~6 左右 (取决于索引值的字节数),因此其查询性能非常优异。
+ 叶子节点存储的数据取决于不同数据库的实现,对于 MySQL 来说,这取决于使用的存储引擎和是否是主键索引。
### 2.2 B+ tree 索引
对于 InnoDB ,因为主键索引是聚集索引,所以其叶子节点存储的就是实际的数据。而非主键索引存储则是主键的值
![mysql-innodb-索引](D:\Full-Stack-Notes\pictures\mysql-innodb-索引.png)
对于 MyISAM因为主键索引是非聚集索引所以其叶子节点存储的只是指向数据位置的指针
![mysql-myisam-索引](D:\Full-Stack-Notes\pictures\mysql-myisam-索引.png)
综上所述B+ tree 普遍适用于范围查找优化排序和分组等操作。B+ tree 的查找是基于字典序的,因此其适用的具体查询类型如下:
+ **全值匹配**:以索引为条件进行精确查找。如 `emp_no` 字段为索引,查询条件为 `emp_no = 10008`
+ **前缀匹配**:以联合索引的前缀为查找条件。如 `emp_no``dept_no` 为联合索引,查找条件为 `emp_no = 10008`
+ **列前缀匹配**:匹配索引列的值的开头部分。如 `dept_no` 为索引,查询条件为 `dept_no like "d1%"`;
+ **列前缀匹配**:匹配索引列的值的开头部分。如 `dept_no` 为索引,查询条件为 `dept_no like "d1%"`。前缀匹配和列前缀匹配都是索引前缀性的体现,在某些时候也称为前缀索引。
+ **匹配范围值**:按照索引列匹配一定范围内的值。如 `emp_no` 字段为索引,查询条件为 `emp_no > 10008`
+ **只访问索引的查询**:如 `emp_no` 字段为索引,查询语句为 `select emp_no from employees`
+ **精确匹配某一列并范围匹配某一列**:如 `emp_no``dept_no` 为联合索引,查找条件为 `dept_no = "d004" and emp_no < 10020`
+ **只访问索引的查询**:如 `emp_no` 字段为索引,查询语句为 `select emp_no from employees`,此时 emp_no 索引被称为本次查询的覆盖索引,即只需要从索引上就可以获取全部的查询信息,而不必访问实际的表中的数据。
+ **精确匹配某一列并范围匹配某一列**:如 `emp_no``dept_no` 为联合索引,查找条件为 `dept_no = "d004" and emp_no < 10020`,这种情况下索引顺序必须为 ( emp_nodept_no ),这样才能基于 emp_no 的字典序排序进行范围查找
### 2.2 哈希索引
### 2.3 哈希索引
使用哈希索引时,存储引擎会对索引列的值进行哈希运算,并将计算出的哈希值和指向该行数据的指针存储在索引中,因此它更适用于等值比较查询,而不是范围查询。在建立哈希索引时,需要选取选择性比较高的列,即列上的数据不容易重复 (如身份证号),这样可以尽量避免哈希冲突。因为哈希索引并不需要存储索引列的数据,所以其结构比较紧凑,对应的查询速度也比较快。
使用哈希索引时,存储引擎会对索引列的值进行哈希运算,并将计算出的哈希值和指向该行数据的指针存储在索引中,因此它更适用于等值比较查询,而不是范围查询,同样也不能用于优化排序和分组等操作。在建立哈希索引时,需要选取选择性比较高的列,即列上的数据不容易重复 (如身份证号),这样可以尽量避免哈希冲突。因为哈希索引并不需要存储索引列的数据,所以其结构比较紧凑,对应的查询速度也比较快。
InnoDB 引擎有一个名为 “自适应哈希索引 (adaptive hash index)” 的功能,当某些索引值被频繁使用时,它会在内存中基于 B-Tree 索引在创建一个哈希索引,从而让 B-Tree 索引具备哈希索引的优点,如快速查找。
InnoDB 引擎有一个名为 “自适应哈希索引 (adaptive hash index)” 的功能,当某些索引值被频繁使用时,它会在内存中基于 B+ tree 索引再创建一个哈希索引,从而让 B-Tree 索引具备哈希索引快速查找的优点
### 2.3 索引的优点
### 2.4 索引的优点
+ 索引极大减少了服务器需要扫描的数据量;
+ 索引可以帮助服务器避免排序和临时表;
+ 索引可以将随机 IO 转换为顺序 IO。
### 2.4 索引的创建与使用策略
- 在查询时,应该避免在索引列上使用函数或者表达式;
- 对于多列索引,应该按照使用频率由高到低的顺序建立联合索引;
### 2.5 索引的创建与使用策略
- 在查询时,应该避免在索引列上使用函数或者表达式。
- 对于多列索引,应该按照使用频率由高到低的顺序建立联合索引。
- 尽量避免创建冗余的索引。如存在索引 (AB),接着又创建了索引 A索引 A 是索引 (AB) 的前缀索引,从而出现冗余。
- 建立索引时,应该考虑查询时候的排序和分组的需求。只有当索引列的顺序和 ORDER BY 字句的顺序完全一致并且遵循同样的升序或降序规则时候MySQL 才会使用索引来对结果做排序。
- 尽量避免创建冗余的索引。通常会出现以下两种情况的冗余:
+ 已经存在索引 (AB),接着又创建了索引 A此时会出现冗余因为索引 A 只是索引 (AB) 的前缀索引;
+ 为主键列再次创建索引,因为主键已经是唯一索引了,此时再创建就会出现冗余。
## 三、锁
### 3.1 共享锁与排它锁
@ -124,9 +167,9 @@ InnoDB 存储引擎支持以下两种标准的行级锁:
| **X** | 不兼容 | 不兼容 |
| **S** | 不兼容 | 兼容 |
### 3.2 意向锁
### 3.2 意向共享锁与意向排它
为了说明意向锁的作用,这里先引入一个案例:假设事务 A 利用 S 锁锁住了表中的某一行,让其只能读不能写。之后事务 B 尝试申请整个表的写锁,如果事务 B 申请成功,那么理论上它就应该能修改表中的任意一行,这与事务 A 持有的行锁是冲突的。想要解决这个问题,数据库必须知道表中某一行已经被锁定,从而在事务 B 尝试申请整个表的写锁时阻塞它。想要知道表中某一行被锁定,可以对表的每一行进行遍历,这种方式性能比较差,所以 InnoDB 引入了意向锁。
为了说明意向锁的作用,这里先引入一个案例:假设事务 A 利用 S 锁锁住了表中的某一行,让其只能读不能写。之后事务 B 尝试申请整个表的写锁,如果事务 B 申请成功,那么理论上它就应该能修改表中的任意一行,这与事务 A 持有的行锁是冲突的。想要解决这个问题,数据库必须知道表中某一行已经被锁定,从而在事务 B 尝试申请整个表的写锁时阻塞它。想要知道表中某一行被锁定,可以对表的每一行进行遍历,这种方式可行但是性能比较差,所以 InnoDB 引入了意向锁。
+ 意向共享锁 ( IS Lock ):当前表中某行或者某几行数据存在共享锁;
+ 意向排它锁 ( LX Lock ):当前表中某行或者某几行数据存在排它锁。
@ -140,17 +183,132 @@ InnoDB 存储引擎支持以下两种标准的行级锁:
| **S** | 不兼容 | 不兼容 | 兼容 | 兼容 |
| **IS** | 不兼容 | 兼容 | 兼容 | 兼容 |
## 三、事务
### 3.3 一致性读
**1. 一致性非锁定读**
一致非锁定读 (consistent nonlocking read) 是指在 InnoDB 存储引擎下,如果将要读取的行正在执行 DELETE 或 UPDATE 操作,此时不必去等待行上锁的释放,而是去读取 undo 日志上该行的快照数据,具体如下:
+ 在 READ COMMITTED 事务隔离级别下,读取被锁定行的最新一份快照数据;
+ 在 REPEATABLE READ 事务隔离级别下,读取事务开始时所处版本的数据。
基于多版本并发控制和一致性非锁定读,可以避免获取锁的等待,从而提高并发访问下的性能。
**2. 一致性锁定度**
一致性锁定读则允许用户按照自己的需求在进行 SELECT 操作时手动加锁,通常有以下两种方式:
+ **SELECT ... FOR SHARE**:在读取行上加 S 锁;
+ **SELECT ... FOR UPDATE**:在读取行上加 X 锁。
### 3.4 锁的算法
InnoDB 存储引擎支持以下三种锁的算法:
**Record Lock**:行锁,用于锁定单个行记录。示例如下:
```sql
-- 利用行锁可以防止其他事务更新或删除该行
SELECT c1 FROM t WHERE c1 = 10 FOR UPDATE;
```
**Gap Lock**:间隙锁,锁定一个范围,但不包括记录本身,主要用于解决幻读问题,示例如下:
```sql
-- 利用间隙锁可以阻止其他事务将值15插入列 t.c1
SELECT c1 FROM t WHERE c1 BETWEEN 10 and 20 FOR UPDATE;
```
**Next-Key Lock**:等价于 行锁+间隙锁,既锁定范围,也锁定记录本身。可以用于解决幻读中的 ”当前读“ 的问题。
## 四、事务
### 4.1 ACID
InnoDB 存储引擎完全支持 ACID 模型:
**1. 原子性Atomicity**
事务是不可分割的最小工作单元,事务的所有操作要么全部提交成功,要么全部失败回滚,不存在部分成功的情况。
**2. 一致性Consistency**
数据库在事务执行前后都保持一致性状态,数据库的完整性没有被破坏。
**3. 隔离性Isolation**
允许多个并发事务同时对数据进行操作,但一个事务所做的修改在最终提交以前,对其它事务是不可见的。
**4. 持久性Durability**
一旦事务提交,则其所做的修改将会永远保存到数据库中。即使宕机等故障,也不能丢失。
### 4.2 事务的实现
数据库隔离性由上一部分介绍的锁来实现,而原子性、一致性、持久性都由 undo log 和 redo log 来实现。
+ **undo log**:存储在 undo 表空间或全局临时表空间的 undo 日志段 (segment) 上,用于记录数据修改前的状态,主要用于帮助事务回滚以及实现 MVCC 功能 (如一致性非锁定读)。
+ **redo log**:负责记录数据修改后的值,主要用于保证事务的持久化。
### 4.3 并发问题
在并发环境下,数据的更改通常会产生下面四种问题:
**1.丢失更新**
一个事务的更新操作被另外一个事务的更新操作锁覆盖,从而导致数据不一致:
![mysql-修改丢失](D:\Full-Stack-Notes\pictures\mysql-修改丢失.png)
**2. 脏读**
在不同的事务下,一个事务读取到其他事务未提交的数据:
![mysql-脏读](D:\Full-Stack-Notes\pictures\mysql-脏读.png)
**3. 不可重复读**
在同一个事务的两次读取之间,由于其他事务对数据进行了修改,导致对同一条数据两次读到的结果不一致:
![mysql-不可重复读](D:\Full-Stack-Notes\pictures\mysql-不可重复读.png)
**4.幻读**
在同一个事务的两次读取之间,由于其他事务对数据进行了修改,导致第二次读取到第一次不存在数据,或第一次原本存在的数据,第二次却读取不到,就好像之前的读取是 “幻觉” 一样:
![mysql-幻读](D:\Full-Stack-Notes\pictures\mysql-幻读.png)
### 4.4 隔离级别
想要解决以上问题可以通过设置隔离级别来实现InnoDB 支持以下四个等级的隔离级别,默认隔离级别为可重复读:
- **读未提交**:在此级别下,一个事务中的修改,即便没有提交,对其他事务也是可见的。
- **读已提交**:在此级别下,一个事务中的修改只有已经提交的情况下,对其他事务才是可见的。
- **可重复读**:保证在同一个事务中多次读取同样数据的结果是一样的。
- **串行化**:所有事务强制串行执行,由于已经不存在并行,所以上述所有并发问题都不会出现。
在每个级别下,并发问题是否可能出现的情况如下:
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
| ---------------------------- | ---------- | ---------- | ------ |
| 读未提交READ UNCOMMITTED | 可能出现 | 可能 | 可能 |
| 读已提交READ COMMITTED | 不可能出现 | 可能 | 可能 |
| 可重复读REPEATABLE READ | 不可能 | 不可能 | 可能 |
| 串行化SERIALIZABLE | 不可能 | 不可能 | 不可能 |
就数据库层面而言,当前任何隔离级别下都不会发生丢失更新的问题,以 InnoDB 存储引擎为例,如果你想要更改表中某行数据,该行数据上必然会加上 X 锁,而对应的表上则会加上 IX 锁,其他任何事务必须等待获取该锁才能进行修改操作。
## 参考资料
1. [ InnoDB 数据页解析](http://mysql.taobao.org/monthly/2018/04/03/)
2. [MySQL索引背后的数据结构及算法原理](https://blog.codinglabs.org/articles/theory-of-mysql-index.html)
1. 官方文档:[The InnoDB Storage Engine](https://dev.mysql.com/doc/refman/8.0/en/innodb-storage-engine.html)[Optimization and Indexes](https://dev.mysql.com/doc/refman/8.0/en/optimization-indexes.html)[InnoDB Locking and Transaction Model](https://dev.mysql.com/doc/refman/8.0/en/innodb-locking-transaction-model.html)
2. 姜承尧 . MySQL技术内幕:InnoDB存储引擎(第2版) . MySQL技术内幕 . 2013-05
3. 施瓦茨 (Baron Schwartz) / 扎伊采夫 (Peter Zaitsev) / 特卡琴科 (Vadim Tkachenko) . 高性能mysql(第3版) . 电子工业出版社 . 2013-05-01
4. [ InnoDB 数据页解析](http://mysql.taobao.org/monthly/2018/04/03/)
5. [MySQL索引背后的数据结构及算法原理](https://blog.codinglabs.org/articles/theory-of-mysql-index.html)
6. [MYSQL-B+TREE索引原理](https://www.jianshu.com/p/486a514b0ded)
7. [Innodb中的事务隔离级别和锁的关系](https://tech.meituan.com/2014/08/20/innodb-lock.html)

212
notes/MySQL_备份.md Normal file
View File

@ -0,0 +1,212 @@
# 数据备份与恢复
## 一、备份
## 二、mysqldump
### 2.1 基本语法
mysqldump 的基本语法如下:
```shell
# 备份数据库或数据库中的指定表
mysqldump [options] db_name [tbl_name ...]
# 备份多个指定的数据库
mysqldump [options] --databases db_name ...
# 备份当前数据库实例中的所有表
mysqldump [options] --all-databases
```
options 代表可选操作,常用的可选参数如下:
+ **--host=host_name -h host_name**
指定服务器地址。
+ **--user=user_name -u user_name**
指定用户名。
+ **--password[=password] -p[password]**
指定密码。通常不用在命令行中明文指定,按照提示输出即可。
+ **--default-character-set=charset_name**
导出文本使用的字符集,默认为 utf8。
+ **--events -E**
备份包含包含数据库中的事件。
+ **--ignore-table=db_name.tbl_name**
不需要进行备份的表,必须使用数据库和表名来指定。也可以作用于视图。
+ **--routines -R**
备份包含包含数据库中的存储过程和自定义函数。
+ **--triggers**
备份包含包含数据库中的触发器。
+ **--where='where_condition' -w 'where_condition'**
在对单表进行导出时候,可以指定过滤条件,例如指定用户名 `--where="user='jimf'"` 或用户范围 `-w"userid>1"`
+ **--lock-all-tables -x**
锁定所有数据库中的所有表,从而保证备份数据的一致性。此选项自动关闭 --single-transaction 和 --lock-tables。
+ **--lock-tables -l**
锁定当前数据库中所有表,能够保证当前数据库中表的一致性,但不能保证全局的一致性。
由于--lock-tables每个数据库的锁表都是单独的因此该选项不保证转储文件中的表在数据库之间在逻辑上是一致的。不同数据库中的表可能会以完全不同的状态转储。
+ **--single-transaction**
此选项会将事务隔离模式设置为: REPEATABLE READ 并开启一个事务,从而保证备份数据的一致性。主要用于事务表,如 InnoDB 表。 但是此时仍然不能在备份表上执行 ALTER TABLE CREATE TABLE DROP TABLE RENAME TABLE TRUNCATE TABLE 等操作,因为 REPEATABLE READ 隔离并不能隔离这些操作。
另外需要注意的是 --single-transaction 选项与 --lock-tables 选项是互斥的,因为 LOCK TABLES 会导致任何正在挂起的事务被隐式提交。转储大表时,可以将 --single-transaction 选项与 --quick 选项组合使用 。
+ **--quick -q**
主要用于备份大表。它强制 mysqldump 一次只从服务器检索一行数据,避免一次检索所有行而导致缓存溢出。
+ **--flush-logs, -F**
在开始备份前刷新 MySQL 的日志文件。此选项需要 RELOAD 权限。如果此选项与 --all-databases 配合使用,则会在每个数据库开始备份前都刷新一次日志。如果配合 --lock-all-tables--master-data 或 --single-transaction 使用,则只会在锁定所有表或者开启事务时刷新一次。
### 2.2 全量备份
mysqldump 的全量备份与恢复操作比较简单,使用示例如下:
```shell
# 备份雇员库
mysqldump -uroot -p --databases employees > employees_bak.sql
# 恢复雇员库
mysql -uroot -p < employees_bak.sql
```
单表备份:
```shell
# 备份雇员库中的职位表
mysqldump -uroot -p --single-transaction employees titles > titles_bak.sql
# 恢复雇员库中的职位表
mysql> use employees;
mysql> source /root/mysqldata/titles_bak.sql;
```
### 2.3 增量备份
## 三、Xtrabackup
### 3.1 安装
Xtrabackup 可以直接使用 yum 命令进行安装,这里我的 MySQL 为 8.0 ,对应安装的 Xtrabackup 也为 8.0,命令如下:
```shell
# 安装Percona yum 源
yum install https://repo.percona.com/yum/percona-release-latest.noarch.rpm
# 安装
yum install percona-xtrabackup-80
```
### 3.2 全量备份
#### 1. 创建备份
Xtrabackup 全量备份的基本语句如下,可以使用 target-dir 指明备份文件的存储位置parallel 则是指明操作的并行度:
```shell
xtrabackup --backup --user=root --password --parallel=3 --target-dir=/data/backups/
```
以上进行的是整个数据库实例的备份,如果需要备份指定数据库,则可以使用 --databases 进行指定。
另外一个容易出现的异常是Xtrabackup 在进行备份时,默认会去 `/var/lib/mysql/mysql.sock` 文件里获取数据库的 socket 信息,如果你修改了数据库的 socket 配置,则需要使用 --socket 参数进行重新指定,否则会抛出找不到连接的异常。备份完整后需要立即执行的另外一个操作是 prepare。
#### 2. 准备备份
由于备份是将所有物理库表等文件复制到备份目录,而整个过程需要持续一段时间,此时备份的数据中就可能会包含尚未提交的事务或已经提交但尚未同步至数据文件中的事务,最终导致备份结果处于不一致状态。此时需要进行 prepare 操作来回滚未提交的事务及同步已经提交的事务至数据文件,从而使得整体达到一致性状态。命令如下:
```shell
xtrabackup --prepare --target-dir=/data/backups/
```
需要特别注意的在该阶段不要随意中断 xtrabackup 进程,因为这可能会导致数据文件损坏,备份将无法使用。
#### 3. 恢复备份
由于 xtrabackup 执行的是物理备份,所以想要进行恢复,必须先要停止 MySQL 服务。同时这里我们可以删除 MySQL 的数据目录来模拟数据丢失的情况,之后使用以下命令将备份文件拷贝到 MySQL 的数据目录下:
```shell
# 模拟数据异常丢失
rm -rf /usr/app/mysql-8.0.17/data/*
# 将备份文件拷贝到 data 目录下
xtrabackup --copy-back --target-dir=/data/backups/
```
copy-back 命令只需要指定备份文件的位置,不需要指定 MySQL 数据目录的位置,因为 Xtrabackup 会自动从 `/etc/my.cnf` 上获取 MySQL 的相关信息,包括数据目录的位置。如果不需要保留备份文件,可以直接使用 --move-back 命令,代表直接将备份文件移动到数据目录下。通常此时数据目录的所有者为执行命令的用户,需要更改为 mysql 用户,命令如下:
```shell
chown -R mysql:mysql /usr/app/mysql-8.0.17/data
```
再次启动即可完成备份恢复。
### 3.3 增量备份
#### 1. 创建备份
```shell
xtrabackup --user=root --password --backup --target-dir=/data/backups/base/
```
```shell
xtrabackup --user=root --password --backup --target-dir=/data/backups/inc1 \
--incremental-basedir=/data/backups/base
```
```shell
xtrabackup --user=root --password --backup --target-dir=/data/backups/inc2 \
--incremental-basedir=/data/backups/inc1
```
#### 2. 准备备份
```shell
xtrabackup --prepare --apply-log-only --target-dir=/data/backups/base
```
```shell
xtrabackup --prepare --apply-log-only --target-dir=/data/backups/base \
--incremental-dir=/data/backups/inc1
```
```shell
xtrabackup --prepare --target-dir=/data/backups/base \
--incremental-dir=/data/backups/inc2
```
#### 3. 恢复备份
```shell
xtrabackup --copy-back --target-dir=/data/backups/base
chown -R mysql:mysql /usr/app/mysql-8.0.17/data
```

View File

@ -1,6 +1,54 @@
## 一、基于二进制日志的复制
# MySQL 复制
主节点配置:
## 一、日志格式
### 1.1 二进制日志格式
MySQL 二进制日志是进行主从复制的基础,它记录了所有对 MySQL 数据库的修改事件,包括增删改查和表结构修改。当前 MySQL 一共支持三种二进制日志格式,可以通过 binlog-format 参数来进行控制,其可选值如下:
+ **STATEMENT**:段格式。是 MySQL 最早支持的二进制日志格式。其记录的是实际执行修改的 SQL 语句,因此在进行批量修改时其所需要记录的数据量比较小,但对于 UUID() 或者其他依赖上下文的执行语句,可能会在主备上产生不一样的结果。
+ **ROW**:行格式,是 MySQL 5.7 版本之后默认的二进制日志格式。其记录的是修改前后的数据。因此在批量修改时其需要记录的数据量比较大,因为需要记录每一行语句修改后的结果。但其安全性比较高,不会导致主备出现不一致的情况。同时因为 ROW 格式是在从库上直接应用更改后的数据,其还能减少锁的使用。
+ **MIXED**:是以上两种日志的混合方式,默认采用段格式进行记录,当段格式不适用时 (如 UUID() ),则默认采用 ROW 格式。
通常在主备之间网络情况良好的时,可以优先考虑使用 ROW 格式,此时数据一致性最高,其次是 MIXED 格式。在设置 ROW 格式时,还有一个非常重要的参数 binlog_row_image 。
### 1.2 binlog_row_image
binlog_row_image 有以下三个可选值:
+ **full**:默认值,记录行在修改前后所有列的值。
+ **minimal**:只记录修改涉及列的值。
+ **noblob**:与 full 类似,但如果 BLOB 或 TEXT 列没有修改,则不对其进行记录。
binlog-format 和 binlog_row_image 的默认值可能在不同版本存在差异,可以使用以下命令进行查看。通常情况下,为了减少在主备复制中需要传输的数据量,可以将 binlog_row_image 的值设置为 minimal 或 noblob。
```sql
show variables like 'binlog_format';
show variables like 'binlog_row_image';
```
## 二、基于二进制日志的复制
### 2.1 复制原理
MySQL 的复制原理如下图所示:
+ 主库首先将变更写入到自己的二进制日志中;
+ 备库会启动一个 IO 线程,然后主动去主库节点上获取变更日志,并写入到自己的中继日志中。
+ 之后从中继日志中读取变更事件,在从库上执行变更。
+ 当备库与主库数据状态一致,备库的 IO 线程就会进入睡眠。当主库再次发生变更时,其会向备库发出信号,唤醒 IO 线程并再次进行工作。
如果没有进行任何配置,主库在将变更写入到二进制日志后,就会返回对客户端的响应,因此默认情况下的复制是完全异步进行的,主备之间可能会短暂存在数据不一致的情况。
![mysql-replication](D:\Full-Stack-Notes\pictures\mysql-replication.jpg)
### 2.2 配置步骤
首先主节点需要开启二进制日志,并且在同一个复制环境下,主备节点的 server-id 需要不一样:
```properties
[mysqld]
@ -9,7 +57,7 @@ server-id = 226
log-bin=mysql-bin
```
从节点配置
在备份节点配置中继日志
```properties
[mysqld]
@ -18,20 +66,20 @@ server-id = 227
relay_log = mysql-relay-bin
# 为了保证数据的一致性,从节点应该设置为只读
read_only = 1
# 以下两个配置代表是否开启二进制日志,如果存在其他节点需要从该从节点上复制数据,则开启,否则不用配置
# 以下两个配置代表是否开启二进制日志,如果该从节点还作为其他备库的主节点,则开启,否则不用配置
log-bin = mysql-bin
# 是否将中继节点收到的复制事件写到自己的二进制日志中
log_slave_updates = 1
```
创建复制账号:
创建用于进行复制账号,并为其授予权限
```shell
CREATE USER 'repl'@'192.168.0.%' IDENTIFIED WITH mysql_native_password BY '123456';
GRANT REPLICATION SLAVE on *.* TO 'repl'@'192.168.0.%' ;
```
查看主节点日志状态:
查看主节点二进制日志状态:
```shell
mysql> SHOW MASTER STATUS;
@ -42,7 +90,7 @@ mysql> SHOW MASTER STATUS;
+------------------+----------+--------------+------------------+-------------------+
```
建立复制链路:
基于日志和偏移量,建立复制链路:
```shell
CHANGE MASTER TO MASTER_HOST='192.168.0.226',\
@ -58,7 +106,7 @@ CHANGE MASTER TO MASTER_HOST='192.168.0.226',\
START SLAVE;
```
查看从节点复制状态
查看从节点复制状态,主要参数有 Slave_IO_Running 和 Slave_SQL_Running其状态都为 Yes 表示用于进行复制的 IO 进程已经开启。Seconds_Behind_Master 参数表示从节点复制的延迟量。此时可以在主库上进行任意更改,并在备库上查看情况。
```shell
mysql> SHOW SLAVE STATUS\G
@ -103,7 +151,7 @@ Master_SSL_Verify_Server_Cert: No
Last_SQL_Error:
Replicate_Ignore_Server_Ids:
Master_Server_Id: 226
Master_UUID: e1148574-bdd0-11e9-8873-0800273acbfd
# Master_UUID: e1148574-bdd0-11e9-8873-0800273acbfd
Master_Info_File: mysql.slave_master_info
SQL_Delay: 0
SQL_Remaining_Delay: NULL
@ -126,22 +174,33 @@ Master_SSL_Verify_Server_Cert: No
1 row in set (0.00 sec)
```
## 二、基于 GTID 的复制
### 2.3 优缺点
主从服务器均增加以下配置:
基于二进制日志的复制是 MySQL 最早使用的复制技术,因此 MySQL 对其的支持比较完善,对执行修改的 SQL 语句几乎没有任何限制。其主要的缺点是在一主多从的高可用复制架构中,如果主库发生宕机,此时想要自动通过从库的日志和偏移量来确定新的主库比较困难。
## 三、基于 GTID 的复制
### 2.1 GTID
MySQL 5.6 版本之后提供了一个新的复制模式:基于 GTID 的复制。GTID 全称为 Global Transaction ID即全局事务 ID。它由每个服务节点的唯一标识和其上的事务 ID 共同组成,格式为: *server_uuid : transaction_id* 。通过 GTID ,可以保证在主库上的每一个事务都能在备库上得到执行,不会存在任何疏漏。
### 2.2 配置步骤
主从服务器均增加以下 GTID 的配置:
```properties
gtid-mode = ON
# 防止执行不受支持的语句,下文会有说明
enforce-gtid-consistency = ON
```
在从服务器上执行以下命令,关闭原有复制链路:
如果配置过上面的基于二进制日志的复制,还需要在从服务器上执行以下命令,关闭原有复制链路:
```shell
STOP SLAVE IO_THREAD FOR CHANNEL '';
```
建立新的 GTID 复制链路:
建立新的基于 GTID 复制链路,指定 MASTER_AUTO_POSITION = 1 表示由程序来自动确认开始同步的 GTID 的位置
```shell
CHANGE MASTER TO MASTER_HOST='192.168.0.226',\
@ -156,12 +215,82 @@ CHANGE MASTER TO MASTER_HOST='192.168.0.226',\
START SLAVE;
```
查看从节点复制状态:
在主节点上执行任意修改操作,并查看从节点状态,关键的输出如下Retrieved_Gtid_Set 代表从主节点上接收到的两个事务Executed_Gtid_Set 表示这两个事务已经在从库上得到执行。
在主节点上执行任意修改操作,查看复制情况:
```
Retrieved_Gtid_Set: e1148574-bdd0-11e9-8873-0800273acbfd:1-2
Executed_Gtid_Set: e1148574-bdd0-11e9-8873-0800273acbfd:1-2
```sql
mysql> SHOW SLAVE STATUS\G
....
Master_UUID : e1148574-bdd0-11e9-8873-0800273acbfd
Retrieved_Gtid_Set : e1148574-bdd0-11e9-8873-0800273acbfd:1-2
Executed_Gtid_Set : e1148574-bdd0-11e9-8873-0800273acbfd:1-2
.....
```
### 2.3 优缺点
GTID 复制的优点在于程序可以自动确认开始复制的 GTID 点。但其仍然存在以下限制:
+ 不支持 CREATE TABLE ... SELECT 语句。 因为在 ROW 格式下,该语句将会被记录为具有不同 GTID 的两个事务,此时从服务器将无法正确处理。
+ 事务,过程,函数和触发器内部的 CREATE TEMPORARY TABLE 和 DROP TEMPORARY TABLE 语句均不受支持。
为防止执行不受支持的语句,建议配置和上文配置一样,开启 enforce-gtid-consistency 属性, 开启后在主库上执行以上不受支持的语句都将抛出异常并提示。
## 四、半同步复制
## 五、高可用架构
不论是主主复制结构,还是一主多从复制结构,单存依靠复制只能解决数据可靠性的问题,并不能解决系统高可用的问题,想要保证高可用,系统必须能够自动进行故障转移,即在主库宕机时,主动将其它备库升级为主库。常用的有以下两种解决方案:
### 4.1 MMM
1. Monitor 检测到 Master1 连接失败;
2. Monitor 发送 set_offline 指令到 Master1 的 Agent
3. Master1 Agent 如果存活,下线写 VIP尝试把 Master1 设置为 read_only=1
4. Moniotr 发送 set_online 指令到 Master2
5. Master2 Agent 接收到指令,执行 `select master_pos_wait()` 等待同步完毕;
6. Master2 Agent 上线写 VIP把 Master2 节点设为 read_only=0
7. Monitor 发送更改同步对象的指令到各个 Slave 节点的 Agent
8. 各个 Slave 节点向新 Master 同步数据;
简单来说MMM 就是暴力的将 Master1 切换到 Master2 ,它本生不会尝试去补齐丢失的数据。
### 4.2 MHA
1. 尝试从宕机 Master 中保存二进制日志;
2. 找到含有最新中继日志的 Slave
3. 把最新中继日志应用到其他实例,实现各实例数据一致;
4. 应用从 Master 保存的二进制日志事件;
5. 提升一个 Slave 为 Master
6. 其他 Slave 向该新 Master 同步。
当 Master 宕机后, MHA 会尝试保存宕机 Master 的二进制日志,然后自动判断 MySQL 集群中哪个实例的中继日志是最新的,并将有最新日志的实例的差异日志传到其他实例补齐,从而实现所有实例数据一致。然后把宕机 Master 的二进制日志应用到选定节点,并提升为 Master 。
从切换流程流程可以看到,如果宕机 Master 主机无法 SSH 登录,那么第一步就没办法实现,对于 MySQL5.5 以前的版本,数据还是有丢失的风险。对于 5.5 后的版本,开启**半同步复制**后,真正有助于避免数据丢失,半同步复制保证至少一个 不是所有slave 在 master 提交时接收到二进制日志事件。因此,对于可以处理一致性问题的 MHA 可以实现"几乎没有数据丢失"和"从属一致性"。
### 优点
- 开源,用 Perl 编写
- 方案成熟故障切换时MHA 会做日志补齐操作,尽可能减少数据丢失,保证数据一
- 部署不需要改变现有架构
### 限制
- 各个节点要打通 SSH 信任,有一定的安全隐患
- 没有 Slave 的高可用
- 自带的脚本不足例如虚IP配置需要自己写命令或者依赖其他软件
- 需要手动清理中继日志
## 参考资料
+ [MHA高可用架构](https://segmentfault.com/a/1190000017486693)
+ [MMM高可用架构](https://segmentfault.com/a/1190000017286307)
+

BIN
pictures/B+Trees.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
pictures/avl-tree.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
pictures/btree.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
pictures/mysql-幻读.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
pictures/mysql-脏读.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
pictures/red-black-tree.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB