Files
Linux-book/03.数据库/01.Mysql.md
2026-02-03 21:30:57 +08:00

4616 lines
165 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 1. 数据库介绍
## 1.1 什么是数据
数据(data)是事实或观察的结果,是对客观事物的逻辑归纳,是用于表示客观事物的未经加工的的原始素材。
数据可以是连续的值,比如声音、图像,称为模拟数据。也可以是离散的,如符号、文字,称为数字数据。
在计算机系统中数据以二进制信息单元0,1的形式表示。
**数据的定义:**数据是指对客观事件进行记录并可以鉴别的符号,是对客观事物的性质、状态以及相互关系等进行记载的物理符号或这些物理符号的组合。它是可识别的、抽象的符号。
## 1.2 什么是数据库
数据库就是存储和管理数据的仓库,数据按照一定的格式进行存储,用户可以对数据库中的数据进行增加修改、删除、查询等操作。数据库的分为关系型数据库和非关系型数据库。
关系型数据库是指采用了关系模型来组织数据的数据库简单来说就是二维表格模型好比Excel文件中的表格强调使用表格的方式存储数据。关系型数据库核心元素数据行、数据列、数据表、数据库数据表的集合非关系型数据库强调Key-Value的方式存储数据。
**常用关系数据库:**MySQL、SQLite、Oracle等
**常见非关系数据库:**MongoDB、Redis
数据库特点:持久化存储、读写速度极高、保证数据的有效性
关系型数据库管理系统是为管理关系型数据库而设计的软件系统,如果要使用关系型数据库就需要安装数据库管理系统,其实就是一个应用软件。关系型数据库管理系统分为关系型数据库服务端软件和关系型数据库客户端软件。
关系型数据库服务端软件主要负责管理不同的数据库,而每个数据库里面会有一系列数据文件,数据文件是用来存储数据的,其实数据库就是一系列数据文件的集合。
关系型数据库客户端软件主要负责和关系型数据库服务端软件进行通信,向服务端传输数据或者从服务端获取数据。
### 1.2.1 RDMS与NoSQL对比
#### 1.2.1.1 功能对比
| | 关系型数据库(sql/RDBMS) | 非关系型数据库 |
| :------------: | :---------------------: | :------------: |
| 强大的查询功能 | √ | × |
| 强一致性 | √ | × |
| 二级索引(目录) | √ | × |
| 灵活模式 | × | √ |
| 扩展性 | × | √ |
| 性能 | × | √ |
#### 1.2.1.2 特点对比
- 关系型数据库RDBMS的特点
- 二维表
- 典型产品Oracle传统企业MySQL互联网企业
- 数据存取是通过SQL语句Structured Query Language结构化查询语言
- 最大特点数据安全性方面强ACID
- 非关系型数据库NoSQLNot only SQL的特点
- 不是否定关系型数据库,而是做关系型数据库的补充
### 1.2.2 数据库市场
#### 1.2.2.1 MySQL的市场应用
- 中、大型互联网公司
- 市场空间:互联网领域第一
- 趋势明显
- 同源产品MariaDB、PerconaDB
#### 1.2.2.2 其他产品
- 微软SQLserver
- 微软和sysbase合作开发的产品后来自己开发windows平台
- 三、四线小公司,传统行业在用
- IBMDB2
- 市场占有量小
- 目前只有:国有银行(人行,中国银行,工商银行等)、中国移动应用
- PostgreSQL
- MongoDB
- Redis
## 1.3 MySQL数据库
MySQL是一个关系型数据库管理系统由瑞典 MySQL AB公司开发属于Oracle旗下产品。MySQL是最流行的关系型数据库管理系统之一在WEB应用方面MySQL是最好的RDBMS (Relational Database Management System关系数据库管理系统应用软件之一。
**数据库管理员(Database Administrator简称*DBA*)**是从事管理和维护数据库管理系统(DBMS)的相关工作人员的统称,属于运维工程师的一个分支,主要负责业务数据库从设计、测试到部署交付的全生命周期管理。
### 1.3.1 DBA工作内容
如下图所示分别是开发DBA和运维DBA
<img src="01.Mysql/image-20240721145430464.png" alt="image-20240721145430464" style="zoom: 80%;" />
包括但不限于以下工作内容:
- 数据管理
- 对库和表的创建
- 基本的数据增删改查
- 用户管理
- root运维用户ops程序连接用户(只读用户,读写用户)
- 以及对于不同用户的权限管理
- 集群管理
- 数据备份、恢复
- 逻辑备份、物理备份、冷备、热备、温备、全备、增量备份、差异备份等
- 健康及状态监控
- 进程,端口、集群状态、主从复制 延时情况、SQL读写速率等
### 1.3.2 MySQL发展史
- 1979年报表工具Unireg出现。
- 1985年以瑞典David Axmark为首成立了一家公司AB前身ISAM引擎出现。
- 1990年提供SQL支持。
- 1999年-2000年MySQL AB公司成立并公布源码开源化。
- 2000年4月BDB引擎出现支持事务。
- 2008年1月16日 MySQL被Sun公司收购。
- 2009年4月20日Oracle收购Sun公司MySQL转入Oracle。
# 2. MySQL的安装
## 2.1 安装方式
- rpm、yum安装
- 安装方便、安装速度快、无法定制
- 二进制
- 不需要安装,解压即可使用,不能定制功能
- 编译安装
- 可定制,安装慢
- 四个步骤
- 解压(tar)
- 生成(./configure) cmake
- 编译(make)
- 安装(make install)
## 2.2 源码安装
- MySQL版本选择
- https://downloads.mysql.com/archives/community/
- 5.6GA(稳定版) 6-12个月 小版本是偶数版是稳定版,奇数版本是开发版
- 5.7选择5.17版本以上支持MGRMySQL自带的高可用
- 下载源码,并且配置编译环境
```shell
wget https://downloads.mysql.com/archives/get/p/23/file/mysql-5.6.40.tar.gz
tar xzvf mysql-5.6.40.tar.gz
cd mysql-5.6.40
yum install -y ncurses-devel libaio-devel cmake gcc gcc-c++ glibc
```
- 创建mysql用户
```shell
useradd mysql -s /sbin/nologin -M
```
- 编译并安装
```shell
[root@localhost mysql-5.6.40]# mkdir /application
[root@localhost mysql-5.6.40]# cmake . -DCMAKE_INSTALL_PREFIX=/application/mysql-5.6.40 \
-DMYSQL_DATADIR=/application/mysql-5.6.40/data \
-DMYSQL_UNIX_ADDR=/application/mysql-5.6.40/tmp/mysql.sock \
-DDEFAULT_CHARSET=utf8 \
-DDEFAULT_COLLATION=utf8_general_ci \
-DWITH_EXTRA_CHARSETS=all \
-DWITH_INNOBASE_STORAGE_ENGINE=1 \
-DWITH_FEDERATED_STORAGE_ENGINE=1 \
-DWITH_BLACKHOLE_STORAGE_ENGINE=1 \
-DWITHOUT_EXAMPLE_STORAGE_ENGINE=1 \
-DWITH_ZLIB=bundled \
-DWITH_SSL=bundled \
-DENABLED_LOCAL_INFILE=1 \
-DWITH_EMBEDDED_SERVER=1 \
-DENABLE_DOWNLOADS=1 \
-DWITH_DEBUG=0
[root@localhost mysql-5.6.40]# c #查看上条命令是否运行正确0表示正确
[root@localhost mysql-5.6.40]# make -j 2
[root@localhost mysql-5.6.40]# make install
```
- 创建配置文件
```shell
ln -s /application/mysql-5.6.38/ /application/mysql
cd /application/mysql/support-files/
cp my-default.cnf /etc/my.cnf
cp是否覆盖"/etc/my.cnf" y
```
- 创建启动脚本
```shell
cp mysql.server /etc/init.d/mysqld
```
- 初始化数据库
```shell
cd /application/mysql/scripts/
yum -y install autoconf
./mysql_install_db --user=mysql --basedir=/application/mysql --datadir=/application/mysql/data/
```
- 启动数据库
```shell
mkdir /application/mysql/tmp
chown -R mysql.mysql /application/mysql*
/etc/init.d/mysqld start
```
- 配置环境变量
```shell
vim /etc/profile.d/mysql.sh
export PATH="/application/mysql/bin:$PATH"
source /etc/profile
```
- systemd管理mysql
```shell
vim /usr/lib/systemd/system/mysqld.service
[Unit]
Description=MySQL Server
Documentation=man:mysqld(8)
Documentation=https://dev.mysql.com/doc/refman/en/using-systemd.html
After=network.target
After=syslog.target
[Install]
WantedBy=multi-user.target
[Service]
User=mysql
Group=mysql
ExecStart=/application/mysql/bin/mysqld --defaults-file=/etc/my.cnf
LimitNOFILE = 5000
vim /etc/my.cnf
basedir = /application/mysql/
datadir = /application/mysql/data
systemctl daemon-reload
```
- 设置mysql开机启动并且开始mysql服务
```shell
systemctl start mysqld
systemctl enable mysqld
```
- 设置mysql密码并且登录测试
```shell
mysqladmin -uroot password '123456'
mysql -uroot -p123456
mysql> show databases;
mysql> \q
Bye
```
## 2.3 二进制安装
当前运维和开发最常见的做法是二进制安装mysql
- 下载二进制包并且解压
```shell
[root@localhost ~]# wget https://downloads.mysql.com/archives/get/p/23/file/mysql-5.6.40-linux-glibc2.12-x86_64.tar.gz
[root@localhost ~]# tar xzvf mysql-5.6.40-linux-glibc2.12-x86_64.tar.gz
```
- 然后的步骤就和编译安装一样
```shell
[root@localhost ~]# mkdir /application
[root@localhost ~]# mv mysql-5.6.40-linux-glibc2.12-x86_64 /application/mysql-5.6.40
[root@localhost ~]# ln -s /application/mysql-5.6.40 /application/mysql
#为了后续写的脚本方便监控mysql
[root@localhost ~]# cd /application/mysql/support-files
#该文件夹有mysql初始化预设配置文件覆盖文件是因为注释更全。
[root@localhost ~]# cp my-default.cnf /etc/my.cnf
[root@localhost ~]# mkdir /etc/init.d
[root@localhost ~]# cp mysql.server /etc/init.d/mysqld
# mysql.server包含如何启动mysql的脚本命令让系统知道通过该命令启动mysql时的动作该目录存放系统中各种服务的启动/停止脚本
[root@localhost ~]# cd /application/mysql/scripts
[root@localhost scripts]# useradd mysql -s /sbin/nologin -M
[root@localhost scripts]# yum -y install autoconf
[root@localhost scripts]# yum install -y perl-Sys-Hostname
[root@localhost scripts]# ./mysql_install_db --user=mysql --basedir=/application/mysql --data=/application/mysql/data
[root@localhost ~]# vim /etc/profile.d/mysql.sh
#写到环境变量的子配置文件内,未修改前只能使用/bin/mysql的命令才能启用而不能全局使用
export PATH="/application/mysql/bin:$PATH"
[root@localhost ~]# source /etc/profile #否则要重启系统才生效
```
- 需要注意,官方编译的二进制包默认是在`/usr/local`目录下的,我们需要修改配置文件
```shell
[root@localhost ~]# sed -i 's#/usr/local#/application#g' /etc/init.d/mysqld /application/mysql/bin/mysqld_safe
```
此时不可以通过systemctl命令启动只能通过/etc/init.d/mysqld start启动nginx也是如果此时通过这样的命令启动Nginx会导致systemctl start Nginx失败因为冲突。
- 创建systemd管理文件并且测试是否正常使用
```shell
[root@localhost ~]# vim /usr/lib/systemd/system/mysqld.service
[Unit]
Description=MySQL Server
Documentation=man:mysqld(8)
Documentation=https://dev.mysql.com/doc/refman/en/using-systemd.html
After=network.target
After=syslog.target
[Install]
WantedBy=multi-user.target
[Service]
User=mysql
Group=mysql
ExecStart=/application/mysql/bin/mysqld --defaults-file=/etc/my.cnf
#强制从my.cnf读配置不然会从多个路径读配置
LimitNOFILE = 5000
server_id = 1
#用作主从的时候生效
[root@localhost ~]# vim /etc/my.cnf
basedir = /application/mysql/
datadir = /application/mysql/data
# 关闭防火墙和selinux负责会因为权限问题启动不了
[root@localhost scripts]# setenforce 0
[root@localhost scripts]# systemctl stop firewalld
# 启动mysqld服务
[root@localhost ~]# systemctl daemon-reload
[root@localhost ~]# systemctl start mysqld
[root@localhost ~]# systemctl enable mysqld
#此时ss -ntl 可以看到3306端口
[root@localhost ~]# mysqladmin -uroot password '123456'
# 由于RockyLinux9版本的lib库较新为了适配mysql5.6版本,我们通过软连接的方式降级
[root@localhost ~]# ln -s /usr/lib64/libncurses.so.6 /usr/lib64/libncurses.so.5
[root@localhost ~]# ln -s /usr/lib64/libtinfo.so.6 /usr/lib64/libtinfo.so.5
# 登录数据库测试
[root@localhost ~]# mysql -uroot -p123456
```
## 2.4 客户端工具
### 2.4.1 mysql
用来**连接**和**管理**数据库
### 2.4.2 mysqladmin
- “强制回应 (Ping)”服务器
- 关闭服务器
- 创建和删除数据库
- 显示服务器和版本信息
- 显示或重置服务器状态变量
- 设置口令
- 重新刷新授权表
- 刷新日志文件和高速缓存
- 启动和停止复制
```shell
[root@localhost ~]# mysqladmin -uroot -p123456 create hellodb
[root@localhost ~]# mysqladmin -uroot -p123456 drop hellodb
[root@localhost ~]# mysqladmin -uroot -p123456 ping # 检查服务端状态的
[root@localhost ~]# mysqladmin -uroot -p123456 status # 服务器运行状态
[root@localhost ~]# mysqladmin -uroot -p123456 status # 服务器状态 --sleep 2 --count 10 每两秒钟显示⼀次。服务器实时状态⼀共显示10次
Uptime:是mysql正常运行的时间。
Threads:指开启的会话数。
Questions 服务器启动以来客户的问题(查询)数目 应该是只要跟mysql作交互不管你查询表还是查询服务器状态都问记一次
Slow queries按字面意思是慢查询的意思不知道mysql认为多久才足够算为长查询这个先放着。
Opens 服务器已经打开的数据库表的数量
Flush tables: 服务器已经执行的flush ...、refresh和reload命令的数量。
open tables通过命令是用的 数据库的表的数量,以服务器启动开始。
Queries per second avgselect语句平均查询时间
[root@localhost ~]# mysqladmin -uroot -p123456 extended-status 显示状态变量
[root@localhost ~]# mysqladmin -uroot -p123456 variables 显示服务器变量
[root@localhost ~]# mysqladmin -uroot -p123456 flush-privileges 数据库重读授权表等同于reload
[root@localhost ~]# mysqladmin -uroot -p123456 flush-tables 关闭所有已经打开的表
[root@localhost ~]# mysqladmin -uroot -p123456 flush-threds 重置线程池缓存
[root@localhost ~]# mysqladmin -uroot -p123456 flush-status 重置⼤多数服务器状态变量
[root@localhost ~]# mysqladmin -uroot -p123456 flush-logs ⽇志滚动。主要实现⼆进制和中继⽇志滚动
[root@localhost ~]# mysqladmin -uroot -p123456 flush-hosts 清楚主机内部信息
[root@localhost ~]# mysqladmin -uroot -p123456 kill 杀死线程
[root@localhost ~]# mysqladmin -uroot -p123456 refresh 相当于同时执⾏flush-hosts flush-logs
[root@localhost ~]# mysqladmin -uroot -p123456 shutdown 关闭服务器进程
[root@localhost ~]# mysqladmin -uroot -p123456 version 服务器版本以及当前状态信息
[root@localhost ~]# mysqladmin -uroot -p123456 start-slave 启动复制,启动从服务器复制线程
[root@localhost ~]# mysqladmin -uroot -p123456 stop-slave 关闭复制线程
```
### 2.4.3 mysqldump
- 备份数据库和表的内容
```shell
mysqldump -uroot -p --all-databases > /backup/mysqldump/all.db
# 备份所有数据库
mysqldump -uroot -p test > /backup/mysqldump/test.db
# 备份指定数据库
mysqldump -uroot -p mysql db event > /backup/mysqldump/2table.db
# 备份指定数据库指定表(多个表以空格间隔)
mysqldump -uroot -p test --ignore-table=test.t1 --ignore-table=test.t2 > /backup/mysqldump/test2.db
# 备份指定数据库排除某些表
```
- 还原的方法
```shell
mysqladmin -uroot -p create db_name
mysql -uroot -p db_name < /backup/mysqldump/db_name.db
# 注在导入备份数据库前db_name如果没有是需要创建的 而且与db_name.db中数据库名是一样的才可以导入。
mysql > use db_name
mysql > source /backup/mysqldump/db_name.db
# source也可以还原数据库
```
## 2.5 图形化工具Navicate
在windows上使用navicate工具的时候需要先登录到MySQL中授权来自windows的IP访问
```bash
mysql > grant all privileges on *.* to root@"192.168.88.%" identified by "123456";
mysql > flush privileges;
```
然后打开navicate输入数据库的连接信息连接到数据库中
<img src="01.Mysql/image-20240721151426381.png" alt="image-20240721151426381" style="zoom:80%;" />
# 3. MySQL架构
## 3.1 客户端与服务器模型
![img](01.Mysql/tL76EP1rBEQKcpeU-173958397657497.png!thumbnail)
- mysql是一个典型的C/S服务结构
- mysql自带的客户端程序/application/mysql/bin
- mysql
- mysqladmin
- mysqldump
- mysqld一个二进制程序后台的守护进程
- 单进程
- 多线程
- 应用程连接MySQL方式
- TCP/IP的连接方式远程方式PHP应用可安装于另一台机器
测试需要服务器数据库
mysql> grant all privileges on *.* to root@'192.168.128.%' identified by '123456';
客户端数据库
yum -y install mariadb #安装客户端
mysql -uroot -p123456 -h192.168.128.X -P3306
- socket**默认连接方式,**本地连接快速无需建立TCP连接通过/application/mysql/tmp下的mysql.sock文件
- `mysql -uroot -p123456 -h127.0.0.1 #TCP连接方式`
```shell
[root@localhost ~]# mysql -uroot -p123456 -h127.0.0.1
Warning: Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 4
Server version: 5.6.40 MySQL Community Server (GPL)
Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql> status;
--------------
mysql Ver 14.14 Distrib 5.6.40, for linux-glibc2.12 (x86_64) using EditLine wrapper
Connection id: 4
Current database:
Current user: root@localhost
SSL: Not in use
Current pager: stdout
Using outfile: ''
Using delimiter: ;
Server version: 5.6.40 MySQL Community Server (GPL)
Protocol version: 10
Connection: 127.0.0.1 via TCP/IP
Server characterset: latin1
Db characterset: latin1
Client characterset: utf8
Conn. characterset: utf8
TCP port: 3306
Uptime: 1 hour 55 min 9 sec
Threads: 2 Questions: 18 Slow queries: 0 Opens: 67 Flush tables: 1 Open tables: 60 Queries per second avg: 0.002
* 套接字连接方式
* `mysql -uroot -p123456 -S /application/mysql/tmp/mysql.sock`
```
## 3.2 MySQL服务器构成
### 3.2.1 mysqld服务结构
- 实例=mysqld后台守护进程+Master Thread +干活的Thread+预分配的内存
- 公司=老板+经理+员工+办公室
![img](01.Mysql/k1P7hTxOM3vKCIG2-173958397657498.png!thumbnail)
- 连接层
- 验证用户的合法性(ip,端口,用户名)
- 提供两种连接方式(socket,TCP/IP)
- 验证操作权限
- 提供一个与SQL层交互的专用线程
- SQL层
- 接受连接层传来的SQL语句
- 检查语法
- 检查语义(DDL,DML,DQL,DCL)
- 解析器解析SQL语句生成多种执行计划
- 优化器,根据多种执行计划,选择最优方式
- 执行器执行优化器传来的最优方式SQL
- 提供与存储引擎交互的线程
- 接收返回数据优化成表的形式返回SQL
- 将数据存入缓存
- 记录日志binlog
- 存储引擎
- 接收上层的执行结构
- 取出磁盘文件和相应数据
- 返回给SQL层结构化之后生成表格由专用线程返回给客户端
### 3.2.2 mysql逻辑结构
MySQL的逻辑对象做为管理人员或者开发人员操作的对象
-
- 表:元数据+真实数据行
- 元数据:列+其它属性(行数+占用空间大小+权限)
- 列:列名字+数据类型+其他约束(非空、唯一、主键、非负数、自增长、默认值)
最直观的数据:二维表,必须用库来存放
![img](01.Mysql/DsbxGtxBZzy6d1kN-173958397657599.png!thumbnail)
mysql逻辑结构与Linux系统对比
| MySQL | Linux |
| :----------------------- | :---------------------------- |
| 库 | 目录 |
| show databases; | ls -l / |
| use mysql | cd /mysql |
| 表 | 文件 |
| show tables; | ls |
| 二维表=元数据+真实数据行 | 文件=文件名+文件属性+文件内容 |
### 3.2.3 mysql的物理结构
- MySQL的最底层的物理结构是数据文件也就是说存储引擎层打交道的文件是数据文件。
- 存储引擎分为很多种类
- 不同存储引擎的区别:存储方式、安全性、性能
myisam:
- mysql自带的表部分就是使用的myisam
```shell
mysql> show create table mysql.user\G;
...
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='Users and global privileges'
```
![img](01.Mysql/JnL2YEThjuHERQPZ-1739583976575100.png!thumbnail)
innodb:
- 自己创建一个表在编译的时候已经默认指定使用innodb
![img](01.Mysql/lK3mQW6m9VMwIgVD-1739583976575101.png!thumbnail)
![img](01.Mysql/WCtzPCFRsaTUTp3A-1739583976575102.png!thumbnail)
# 4. Mysql用户权限管理
## 4.1 Mysql用户基础操作
- Linux用户的作用
- 登录系统
- 管理系统文件
- Linux用户管理
- 创建用户:useradd
- 删除用户:userdel
- 修改用户:usermod
- Mysql用户的作用
- 登录Mysql数据库
- 管理数据库对象
- Mysql用户管理
- 创建用户:create user
- 删除用户:delete user、drop user
- 修改用户:update
- 用户的定义
- username@'主机域'
- 主机域:可以理解为是Mysql登录的白名单
- 主机域格式:
- 10.1.1.12
- 10.1.0.1%
- 10.1.0.%
- 10.1.%.%
- %
- localhost
- 192.168.1.1/255.255.255.0
- 刚装完mysql数据库该做的事情
- 设定初始密码
```shell
[root@localhost ~]# mysqladmin -uroot password '123456'
# 忘记root密码
[root@localhost ~]# systemctl stop mysqld
[root@localhost ~]# mysqld_safe --skip-grant-tables --skip-networking
# skip-networking 禁止掉3306端口不允许网络登录
# 修改root密码
[root@localhost ~]# mysql -uroot
mysql> update mysql.user set password=PASSWORD('123') where user='root' and host='localhost';
mysql> flush privileges;
# 杀死mysqld服务重启
[root@localhost ~]# pkill mysqld
[root@localhost ~]# systemctl start mysqld
```
## 4.2 用户管理
- 创建用户
```sql
mysql> create user user01@'192.168.88.%' identified by '123456';
```
- 查看用户
```sql
mysql> select user,host from mysql.user;
```
- 查看当前用户
```sql
mysql> select user();
```
- 查看当前数据库
```sql
mysql> select database();
```
- 删除用户
```sql
mysql> drop user user01@'192.168.88.%';
```
- 修改密码
```sql
mysql> update mysql.user set password=PASSWORD('user01') where user='root' and host='localhost';
mysql> grant all privileges on *.* to user01@'192.168.88.%' identified by '123456';
```
## 4.3 用户权限介绍
- 权限
```sql
INSERT,SELECT, UPDATE, DELETE, CREATE, DROP, RELOAD, SHUTDOWN, PROCESS, FILE, REFERENCES, INDEX, ALTER, SHOW DATABASES, SUPER, CREATE TEMPORARY TABLES, LOCK TABLES, EXECUTE, REPLICATION SLAVE, REPLICATION CLIENT, CREATE VIEW, SHOW VIEW, CREATE ROUTINE, ALTER ROUTINE, CREATE USER, EVENT, TRIGGER, CREATE TABLESPACE
```
1. **INSERT**: 用于向数据库表中插入新数据。
2. **SELECT**: 用于从数据库表中查询和检索数据。
3. **UPDATE**: 用于更新数据库表中的现有数据。
4. **DELETE**: 用于从数据库表中删除数据。
5. **CREATE**: 用于创建数据库对象,如表、视图、存储过程等。
6. **DROP**: 用于删除数据库对象,如表、视图、存储过程等。
7. **RELOAD**: 用于重新加载 MySQL 服务器的配置文件。
8. **SHUTDOWN**: 用于关闭 MySQL 服务器。
9. **PROCESS**: 用于查看和管理 MySQL 服务器的进程。
10. **FILE**: 用于读写服务器上的文件。
11. **REFERENCES**: 用于创建外键约束。
12. **INDEX**: 用于创建和管理数据库索引。
13. **ALTER**: 用于修改数据库对象的结构,如表、视图、存储过程等。
14. **SHOW DATABASES**: 用于查看当前 MySQL 服务器上的所有数据库。
15. **SUPER**: 是一种特殊的权限,允许用户执行一些高级操作,如查看和杀死其他用户的进程。
16. **CREATE TEMPORARY TABLES**: 用于创建临时表。
17. **LOCK TABLES**: 用于手动锁定数据库表。
18. **EXECUTE**: 用于执行存储过程和函数。
19. **REPLICATION SLAVE**: 用于配置数据库复制时的从库权限。
20. **REPLICATION CLIENT**: 用于获取数据库复制状态信息。
21. **CREATE VIEW**: 用于创建数据库视图。
22. **SHOW VIEW**: 用于查看数据库视图的定义。
23. **CREATE ROUTINE**: 用于创建存储过程和函数。
24. **ALTER ROUTINE**: 用于修改存储过程和函数。
25. **CREATE USER**: 用于创建新的数据库用户账号。
26. **EVENT**: 用于创建和管理数据库事件。
27. **TRIGGER**: 用于创建和管理数据库触发器。
28. **CREATE TABLESPACE**: 用于创建数据库表空间。
- 每次设定只能有一个属主,没有属组或其他用户的概念
```sql
grant all privileges on *.* to user01@''192.168.88.%'' identified by ''123'';
权限 作用对象 归属 密码
```
作用对象分解
- *.* [当前MySQL实例中所有库下的所有表]
- wordpress.* [当前MySQL实例中wordpress库中所有表单库级别]
- wordpress.user [当前MySQL实例中wordpress库中的user表单表级别]
企业中权限的设定
- 开发人员说:给我开一个用户
- 沟通
- 你需要对哪些库、表进行操作
- 你从哪里连接过来
- 用户名有没有要求
- 密码要求
- 发邮件
- 一般给开发创建用户权限
```sql
grant select,update,delete,insert on *.* to 'user01'@'192.168.175.%' identified by '123456';
```
实验思考问题
```sql
#创建wordpress数据库
create database wordpress;
#使用wordpress库
use wordpress;
#创建t1t2表
create table t1 (id int);
create table t2 (id int);
#创建blog库
create database blog;
#使用blog库
use blog;
#创建t1表
create table tb1 (id int);
```
授权
```sql
1grant select on *.* to wordpress@10.0.0.5% identified by 123;
2grant insert,delete,update on wordpress.* to wordpress@10.0.0.5% identified by 123;
3grant all on wordpress.t1 to wordpress@10.0.0.5% identified by 123;
```
- 一个客户端程序使用wordpress用户登陆到10.0.0.51的MySQL后
- 对t1表的管理能力
- 对t2表的管理能力
- 对tb1表的管理能力
-
- 同时满足123最终权限是1+2+3
- 同时满足了1和2两个授权最终权限是1+2
- 只满足1授权所以只能select
- 结论
- 如果在不同级别都包含某个表的管理能力时,权限是相加关系。
- 但是我们不推荐在多级别定义重复权限。
- 最常用的权限设定方式是单库级别授权wordpress.*
# 5. Mysql启动关闭流程
![img](01.Mysql/uCoOhC6Rn5HuTfxg-1739583976575103.png!thumbnail)
- 启动
/usr/lib/systemd/system/mysqld.service告知系统从哪执行mysqld
mysld_safe是个脚本mysqld才是binary
```shell
/etc/init.d/mysqld start ------> mysqld_safe ------> mysqld
systemctl start mysqld
```
- 关闭
```shell
/etc/init.d/mysqld stop
systemctl stop mysqld
mysqladmin -uroot -p123456 shutdown
kill -9 pid ?
killall mysqld ?
pkill mysqld ?
pkill mysqld_safe
```
出现问题:
1. 如果在业务繁忙的情况下数据库不会释放pid和sock文件
2. 号称可以达到和Oracle一样的安全性但是并不能100%达到
3. 在业务繁忙的情况下,丢数据(补救措施,高可用)
# 6. Mysql实例初始化配置
- 在启动一个实例的时候,必须要知道如下的问题
- 我不知道我的程序在哪?
- 我也不知道我将来启动后去哪找数据库?
- 将来我启动的时候启动信息和错误信息放在哪?
- 我启动的时候sock文件pid文件放在哪
- 我启动,你们给了我多少内存?
- ......若干问题
![img](01.Mysql/Tw0AJl9FjrygD1S4-1739583976575104.png!thumbnail)
- 预编译cmake去指定硬编译到程序当中去
- 在命令行设定启动初始化配置
```shell
--skip-grant-tables
--skip-networking
--datadir=/application/mysql/data
--basedir=/application/mysql
--defaults-file=/etc/my.cnf
--pid-file=/application/mysql/data/db01.pid
--socket=/application/mysql/data/mysql.sock
--user=mysql
--port=3306
--log-error=/application/mysql/data/db01.err
```
- 初始化配置文件(/etc/my.cnf
- --defaults-file默认配置文件
- 如果使用./bin/mysqld_safe 守护进程启动mysql数据库时使用了 --defaults-file=<配置文件的绝对路径>参数,这时只会使用这个参数指定的配置文件。
```shell
#cmake
socket=/application/mysql/tmp/mysql.sock
#命令行:
--socket=/tmp/mysql.sock
#配置文件:
/etc/my.cnf中[mysqld]标签下socket=/opt/mysql.sock
#default参数
--defaults-file=/tmp/a.txt配置文件中[mysqld]标签下socket=/tmp/test.sock
```
- 优先级结论
- 命令行
- defaults-file
- 配置文件
- 预编译
- 初始化配置文件功能
- 影响实例的启动mysqld
- 影响到客户端
- 配置标签分类
- [client]所有客户端程序
- [server]所有服务器程序
# 7. MySQL多实例配置
## 7.1 多实例
- 多套后台进程+线程+内存结构
- 多个配置文件
- 多端口
- 多socket文件
- 多个日志文件
- 多个server_id
- 多套数据
## 7.2 实战配置
```shell
#创建数据目录
[root@localhost ~]# mkdir -p /data/330{7..9}
#创建配置文件
[root@localhost ~]# touch /data/330{7..9}/my.cnf
[root@localhost ~]# touch /data/330{7..9}/mysql.log
#编辑3307配置文件
[root@localhost ~]# vim /data/3307/my.cnf
[mysqld]
basedir=/application/mysql
datadir=/data/3307/data
socket=/data/3307/mysql.sock
log_error=/data/3307/mysql.log
log-bin=/data/3307/mysql-bin
server_id=7
port=3307
[client]
socket=/data/3307/mysql.sock
#编辑3308配置文件
[root@localhost ~]# vim /data/3308/my.cnf
[mysqld]
basedir=/application/mysql
datadir=/data/3308/data
socket=/data/3308/mysql.sock
log_error=/data/3308/mysql.log
log-bin=/data/3308/mysql-bin
server_id=8
port=3308
[client]
socket=/data/3308/mysql.sock
#编辑3309配置文件
[root@localhost ~]# vim /data/3309/my.cnf
[mysqld]
basedir=/application/mysql
datadir=/data/3309/data
socket=/data/3309/mysql.sock
log_error=/data/3309/mysql.log
log-bin=/data/3309/mysql-bin
server_id=9
port=3309
[client]
socket=/data/3309/mysql.sock
# 修改目录权限
[root@localhost ~]# chown -R mysql.mysql /data/330*
# 初始化3307数据,每条命令需要间隔若干秒,否则失败
[root@localhost ~]# /application/mysql/scripts/mysql_install_db \
--user=mysql \
--defaults-file=/data/3307/my.cnf \
--basedir=/application/mysql --datadir=/data/3307/data
#初始化3308数据,每条命令需要间隔若干秒,否则失败
[root@localhost ~]# /application/mysql/scripts/mysql_install_db \
--user=mysql \
--defaults-file=/data/3308/my.cnf \
--basedir=/application/mysql --datadir=/data/3308/data
#初始化3309数据,每条命令需要间隔若干秒,否则失败
[root@localhost ~]# /application/mysql/scripts/mysql_install_db \
--user=mysql \
--defaults-file=/data/3309/my.cnf \
--basedir=/application/mysql --datadir=/data/3309/data
#启动多实例
[root@localhost ~]# mysqld_safe --defaults-file=/data/3307/my.cnf &
[root@localhost ~]# mysqld_safe --defaults-file=/data/3308/my.cnf &
[root@localhost ~]# mysqld_safe --defaults-file=/data/3309/my.cnf &
#设置每个实例的密码
[root@localhost ~]# mysqladmin -S /data/3307/mysql.sock -uroot password '123456'
#查看server_id
[root@localhost ~]# mysql -S /data/3307/mysql.sock -uroot -p123456 -e "show variables like 'server_id'"
[root@localhost ~]# mysql -S /data/3308/mysql.sock -uroot -p123456 -e "show variables like 'server_id'"
[root@localhost ~]# mysql -S /data/3309/mysql.sock -uroot -p123456 -e "show variables like 'server_id'"
# 进入单独的mysql实例
[root@localhost ~]# mysql -S /data/3307/mysql.sock -uroot
# 关闭实例,如果设置了密码登录就需要密码了 用参数-p
[root@localhost ~]# mysqladmin -S /data/3307/mysql.sock -uroot -p123456 shutdown
[root@localhost ~]# mysqladmin -S /data/3308/mysql.sock -uroot -p123456 shutdown
[root@localhost ~]# mysqladmin -S /data/3309/mysql.sock -uroot -p123456 shutdown
```
# 8. SQL语句
- SQL是结构化的查询语句
- SQL的种类
- DDL:数据定义语句
- DCL:数据控制语言
- DML:数据操作语言
- DQL:数据查询语言
## 8.1 DDL数据定义语句
- 对库或者表进行操作的语句
- 创建数据库
```sql
create database db01;
# 创建数据库
create database DB01;
# 数据库名区分大小写(注意windows里面不区分)
show variables like 'lower_case_table_names';
+------------------------+-------+
| Variable_name | Value |
+------------------------+-------+
| lower_case_table_names | 0 |
+------------------------+-------+
具体lower_case_table_names 取值详解如下:
0:区分大小写
1:不区分大小写,存储的时候,将表名都转换成小写
2:不区分大小写,存储的时候,表名跟建表时大小写保持一致
show databases;
# 查看数据库(DQL)
show create database db01;
# 查看创建数据库语句
help create database;
# 查看创建数据库语句帮助
create database db02 charset utf8;
# 创建数据库的时候添加属性
```
- 删除数据库
```sql
drop database db02;
# 删除数据库db02
```
- 修改定义库
```sql
alter database db01 charset utf8;
show create database db01;
```
- 创建表
```sql
help create table;
# 查看创表语句的帮助
use db01;
# 进入库中
create table student(
sid int,
sname varchar(20),
sage tinyint,
sgender enum('m','f'),
cometime datetime
);
# 创建表,并且定义每一列
```
- 数据类型(下面有完整的)
| int | 整数 -231~230 |
| :------- | :-------------------- |
| tinyint | 整数 -128~127 |
| varchar | 字符类型(可变长) |
| char | 字符类型(定长) |
| enum | 枚举类型 |
| datetime | 时间类型 年月日时分秒 |
```sql
# 删除表
drop table student;
# 带数据属性创建学生表
create table student(
sid int not null primary key auto_increment comment '学号',
sname varchar(20) not null comment '学生姓名',
sgender enum('m','f') not null default 'm' comment '学生性别',
cometime datetime not null comment '入学时间'
)charset utf8 engine innodb;
# 查看建表语句
show create table student;
# 查看表
show tables;
# 查看表中列的定义信息
desc student;
```
- 数据属性
| not null | 不允许是空 |
| :------------- | :---------------------------------------- |
| primary key | 主键(唯一且非空) |
| auto_increment | 自增此列必须是primary key或者unique key |
| unique key | 单独的唯一的 |
| default | 默认值 |
| unsigned | 非负数 |
| comment | 注释 |
- 删除表
```sql
drop table student;
```
- 修改表的定义
```sql
alter table student rename stu;
# 修改表名
alter table stu add age int;
# 添加列和列数据类型的定义
alter table stu add test varchar(20),add qq int;
# 添加多个列
alter table stu add classid varchar(20) first;
# 指定位置进行添加列(表首)
alter table stu add phone int after age;
# 指定位置进行添加列(指定列)
alter table stu drop qq;
# 删除指定的列及定义
alter table stu modify sid varchar(20);
# 修改列及定义(列属性)
alter table stu change phone telphone char(20);
# 修改列及定义(列名及属性)
```
## 8.2 DCL数据控制语言
- DCL是针对权限进行控制
- 授权
```sql
grant all on *.* to root@'192.168.88.%' identified by '123456'
# 授予root@'192.168.175.%'用户所有权限(非超级管理员)
grant all on *.* to root@'192.168.88.%' identified by '123456' with grant option;
# 授权一个超级管路员
max_queries_per_hour:一个用户每小时可发出的查询数量
max_updates_per_hour:一个用户每小时可发出的更新数量
max_connections_per_hour:一个用户每小时可连接到服务器的次数
max_user_connections:允许同时连接数量
```
- 收回权限
```sql
revoke select on *.* from root@'192.168.88.%';
# 收回select权限
show grants for root@'192.168.88.%';
# 查看权限
```
## 8.3 DML数据操作语言
- 操作表中的数据
- 插入数据
```sql
create table stu(
sid int not null primary key auto_increment comment '学号',
sname varchar(20) not null comment '学生姓名',
sgender enum('m','f') not null default 'm' comment '学生性别',
sage int comment '年龄',
qq varchar(20) comment 'QQ',
telphone varchar(20) comment '电话号',
cometime datetime not null comment '入学时间'
)charset utf8 engine innodb;
insert into stu values(1,'zhangsan','m',19,'123123','18888888888',NOW());
# 基础用法,插入数据
insert into stu(sname,sgender,sage,qq,telphone,cometime) values('lisi','f',20,'11111',1777777777,NOW());
# 规范用法,插入数据
insert into stu(sname,sgender,sage,qq,telphone,cometime) values('linux01','f',20,'11111',1777777777,NOW()),
('linux02','f',20,'22222',1777777777,NOW());
# 插入多条数据
insert into stu(sname,cometime) values('linux03',NOW());
# 不是非空字段可以为空
```
- 更新数据
```sql
update stu set sgender='f';
# 不规范
update stu set sgender='f' where sid=1;
# 规范update修改
update stu set sgender='f' where 1=1;
# 如果非要全表修改
update mysql.user set password=PASSWORD('123456') where user='root' and host='localhost';
# 修改密码,需要刷新权限flush privileges
```
- 删除数据`
```sql
delete from stu;
# 不规范
delete from stu where sid=6;
# 规范删除(危险)
truncate table stu;
# DDL清空表中的内容,恢复表原来的状态,自增重新从1开始,否则delete依旧正常增长
```
- 使用伪删除
- 有时候一些重要数据不能直接删除,只能伪删除,因为以后还得使用呢
- 使用update代替delete将状态改成删除状态在查询的时候就可以不显示被标记删除的数据
```sql
alter table stu add status enum('1','0') default '1';
# 额外添加一个状态列
update stu set status='0' where sid=1;
# 使用update
select * from stu where status=1;
# 应用查询存在的数据
```
## 8.4 DQL数据查询语言
- select基础用法
- 演示用的SQL文件下载https://download.s21i.faiusr.com/23126342/0/0/ABUIABAAGAAgzcXwhQYozuPv2AE?f=world.sql&v=1622942413
```sql
mysql -uroot -p123 < world.sql
或者mysql> source /root/world.sql
select countrycode,district from city;
# 常用用法
select countrycode from city;
# 查询单列
select countrycode,district from city limit 2;
select id,countrycode,district from city limit 2,2; #从第二个开始继续显示2
# 行级查询
select name,population from city where countrycode='CHN';
# 条件查询
select name,population from city where countrycode='CHN' and district='heilongjiang';
# 多条件查询
select name,population,countrycode from city where countrycode like '%H%' limit 10;
# 模糊查询
select id,name,population,countrycode from city order by countrycode limit 10;
# 排序查询(顺序)
select id,name,population,countrycode from city order by countrycode desc limit 10;
# 排序查询(倒序)
select * from city where population>=1410000;
# 范围查询(>,<,>=,<=,<>)
select * from city where countrycode='CHN' or countrycode='USA';
# 范围查询OR语句
select * from city where countrycode in ('CHN','USA');
# 范围查询IN语句
select country.name,city.name,city.population,country.code from city,country where city.countrycode=country.code and city.population < 100;
# 多表查询
```
# 9. 字符集定义
- 什么是字符集Charset
- 字符集:是一个系统支持的所有抽象字符的集合。字符是各种文字和符号的总称,包括各国家文字、标点符号、图形符号、数字等。
![img](01.Mysql/157ueBBseL6MvenH-1739583976575105.png!thumbnail)
- MySQL数据库的字符集
- 字符集CHARACTER
- 校对规则COLLATION
- MySQL中常见的字符集
- UTF8
- LATIN1
- GBK
- 常见校对规则
- ci大小写不敏感
- cs或bin大小写敏感
- 我们可以使用以下命令查看
```sql
show charset;
show collation;
```
字符集设置
- 操作系统级别
```shell
source /etc/sysconfig/i18n
echo $LANG
```
- Mysql实例级别
```shell
cmake .
-DDEFAULT_CHARSET=utf8 \
-DDEFAULT_COLLATION=utf8_general_ci \
-DWITH_EXTRA_CHARSETS=all \
# 在编译的时候指定
[mysqld]
character-set-server=utf8
# 在配置文件中指定
mysql> create database db01 charset utf8 default collate = utf8_general_ci;
# 建库的时候
mysql> CREATE TABLE `test` (
`id` int(4) NOT NULL AUTO_INCREMENT,
`name` char(20) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8;
# 建表的时候
mysql> alter database db01 CHARACTER SET utf8 collate utf8_general_ci;
mysql> alter table t1 CHARACTER SET utf8;
# 修改字符集
```
# 10. MySQL数据类型
## 10.1 数据类型介绍
- 四种主要类别
- 数值
- 字符
- 二进制
- 时间
- 数据类型的ABC要素
- Appropriate(适当)
- Brief(简洁)
- Complete(完整)
- 数值数据类型
- 使用数值数据类型时的注意事项
- 数据类型所标识的值的范围
- 列值所需的空间量
- 列精度和范围(浮点数和定点数)
- 数值数据类型的类
- 整数:整数
- 浮点数:小数
- 定点数:精确值数值
- BIT位字段值
| 类 | 类型 | 说明 |
| :----- | :-------- | :----------------------------------------------- |
| 整数 | tinyint | 极小整数数据类型(0-255) |
| 整数 | smallint | 较小整数数据类型(-2^15到2^15-1) |
| 整数 | mediumint | 中型整数数据类型 |
| 整数 | int | 常规(平均)大小的整数数据类型(-2^31到2^31-1) |
| 整数 | bigint | 较大整数数据类型 |
| 浮点数 | float | 小型单精度(四个字节)浮点数 |
| 浮点数 | double | 常规双精度(八个字节)浮点数 |
| 定点数 | decimal | 包含整数部分、小数部分或同时包括二者的精确值数值 |
| BIT | BIT | 位字段值 |
- 字符串数据类型
- 表示给定字符集中的一个字母数字字符序列
- 用于存储文本或二进制数据
- 几乎在每种编程语言中都有实现
- 支持字符集和整理
- 属于以下其中一类
| 类 | 类型 | 说明 |
| :--- | :--------- | :----------------------------------- |
| 文本 | char | 固定长度字符串最多为255个字符 |
| 文本 | varchar | 可变长度字符串最多为65535个字符 |
| 文本 | tinytext | 可变长字符串最多为255个字符 |
| 文本 | text | 可变长字符串最多为65535个字符 |
| 文本 | mediumtext | 可变长字符串最多为16777215个字符 |
| 文本 | longtext | 可变长字符串最多为4294967295个字符 |
| 整数 | enum | 由一组固定的合法值组成的枚举 |
| 整数 | set | 由一组固定的合法值组成的集 |
- 文本:真实的非结构化字符串数据类型
- 整数:结构化字符串类型
- 二进制字符串数据类型
- 字节序列
- 二进制位按八位分组
- 存储二进制值
- 编译的计算机程序和应用程序
- 图像和声音文件
- 字符二进制数据类型的类
- 二进制:固定长度和可变长度的二进制字符串
- BLOB二进制数据的可变长度非结构化集合
| 类 | 类型 | 说明 |
| :----- | :---------- | :----------------------------------------------------------- |
| 二进制 | binary | 类似于char(固定长度)类型,但存储的是二进制字节字符串,而不是非二进制字符串 |
| 二进制 | varbinary | 类似于varchar(可变长度)类型, |
| BLOB | tinyblob | 最大长度为255个字节的BLOB列 |
| BLOB | blob | 最大长度为65535个字节的BLOB列 |
| BLOB | MEDIUDMBLOB | 最大长度为16777215个字节的BLOB列 |
| BLOB | longblob | 最大长度为4294967295个自己的blob列 |
- 时间数据类型
## 10.2 列属性介绍
- 列属性的类别
- 数值适用于数值数据类型BIT 除外)
- 字符串:适用于非二进制字符串数据类型
- 常规:适用于所有数据类型
| 数据类型 | 属性 | 说明 |
| :------- | :------------- | :------------------------------------- |
| 数值 | unsigned | 禁止使用负值 |
| 仅整数 | auto_increment | 生成包含连续唯一整数值的序列 |
| 字符串 | character set | 指定要使用的字符集 |
| 字符串 | collate | 指定字符集整理 |
| 字符串 | binary | 指定二进制整理 |
| 全部* | Null或not Null | 指定列是否可以包含NULL值 |
| 全部 | Default | 如果未为新记录指定值,则为其提供默认值 |
# 11. 索引介绍
索引是数据库中用于加快数据检索速度的数据结构。它类似于书籍的目录,可以帮助数据库快速定位到表中的特定行,而不需要扫描整个表。
## 11.1 索引的作用
- 加快数据检索速度。
- 提高查询性能。
- 通过唯一索引保证数据的唯一性。
## 11.2 索引类型介绍
### 11.2.1 按数据结构分类
- **B-Tree 索引**:最常见的索引类型,适用于全键值、键值范围或键值前缀查找。
- **Hash 索引**:基于哈希表实现,适用于等值查询,不支持范围查询。
- **Full-Text 索引**:用于全文搜索,适用于文本字段。
- **二叉树** :是一种每个节点最多支持两个分支的树结构,相比单向链表多了一个分支。二叉查找树在二叉树的基础上增加了规则,即左子树所有节点值小于根节点,右子树所有节点值大于根节点。但二叉查找树可能出现斜树问题,导致时间复杂度增加。
- **平衡二叉树AVL 树)** :具有二叉查找树的所有特点,同时规定左右两个子树的高度差绝对值不能超过 1。它通过左旋和右旋的方式来维持平衡。
- **B 树** :是一种多路平衡查找树,满足平衡二叉树的规则,但可以有多个子树。子树的数量取决于关键字的数量,比如根节点有两个关键字,那么它能拥有的子树数量是关键字数量加 1。在存储同样数据量的情况下平衡二叉树的高度要大于 B 树。
#### 11.2.1.1 B+树
<img src="01.Mysql/G1WukGpm4kWFCtKy-1739583976575106.png!thumbnail" alt="img" style="zoom:80%;" />
**B+树的特点**
1. 多路平衡树,每个节点可以有多个子节点,树始终保持平衡,适合高效查找。
2. 数据只在叶子节点,内部节点只存键值和指针(索引信息),叶子节点存键值和实际数据(或数据指针)。
3. 叶子节点通过指针连接成链表,支持高效的范围查询和顺序访问。
4. 适合磁盘存储,节点大小通常设计为磁盘块大小,减少磁盘 I/O 次数。
#### 11.2.1.2 B*树
<img src="01.Mysql/HET6YsqLtNayuI6Q-1739583976575107.png!thumbnail" style="zoom:80%;" />
#### 11.2.1.3 为什么 MySQL 使用 B+ 树?
1. **数据存储在叶子节点**
B+ 树的所有数据都存储在叶子节点,内部节点只存储键值和指针。这种设计使得树的高度更低,查询效率更高。
2. **叶子节点形成有序链表**
B+ 树的叶子节点通过指针连接成链表,非常适合范围查询(如 `BETWEEN``>``<` 等操作),只需遍历叶子节点即可。
3. **更适合磁盘存储**
B+ 树的节点大小通常设计为磁盘块的大小,减少了磁盘 I/O 次数,适合处理大规模数据。
4. **更高的查询效率**
由于 B+ 树的内部节点只存储键值和指针,一个节点可以存储更多的键值,从而降低树的高度,减少查找时间。
#### 11.2.1.4 B+ 树 vs B 树
| 特性 | B 树 | B+ 树 |
| :--------------- | :--------------------- | :------------------------- |
| **数据存储位置** | 所有节点都可以存储数据 | 只有叶子节点存储数据 |
| **叶子节点连接** | 叶子节点没有连接 | 叶子节点通过指针连接成链表 |
| **范围查询效率** | 较低 | 较高 |
| **适合场景** | 内存数据库或小规模数据 | 磁盘数据库或大规模数据 |
### 11.2.2 按功能分类
- **普通索引INDEX**:最基本的索引,没有任何限制。
- 就像是在一本书的页边做了一些标记,方便以后快速找到感兴趣的内容。
- 普通索引不要求值必须唯一,可以建立在单个列或多个列上。
- 普通索引可以提高查询速度,但更新和插入数据时会稍微损耗一些性能。
- **唯一索引UNIQUE**:索引列的值必须唯一,允许有空值。
- 可以认为是一种特殊的主键,它要求索引列的值必须是唯一的。
- 唯一索引既可以提高查询效率,又能确保数据的完整性,防止出现重复数据。
- 不过,与主键不同的是,唯一索引允许出现空值。
- **主键索引PRIMARY KEY**:特殊的唯一索引,不允许有空值。
- 就像是一本书的目录页,可以快速定位到某一页的内容。
- 每一行数据都有一个独一无二的标识(主键),建立主键索引可以迅速找到对应的行。
- 主键索引是最重要的索引类型,能大大提高查询效率。
- **全文索引FULLTEXT**:用于全文搜索。
- ........
## 11.3 索引管理
### 11.3.1 添加索引
1. 添加普通索引
```sql
create index idx_name on table_name(column_name);
# 查看索引
show index from table_name;
# 案例
mysql> create index sname_idx on stu(sname);
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> show index from stu;
+-------+------------+-----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-------+------------+-----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| stu | 0 | PRIMARY | 1 | sid | A | 2 | NULL | NULL | | BTREE | | |
| stu | 1 | sname_idx | 1 | sname | A | 2 | NULL | NULL | | BTREE | | |
+-------+------------+-----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
2 rows in set (0.00 sec)
```
2. 添加主键索引
如果我们将某个列设置为主键的话,那么会默认顺带设置主键索引。
手动添加主键索引
```sql
alter table table_name add primary key (column_name);
```
3. 添加唯一索引
```sql
create unique index idx_name on table_name(column_name);
# 案例
mysql> create unique index qq_idx on stu(qq);
Query OK, 0 rows affected (0.00 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> show index from stu;
+-------+------------+-----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-------+------------+-----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| stu | 0 | PRIMARY | 1 | sid | A | 2 | NULL | NULL | | BTREE | | |
| stu | 0 | qq_idx | 1 | qq | A | 2 | NULL | NULL | YES | BTREE | | |
| stu | 1 | sname_idx | 1 | sname | A | 2 | NULL | NULL | | BTREE | | |
+-------+------------+-----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
3 rows in set (0.00 sec)
```
#### 11.3.1.1 删除索引
删除普通索引或者唯一索引
```sql
DROP INDEX idx_name ON table_name;
```
删除主键索引
```sql
ALTER TABLE table_name DROP PRIMARY KEY;
```
## 11.4 前缀索引和联合索引
### 11.4.1 前缀索引
前缀索引是对列的前缀部分创建索引,适用于较长的字符串列。
- 根据字段的前N个字符建立索引
```sql
alter table test add index idx_name(name(10));
#比如表里很多城市以D开头Dalas Des'Moine Denver Detroit以字母D建立的索引会加快检索速度
```
### 11.4.2 联合索引
联合索引是对多个列组合创建的索引。
- 多个字段建立一个索引
- 原则:把最常用来做为条件查询的列放在最前面
```sql
create table people (id int,name varchar(20),age tinyint,money int ,gender enum('m','f'));
#创建people表
alter table people add index idx_gam(gender,age,money);
#创建联合索引
```
## 11.5 explain详解
`EXPLAIN` 是 SQL 中一个非常有用的关键字,它可以帮助我们分析和优化 SQL 查询的执行计划。使用 `EXPLAIN` 语句可以获取 SQL 查询的执行计划,了解查询是如何执行的,从而帮助我们识别并优化查询性能瓶颈。
下面我们来详细讲解 `EXPLAIN` 语句的用法和输出含义:
```sql
mysql> explain select * from stu where sname='linux02';
+----+-------------+-------+------+---------------+-----------+---------+-------+------+-----------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+-----------+---------+-------+------+-----------------------+
| 1 | SIMPLE | stu | ref | sname_idx | sname_idx | 62 | const | 1 | Using index condition |
+----+-------------+-------+------+---------------+-----------+---------+-------+------+-----------------------+
1 row in set (0.01 sec)
```
1. **使用方法**:
- 在 SQL 语句前加上 `EXPLAIN` 关键字即可,例如: `EXPLAIN SELECT * FROM users WHERE id = 1;`
```sql
mysql> explain select name,countrycode from city where id=1;
```
2. **输出信息**:
- `id`: 查询编号,表示查询执行的顺序。如果有子查询,每个子查询都会分配一个单独的 id。
- `select_type`: 查询类型,包括 SIMPLE、PRIMARY、SUBQUERY 等。
- `table`: 正在访问的数据表名称。
- `type`: 访问类型,包括 system、const、eq_ref、ref、range、index、ALL 等,访问类型由快到慢依次排列。
- `possible_keys`: 可能使用的索引。
- `key`: 实际使用的索引名称。
- `key_len`: 使用索引的长度。
- `ref`: 列上的比较操作。
- `rows`: 估计要读取的行数。
- `filtered`: 条件过滤后剩余的行占原始行的比例。
- `Extra`: 一些额外的信息,如 Using index、Using where 等。
3. **应用场景**:
- 查看查询计划,了解 MySQL 是如何执行 SQL 查询的。
- 分析查询瓶颈,如果 `type` 是 `ALL` 表示全表扫描,可能需要优化索引。
- 检查是否使用了正确的索引,`possible_keys` 和 `key` 列可以告诉你 MySQL 选择了哪个索引。
- 估算查询的行数,`rows` 列给出了 MySQL 估计的行数,可以帮助判断查询的效率。
### 11.5.1 MySQL查询数据的方式
**全表扫描**在explain语句结果中type为ALL例如select * from XXX
**出现场景:**
- 业务确实要获取所有数据
- 不走索引导致的全表扫描
- 没索引
- 索引创建有问题
- 语句有问题
- 生产中,mysql在使用全表扫描时的性能是极其差的所以MySQL尽量避免出现全表扫描
**索引扫描**
#### 11.5.1.1 常见的索引扫描类型
- index全索引扫描扫描整个索引树
- range范围扫描使用索引进行范围查询
- ref使用非唯一索引进行等值查询
- eq_ref使用唯一索引进行等值查询通常出现在多表连接中
- const使用主键或唯一索引进行等值查询最多返回一行
- system表中只有一行数据是 const 的特例)
- nullMySQL 不用访问表或索引就能得到结果)
- 从上到下性能从最差到最好我们认为至少要达到range级别
### 11.5.2 index
- Full Index Scanindex与ALL区别为index类型只遍历索引树。
### 11.5.3 range
- 索引范围扫描对索引的扫描开始于某一点返回匹配值域的行。显而易见的索引范围扫描是带有between或者where子句里带有<,>查询。
```sql
mysql> alter table city add index idx_city(population);
mysql> explain select * from city where population>30000000;
```
### 11.5.4 ref
- 用非唯一索引扫描或者唯一索引的前缀扫描,返回匹配某个单独值的记录行。
```sql
mysql> alter table city drop key idx_code;
mysql> explain select * from city where countrycode='chn';
mysql> explain select * from city where countrycode in ('CHN','USA');
mysql> explain select * from city where countrycode='CHN' union all select * from city where countrycode='USA';
```
### 11.5.5 eq_ref
- 类似ref区别就在使用的索引是唯一索引对于每个索引键值表中只有一条记录匹配简单来说就是多表连接中使用primary key或者 unique key作为关联条件A
```sql
join B
on A.sid=B.sid
```
### 11.5.6 const、system
- 当MySQL对查询某部分进行优化并转换为一个常量时使用这些类型访问。
- 如将主键置于where列表中MySQL就能将该查询转换为一个常量
```sql
mysql> explain select * from city where id=1000;
```
### 11.5.7 NULL
- MySQL在优化过程中分解语句执行时甚至不用访问表或索引例如从一个索引列里选取最小值可以通过单独索引查找完成。
```sql
mysql> explain select * from city where id=1000000000000000000000000000;
```
### 11.5.8 Extra扩展
在 MySQL 中,当你执行一条 SQL 语句时,结果集通常包含一个名为 "Extra" 的列,它提供了一些额外的信息,可以帮助我们更好地理解 SQL 语句的执行过程。下面是一些常见的 "Extra" 信息及其含义:
1. **No index used**
- 表示在执行 SQL 语句时,数据库没有使用任何索引。这可能会降低查询性能。
2. **Using index**
- 表示在执行 SQL 语句时,数据库使用了索引来优化查询。这通常意味着查询性能较好。
3. **Using index condition**
- 表示在执行 SQL 语句时,数据库使用了索引条件下推(Index Condition Pushdown)技术来优化查询。这可以减少不必要的行访问。
4. **Using index for group-by**
- 表示在执行 GROUP BY 语句时,数据库使用了索引来优化分组操作。
5. **Using temporary**
- 表示在执行 SQL 语句时,数据库创建了一个临时表来存储中间结果。这可能会降低查询性能。
6. **Using filesort**
- 表示在执行 SQL 语句时,数据库需要对结果集进行额外的排序操作。这可能会降低查询性能。
7. **Using join buffer (Block Nested Loop)**
- 表示在执行 JOIN 查询时,数据库使用了连接缓冲区来优化连接操作。
8. **Impossible WHERE noticed after reading const tables**
- 表示在执行 SQL 语句时,数据库发现 WHERE 条件永远为 false,因此直接返回空结果集。
9. **No tables used**
- 表示在执行 SQL 语句时,数据库没有访问任何表,例如执行 `SELECT 1;`。
10. **Select tables optimized away**
- 表示在执行 SQL 语句时,数据库优化掉了部分表的访问,例如在某些情况下,数据库可以直接从索引中获取所需的数据,而无需访问表本身。
这些 "Extra" 信息可以帮助我们了解 SQL 语句的执行过程,识别潜在的性能问题,并针对性地优化查询。在优化 SQL 语句时,我们应该关注那些可能降低性能的信息,例如 "Using temporary"、"Using filesort" 等
- 如果出现Using filesort请检查order by ,group by ,distinct,join 条件列上没有索引
```sql
mysql> explain select * from city where countrycode='CHN' order by population;
```
- 当order by语句中出现Using filesort那就尽量让排序值在where条件中出现
```sql
mysql> explain select * from city where population>30000000 order by population;
mysql> select * from city where population=2870300 order by population;
* key_len: 越小越好
* 前缀索引去控制rows: 越小越好
```
## 11.6 建立索引的原则(规范)
- 为了使索引的使用效率更高,在创建索引时,必须考虑在哪些字段上创建索引和创建什么类型的索引。
- 选择唯一性索引
- 唯一性索引的值是唯一的,可以更快速的通过该索引来确定某条记录。
> 例如:
> 学生表中学号是具有唯一性的字段。为该字段建立唯一性索引可以很快的确定某个学生的信息。
> 如果使用姓名的话,可能存在同名现象,从而降低查询速度。
> 主键索引和唯一键索引,在查询中使用是效率最高的。
```sql
select count(*) from world.city;
select count(distinct countrycode) from world.city;
select count(distinct countrycode,population ) from world.city;
* 注意:如果重复值较多,可以考虑采用联合索引
```
- 为经常需要排序、分组和联合操作的字段建立索引
- 经常需要ORDER BY、GROUP BY、DISTINCT和UNION等操作的字段排序操作会浪费很多时间。
- 如果为其建立索引,可以有效地避免排序操作
- 为常作为查询条件的字段建立索引
- 如果某个字段经常用来做查询条件,那么该字段的查询速度会影响整个表的查询速度。
- 因此,为这样的字段建立索引,可以提高整个表的查询速度。
- 如果经常作为条件的列,重复值特别多,可以建立联合索引
- 尽量使用前缀来索引
- 如果索引字段的值很长最好使用值的前缀来索引。例如TEXT和BLOG类型的字段进行全文检索会很浪费时间。如果只检索字段的前面的若干个字符这样可以提高检索速度。
- 限制索引的数目
- 索引的数目不是越多越好。每个索引都需要占用磁盘空间,索引越多,需要的磁盘空间就越大。
- 修改表时,对索引的重构和更新很麻烦。越多的索引,会使更新表变得很浪费时间。
- 删除不再使用或者很少使用的索引
- 表中的数据被大量更新,或者数据的使用方式被改变后,原有的一些索引可能不再需要。数据库管理员应当定期找出这些索引,将它们删除,从而减少索引对更新操作的影响。
重点关注:
- 没有查询条件,或者查询条件没有建立索引
```sql
select * from table;
select * from tab where 1=1;
# 全表扫描
```
- 在业务数据库中,特别是数据量比较大的表,是没有全表扫描这种需求。
- 对用户查看是非常痛苦的。
- 对服务器来讲毁灭性的。
- SQL改写成以下语句
```sql
# 情况1
select * from table;
#全表扫描
selec * from tab order by price limit 10;
#需要在price列上建立索引
# 情况2
select * from table where name='zhangsan';
#name列没有索引
1、换成有索引的列作为查询条件
2、将name列建立索引
```
- 查询结果集是原表中的大部分数据应该是25以上
```sql
mysql> explain select * from city where population>3000 order by population;
* 如果业务允许可以使用limit控制
* 结合业务判断有没有更好的方式。如果没有更好的改写方案就尽量不要在mysql存放这个数据了放到redis里面。
```
- 索引本身失效,统计数据不真实
- 索引有自我维护的能力。
- 对于表内容变化比较频繁的情况下,有可能会出现索引失效。
- 重建索引就可以解决
- 查询条件使用函数在索引列上或者对索引列进行运算,运算包括(+-*等)
```sql
错误的例子select * from test where id-1=9;
正确的例子select * from test where id=10;
```
- 隐式转换导致索引失效.这一点应当引起重视,也是开发中经常会犯的错误
```sql
mysql> create table test (id int ,name varchar(20),telnum varchar(10));
# 例如列类型为整形,非要在检索条件中用字符串
mysql> insert into test values(1,'zs','110'),(2,'l4',120),(3,'w5',119),(4,'z4',112);
mysql> explain select * from test where telnum=120;
mysql> alter table test add index idx_tel(telnum);
mysql> explain select * from test where telnum=120;
mysql> explain select * from test where telnum=120;
mysql> explain select * from test where telnum='120';
```
```sql
EXPLAIN SELECT * FROM teltab WHERE telnum IN ('110','119');
#改写成
EXPLAIN SELECT * FROM teltab WHERE telnum='110'
UNION ALL
SELECT * FROM teltab WHERE telnum='119'
```
# 12. MySQL的存储引擎
## 12.1 存储引擎简介
Mysql的数据用不同的技术存储在文件(或内存)中,不同的技术拥有不同的存储机制、索引技巧、锁定水平,通过选择不同的技术获得不同的功能,从而改善应用的整体功能,而这些不同的技术和功能就是存储引擎(也称作表引擎)
Mysql支持的存储引擎InnoDB、MyISAM、MEMORY、CSV、BLACKHOLE、FEDERATED、MRG_MYISAM、 ARCHIVE、PERFORMANCE_SCHEMA。其中NDB和InnoDB提供事务安全表其他存储引擎都是非事务安全表
![img](01.Mysql/KkaagjFIBB8C0CCF-1739583976575108.png!thumbnail)
- 文件系统:
- 操作系统组织和存取数据的一种机制。
- 文件系统是一种软件。
- 文件系统类型ext2 3 4 xfs 数据
- 不管使用什么文件系统,数据内容不会变化
- 不同的是,存储空间、大小、速度。
- MySQL引擎
- 可以理解为MySQL的“文件系统”只不过功能更加强大。
- MySQL引擎功能
- 除了可以提供基本的存取功能,还有更多功能事务功能、锁定、备份和恢复、优化以及特殊功能
- 总之,存储引擎的各项特性就是为了保障数据库的安全和性能设计结构。
## 12.2 MySQL自带的存储引擎类型
- MySQL 提供以下存储引擎:
- InnoDB
- MyISAM
- MEMORY
- ARCHIVE
- FEDERATED
- EXAMPLE
- BLACKHOLE
- MERGE
- NDBCLUSTER
- CSV
- 还可以使用第三方存储引擎:
- MySQL当中插件式的存储引擎类型
- MySQL的两个分支
- perconaDB
- mariaDB
## 12.3 不同存储引擎介绍
### 12.3.1 InnoDB
MySql 5.6 版本默认的存储引擎。InnoDB 是一个事务安全的存储引擎,它具备提交、回滚以及崩溃恢复的功能以 保护用户数据。InnoDB 的行级别锁定以及 Oracle 风格的一致性无锁读提升了它的多用户并发数以及性能。
InnoDB 将用户数据存储在聚集索引中以减少基于主键的普通查询所带来的 I/O 开销。为了保证数据的完整性,
InnoDB 还支持外键约束。
应用场景:并发高、数据一致、更新和删除操作较多、事务的完整提交和回滚
**innodb体系结构**
innodb主要包括 内存池、后台线程以及存储文件
内存池是由多个内存块组成的主要包括缓存磁盘数据redolog 缓冲等
后台线程则包括了: Master Thread、IO Thread、Purge Thread等
由 InnoDB 存储引擎实现的表的存储结构文件一般包括表结构文件(.frm、共享表空间文件ibdata1、独占表空间文件ibd以及日志文件redo 文件等)等。
### 12.3.2 MyISAM(读、插入操作)
MyISAM既不支持事务、也不支持外键、其优势是访问速度快但是表级别的锁定限制了它在读写负载方面的性能 因此它经常应用于只读或者以读为主的数据场景。
应用场景:读操作和插入操作为主
### 12.3.3 Memory内存存储数据快速定位记录
在内存中存储所有数据应用于对非关键数据由快速查找的场景。Memory类型的表访问数据非常快因为它的数据 是存放在内存中的并且默认使用HASH索引但是一旦服务关闭表中的数据就会丢失
应用场景:快速定位记录
- MEMORY是MySQL中一类特殊的存储引擎。它使用存储在内存中的内容来创建表而且数据全部放在内存中。这些特性与前面的两个很不同。
- 每个基于MEMORY存储引擎的表实际对应一个磁盘文件。该文件的文件名与表名相同类型为frm类型。该文件中只存储表的结构。而其数据文件都是存储在内存中这样有利于数据的快速处理提高整个表的效率。值得注意的是服务器需要有足够的内存来维持MEMORY存储引擎的表的使用。如果不需要了可以释放内存甚至删除不需要的表。
- MEMORY默认使用哈希索引。速度比使用B型树索引快。当然如果你想用B型树索引可以在创建索引时指定。
- 注意MEMORY用到的很少因为它是把数据存到内存中如果内存出现异常就会影响数据。如果重启或者关机所有数据都会消失。因此基于MEMORY的表的生命周期很短一般是一次性的。
### 12.3.4 BLACKHOLE只接收不保存数据
黑洞存储引擎,类似于 Unix 的 /dev/nullArchive 只接收但却并不保存数据。对这种引擎的表的查询常常返 回一个空集。这种表可以应用于 DML 语句需要发送到从服务器,但主服务器并不会保留这种数据的备份的主从配置 中。
### 12.3.5 CSV
它的表真的是以逗号分隔的文本文件。CSV 表允许你以 CSV 格式导入导出数据,以相同的读和写的格式和脚本和应用交互数据。由于 CSV 表没有索引,你最好是在普通操作中将数据放在 InnoDB 表里,只有在导入或导出阶段 使用一下 CSV 表。
### 12.3.6 NDB
(又名 NDBCLUSTER)——这种集群数据引擎尤其适合于需要最高程度的正常运行时间和可用性的应用。注意NDB 存 储引擎在标准 MySql 5.6 版本里并不被支持。
目前能够支持 MySql 集群的版本有:基于 MySql 5.1 的 MySQL Cluster NDB 7.1;基于 MySql 5.5 的 MySQL Cluster NDB 7.2;基于 MySql 5.6 的 MySQL Cluster NDB 7.3。同样基于 MySql 5.6 的 MySQL Cluster NDB 7.4
目前正处于研发阶段。
### 12.3.7 Merge超大规模数据场景
允许 MySql DBA 或开发者将一系列相同的 MyISAM 表进行分组,并把它们作为一个对象进行引用。适用于超大规 模数据场景,如数据仓库。
### 12.3.8 Federated分布式
提供了从多个物理机上联接不同的 MySql 服务器来创建一个逻辑数据库的能力。适用于分布式或者数据市场的场景。
### 12.3.9 Example保存例子
这种存储引擎用以保存阐明如何开始写新的存储引擎的 MySql 源码的例子。它主要针对于有兴趣的开发人员。这 种存储引擎就是一个啥事也不做的 "存根"。你可以使用这种引擎创建表,但是你无法向其保存任何数据,也无法从 它们检索任何索引。
### 12.3.10 **经典面试题InnoDB和MyISAM区别**
两种存储引擎的大致区别表现在:
- InnoDB支持事务MyISAM不支持这一点是非常之重要。事务是一种高级的处理方式如在一些列增删改中只要哪个出错还可以回滚还原而MyISAM就不可以了。
- MyISAM适合查询以及插入为主的应用。
- InnoDB适合频繁修改以及涉及到安全性较高的应用。
- InnoDB支持外键MyISAM不支持。
- 从MySQL5.5.5以后InnoDB是默认引擎。
- InnoDB不支持FULLTEXT类型的索引。
- InnoDB中不保存表的行数如select count(*) from table时InnoDB需要扫描一遍整个表来计算有多少行但是 MyISAM只要简单的读出保存好的行数即可。注意的是当count()语句包含where条件时MyISAM也需要扫描整个表。
- 对于自增长的字段InnoDB中必须包含只有该字段的索引但是在MyISAM表中可以和其他字段一起建立联合索引。
- DELETE FROM table时InnoDB不会重新建立表而是一行一行的 删除效率非常慢。MyISAM则会重建表。
- InnoDB支持行锁某些情况下还是锁整表如 update table set a=1 where user like '%lee%'。
## 12.4 查看当前MySQL的存储引擎
```sql
mysql> show engines;
#查看当前MySQL支持的存储引擎类型
mysql> select table_schema,table_name,engine from information_schema.tables where engine='innodb';
#查看innodb的表有哪些
mysql> select table_schema,table_name,engine from information_schema.tables where engine='myisam';
#查看myisam的表有哪些
```
- innodb和myisam的区别
```shell
#进入mysql目录
[root@localhost~l]# cd /application/mysql/data/mysql
#查看所有user的文件
[root@localhost mysql]# ll user.*
-rw-rw---- 1 mysql mysql 10684 Mar 6 2017 user.frm
-rw-rw---- 1 mysql mysql 960 Aug 14 01:15 user.MYD
-rw-rw---- 1 mysql mysql 2048 Aug 14 01:15 user.MYI
#进入word目录
[root@localhost world]# cd /application/mysql/data/world/
#查看所有city的文件
[root@localhost world]# ll city.*
-rw-rw---- 1 mysql mysql 8710 Aug 14 16:23 city.frm
-rw-rw---- 1 mysql mysql 688128 Aug 14 16:23 city.ibd
```
## 12.5 innodb存储引擎的简介
- 在MySQL5.5版本之后,默认的存储引擎,提供高可靠性和高性能。
- 优点:
- 事务安全(遵从 ACID
- MVCCMulti-Versioning Concurrency Control多版本并发控制
- InnoDB 行级别锁定另一种DB是表级别的
- Oracle 样式一致非锁定读取
- 表数据进行整理来优化基于主键的查询
- 支持外键引用完整性约束
- 大型数据卷上的最大性能
- 将对表的查询与不同存储引擎混合
- 出现故障后快速自动恢复
- 用于在内存中缓存数据和索引的缓冲区池
| 功能 | 支持 | 功能 | 支持 |
| :----------- | :--- | :----------------- | :--- |
| 存储限制 | 64TB | 索引高速缓存 | 是 |
| MVCC | 是 | 数据高速缓存 | 是 |
| B树索引 | 是 | 自适应散列索引 | 是 |
| 群集索引 | 是 | 复制 | 是 |
| 压缩数据 | 是 | 更新数据字典 | 是 |
| 加密数据 | 是 | 地理空间数据类型 | 是 |
| 查询高速缓存 | 是 | 地理空间索引 | 否 |
| 事务 | 是 | 全文搜索索引 | 是 |
| 锁定粒度 | 行 | 群集数据库 | 否 |
| 外键 | 是 | 备份和恢复 | 是 |
| 文件格式管理 | 是 | 快速索引创建 | 是 |
| 多个缓冲区池 | 是 | performance_schema | 是 |
| 更改缓冲 | 是 | 自动故障恢复 | 是 |
- innodb核心特性
- MVCC
- 事务
- 行级锁
- 热备份
- Crash Safe Recovery自动故障恢复
- 查看存储引擎
- 使用 SELECT 确认会话存储引擎
```sql
SELECT @@default_storage_engine;
# 查询默认存储引擎
```
- 使用 SHOW 确认每个表的存储引擎
```sql
SHOW CREATE TABLE City\G
SHOW TABLE STATUS LIKE 'CountryLanguage'\G
# 查看表的存储引擎
```
- 存储引擎的设置
- 在启动配置文件中设置服务器存储引擎
```sql
[mysqld]
default-storage-engine=<Storage Engine>
# 在配置文件的[mysqld]标签下添加
```
- 使用 SET 命令为当前客户机会话设置
```sql
SET @@storage_engine=<Storage Engine>
# 在MySQL命令行中临时设置
```
- 在 CREATE TABLE 语句指定
```sql
CREATE TABLE talbe_name (id int) ENGINE = <Storage Engine>;
# 建表的时候指定存储引擎
```
## 12.6 【实战】存储引擎切换
- 项目背景:
- 公司原有的架构一个展示型的网站LAMPMySQL5.1.77版本MYISAM50M数据量。
- 小问题不断:
- 表级锁:对表中任意一行数据修改类操作时,整个表都会锁定,对其他行的操作都不能同时进行。
- 不支持故障自动恢复CSR当断电时有可能会出现数据损坏或丢失的问题。
- 解决方案:
- 提建议将现有的MYISAM引擎替换为Innodb将版本替换为5.6.38
- 如果使用MYISAM会产生”小问题”性能安全不能得到保证使用innodb可以解决这个问题。
- 5.1.77版本对于innodb引擎支持不够完善5.6.38版本对innodb支持非常完善了。
- 实施过程和注意要素
- 备份生产库数据mysqldump
```sql
#[root@db01 ~]# mysqldump -uroot -p123 -A --triggers -R --master-data=2 >/tmp/full.sql
#由于没有开启bin logging所以去掉 --master-data=2
[root@db01 ~]# mysqldump -uroot -p123 -A --triggers -R >/tmp/full.sql
```
- 准备一个5.6.38版本的新数据库
- 对备份数据进行处理将engine字段替换
```shell
[root@db01 ~]# sed -i 's#ENGINE=MYISAM#ENGINE=INNODB#g' /tmp/full.sql
```
- 将修改后的备份恢复到新库
- 应用测试环境连接新库,测试所有功能
- 停应用,将备份之后的生产库发生的新变化,补偿到新库
- 应用割接到新数据库
## 12.7 【实战】数据库服务损坏
- 在没有备份数据的情况下,突然断电导致表损坏,打不开数据库。
1. 拷贝库目录到新库中
```shell
[root@db01 ~]# cp -r /application/mysql/data/world/ /data/3307/data/
[root@db01 ~]# chown -R mysql.mysql /data/3307/data/
```
2. 启动新数据库
```shell
[root@db01 ~]# pkill mysqld #先干掉mysql
[root@db01 ~]# mysqld_safe --defaults-file=/data/3307/my.cnf &
```
3. 登陆数据库查看
```sql
mysql> show databases;
```
4. 查询表中数据
```sql
mysql> select * from city;
ERROR 1146 (42S02): Table 'world.city' doesn't exist
#先 use world
```
5. 找到**以前的表**结构在新库中创建表此处演示用的show命令实际需要从原来的开发文档查看
```sql
mysql> show create table world.city;
#删掉外键创建语句
CREATE TABLE `city` (
`ID` int(11) NOT NULL AUTO_INCREMENT,
`Name` char(35) NOT NULL DEFAULT '',
`CountryCode` char(3) NOT NULL DEFAULT '',
`District` char(20) NOT NULL DEFAULT '',
`Population` int(11) NOT NULL DEFAULT '0',
PRIMARY KEY (`ID`),
KEY `CountryCode` (`CountryCode`),
KEY `idx_city` (`Population`,`CountryCode`),
CONSTRAINT `city_ibfk_1` FOREIGN KEY (`CountryCode`) REFERENCES `country` (`Code`)
) ENGINE=InnoDB AUTO_INCREMENT=4080 DEFAULT CHARSET=latin1;
mysql> CREATE TABLE `city_new` (
`ID` int(11) NOT NULL AUTO_INCREMENT,
`Name` char(35) NOT NULL DEFAULT '',
`CountryCode` char(3) NOT NULL DEFAULT '',
`District` char(20) NOT NULL DEFAULT '',
`Population` int(11) NOT NULL DEFAULT '0',
PRIMARY KEY (`ID`),
KEY `CountryCode` (`CountryCode`)
# KEY `idx_city` (`Population`,`CountryCode`),
#干掉外键的约束,否则失败
#CONSTRAINT `city_ibfk_1` FOREIGN KEY (`CountryCode`) REFERENCES `country` (`Code`)
) ENGINE=InnoDB AUTO_INCREMENT=4080 DEFAULT CHARSET=latin1;
```
6. 删除表空间文件
```sql
mysql> alter table city_new discard tablespace;
```
7. 拷贝旧表空间文件
```sql
[root@db01 world]# cp /data/3307/data/world/city.ibd /data/3307/data/world/city_new.ibd
```
8. 授权
```sql
[root@db01 world]# cd /data/3307/data/world
[root@db01 world]# chown -R mysql.mysql *
```
9. 导入表空间
```sql
mysql> alter table city_new import tablespace;
mysql> alter table city_new rename city;
```
10. 恢复索引和外键
```sql
mysql> alter table city add index idx_city(Population,CountryCode);
mysql> alter table city add constraint `city_ibfk_1` foreign key (`CountryCode`) references `country` (`Code`);
```
- 恢复剩下的其他表,最后将外键加回来
# 13. MySQL事务
## 13.1 事务
- 事务的定义
- 主要针对DML语句updatedeleteinsert一组数据操作执行步骤这些步骤被视为一个工作单元
- 用于对多个语句进行分组
- 可以在多个客户机并发访问同一个表中的数据时使用
- 所有步骤都成功或都失败
- 如果所有步骤正常,则执行
- 如果步骤出现错误或不完整,则取消
- 交易的概念
- 物与物的交换(古代)
- 货币现金与实物的交换现代1
- 虚拟货币与实物的交换现代2
- 虚拟货币与虚拟实物交换现代3
- 事务ACID特性
- Atomic原子性
- 所有语句作为一个单元全部成功执行或全部取消。
- Consistent一致性
- 如果数据库在事务开始时处于一致状态,则在执行该事务期间将保留一致状态。
- Isolated隔离性
- 事务之间不相互影响。
- Durable持久性
- 事务成功完成后,所做的所有更改都会准确地记录在数据库中。所做的更改不会丢失。
![img](01.Mysql/uxH9iBpInyy8pwrk-1739583976575109.png!thumbnail)
- 事务的控制语句
```sql
START TRANSACTION或 BEGIN显式开始一个新事务
SAVEPOINT分配事务过程中的一个位置以供将来引用
COMMIT永久记录当前事务所做的更改
ROLLBACK取消当前事务所做的更改
ROLLBACK TO SAVEPOINT取消在 savepoint 之后执行的更改
RELEASE SAVEPOINT删除 savepoint 标识符
SET AUTOCOMMIT为当前连接禁用或启用默认 autocommit 模式
```
- 一个成功事务的生命周期
```sql
begin;
sql1
sql2
sql3
...
commit;
```
- 一个失败事务的生命周期
```sql
begin;
sql1
sql2
sql3
...
rollback;
```
- 自动提交
```sql
#默认自动提交关闭后需要命令commit才生效此时另一个客户端除非重新登录才能看到变化隔离性
#开启两个窗口不同update不同时间commit演示
mysql> show variables like 'autocommit';
#查看自动提交
mysql> set autocommit=0;
#临时关闭
[root@db01 world]# vim /etc/my.cnf
[mysqld]
autocommit=0
#永久关闭
```
- 事务演示
- 成功事务
需要打开另一个mysql终端查看分别在执行完语句以及commit后查看
```sql
mysql> create table stu(id int,name varchar(10),sex enum('f','m'),money int);
mysql> begin;
mysql> insert into stu(id,name,sex,money) values(1,'zhang3','m',100), (2,'zhang4','m',110);
mysql> commit;
* 事务回滚
mysql> begin;
mysql> update stu set name='zhang3';
mysql> delete from stu;
mysql> rollback;
```
- 事务隐式提交情况
- 现在版本在开启事务时不需要手工begin只要你输入的是DML语句就会自动开启事务。
- 有些情况下事务会被隐式提交
- 在事务运行期间手工执行begin的时候会自动提交上个事务
- 在事务运行期间加入DDL、DCL操作会自动提交上个事务
- 在事务运行期间执行锁定语句lock tables、unlock tables
- load data infile
- select for update
- 在autocommit=1的时候
### 13.1.1 事务日志redo
- redo,顾名思义“重做日志”,是事务日志的一种。
- 在事务ACID过程中实现的是“D”持久化的作用。
- 特性:WAL(Write Ahead Log)日志优先写
- REDO记录的是内存数据页的变化过程
- REDO工作过程
```sql
update t1 set num=2 where num=1;
```
- 执行步骤
* 首先将t1表中num=1的行所在数据页加载到内存中buffer page
* MySQL实例在内存中将num=1的数据页改成num=2
* num=1变成num=2的变化过程会记录到redo内存区域也就是redo buffer page中
commit;
- 提交事务执行步骤
* 当敲下commit命令的瞬间MySQL会将redo buffer page写入磁盘区域redo log
* 当写入成功之后commit返回ok
### 13.1.2 事务日志undo
- undo,顾名思义“回滚日志”,是事务日志的一种。
- 在事务ACID过程中实现的是“A”原子性的作用。当然C的特性也和undo有关
- redo和undo的存储位置
```shell
[root@db01 data]# ll /application/mysql/data/
-rw-rw---- 1 mysql mysql 50331648 Aug 15 06:34 ib_logfile0
-rw-rw---- 1 mysql mysql 50331648 Mar 6 2021 ib_logfile1
# redo位置
[root@db01 data]# ll /application/mysql/data/
-rw-rw---- 1 mysql mysql 79691776 Aug 15 06:34 ibdata1
-rw-rw---- 1 mysql mysql 79691776 Aug 15 06:34 ibdata2
# undo位置
```
- 在MySQL5.6版本中undo是在ibdata文件中在MySQL5.7版本会独立出来。
### 13.1.3 事务中的锁
- “锁”顾名思义就是锁定的意思
- 在事务ACID特性过程中“锁”和“隔离级别”一起来实现“I”隔离性的作用。
- 排他锁:保证在多事务操作时,数据的一致性。
- 共享锁:保证在多事务工作期间,数据查询时不会被阻塞。
- 多版本并发控制MVCC
- 只阻塞修改类操作,不阻塞查询类操作
- 乐观锁的机制(谁先提交谁为准)
- 锁的粒度
- MyIsam低并发锁表级锁
- Innodb高并发锁行级锁
- 事务的隔离级别
- 四种隔离级别
- READ UNCOMMITTED独立提交
- 允许事务查看其他事务所进行的未提交更改一个用户update命令后未commit另一个用户看到的是修改的内存里的即使没有写入硬盘
- READ COMMITTED
- 允许事务查看其他事务所进行的已提交更改
- REPEATABLE READ******
- 确保每个事务的 SELECT 输出一致一个用户update命令即使commit。另一个用户看到的未修改的除非重新登录或者commit+
- InnoDB 的默认级别
- SERIALIZABLE
- 将一个事务的结果与其他事务完全隔离一个用户update命令后commit另一个用户即使select都看不到。
```sql
mysql> show variables like '%iso%';
#查看隔离级别
[mysqld]
transaction_isolation=read-uncommit
#修改隔离级别为RU
mysql> use test
mysql> select * from stu;
mysql> insert into stu(id,name,sex,money) values(2,'li4','f',123);
[mysqld]
transaction_isolation=read-commit
#修改隔离级别为RC
```
| 隔离级别 | 英文缩写 | 解决的问题 | 存在的问题 | InnoDB 实现方式 | 并发性能 | 适用场景 |
| :----------: | :------: | :----------------------: | :--------------------: | :-----------------------------: | :------: | :--------------------------: |
| 读未提交 | RU | 无 | 脏读、不可重复读、幻读 | 无锁,直接读最新版本 | 最高 | 无生产场景,临时查询 |
| 读已提交 | RC | 脏读 | 不可重复读、幻读 | 快照读MVCC+ 行级锁 | 较高 | 一致性一般、并发要求高的场景 |
| **可重复读** | **RR** | 脏读、不可重复读、幻读 * | 无InnoDB 优化) | 快照读MVCC+ 行级锁 + 间隙锁 | **中等** | **绝大多数生产业务系统** |
| 串行化 | S | 所有并发问题 | 无 | 表级锁,事务串行执行 | 最低 | 一致性极高、无并发需求的场景 |
> 注 *SQL 标准中 RR 未解决幻读,**InnoDB 通过 Next-Key Lock 彻底解决幻读**。
- 脏读
- A 事务改了数据但**没提交**B 事务此时读到了 A 的**未提交修改**;后续 A 事务**回滚**B 事务读到的就是 “不存在的虚假数据”(脏数据)。
- 不可重复读
- A 事务中**多次读取同一数据**,在 A 事务未结束时B 事务**修改并提交**了该数据;导致 A 事务后续再读,结果和第一次不一样。
- 幻读
- A 事务中**多次执行同一范围查询**,在 A 事务未结束时B 事务**插入 / 删除并提交**了符合该范围的新数据;导致 A 事务后续再查,结果的**行数变多 / 变少**,像出现了 “幻觉”。
# 14. MySQL日志管理
## 14.1 日志简介
| 日志文件 | 选项 | 文件名,表名 | 程序 |
| :------- | :--------------------------------- | :------------------------ | :------------ |
| 错误 | --log-error | host_name.err | |
| 常规 | --general_log | host_name.log general_log | |
| 慢速查询 | --slow_query_log --long_query_time | host_name-slow.log | mysqldumpslow |
| 二进制 | --log-bin --expire-logs-days | host_name-bin.000001 | mysqlbinlog |
| 审计 | --audit_log --audit_log_file ... | audit.log | |
## 14.2 错误日志
- 记录mysql数据库的一般状态信息及报错信息是我们对于数据库常规报错处理的常用日志。
- 默认位置
- $MYSQL_HOME/data/
- 开启方式
- MySQL安装完后默认开启
```shell
[root@db01 ~]# vim /etc/my.cnf
# 编辑配置文件
[mysqld]
log_error=/application/mysql/data/$hostname.err
mysql> show variables like 'log_error';
# 查看方式
```
## 14.3 一般查询日志
- 记录mysql所有执行成功的SQL语句信息可以做审计用但是我们很少开启。
- 默认位置
- $MYSQL_HOME/data/
- 开启方式
- MySQL安装完之后默认不开启
```shell
[root@db01 ~]# vim /etc/my.cnf
[mysqld]
general_log=on
general_log_file=/application/mysql/data/$hostnamel.log
# 编辑配置文件
mysql> show variables like '%gen%';
# 查看方式
```
## 14.4 二进制日志
- 记录已提交的DML事务语句并拆分为多个事件event来进行记录
- 记录所有DDL、DCL等语句
- 总之,二进制日志会记录所有对数据库发生修改的操作
- 二进制日志模式
- statement语句模式
- row行模式即数据行的变化过程
- mixed以上两者的混合模式。
- 企业推荐使用row模式
- 二进制日志模式优缺点
- statement模式
- 优点简单明了容易被看懂就是sql语句记录时不需要太多的磁盘空间
- 缺点:记录不够严谨
- row模式
记录了底层操作的所有事情
- 优点:记录更加严谨
- 缺点:有可能会需要更多的磁盘空间,不太容易被读懂
- binlog的作用
- 如果我拥有数据库搭建开始所有的二进制日志,那么我可以把数据恢复到任意时刻
- 数据的备份恢复
- 数据的复制
### 14.4.1 二进制日志的管理操作实战
#### 14.4.1.1 开启bin-log日志
注意:在mysql5.6及以后的版本中开启binlog必须要加上server-id。
```shell
[root@localhost ~]# vim /etc/my.cnf
[mysqld]
log-bin=mysql-bin
binlog_format=row
server_id=1
```
检查主目录下的binlog日志文件
```shell
# 物理查看
[root@localhost ~]# ll /application/mysql/data/mysql-bin*
-rw-rw---- 1 mysql mysql 120 Feb 15 11:23 /application/mysql/data/mysql-bin.000001
-rw-rw---- 1 mysql mysql 19 Feb 15 11:23 /application/mysql/data/mysql-bin.index
# MySQL命令行查看
mysql> show binary logs;
+------------------+-----------+
| Log_name | File_size |
+------------------+-----------+
| mysql-bin.000001 | 120 |
+------------------+-----------+
1 row in set (0.00 sec)
mysql> show master status;
+------------------+----------+--------------+------------------+-------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+-------------------+
| mysql-bin.000001 | 120 | | | |
+------------------+----------+--------------+------------------+-------------------+
1 row in set (0.00 sec)
# 查看binlog事件
mysql> show binlog events in 'mysql-bin.000001';
+------------------+-----+-------------+-----------+-------------+---------------------------------------+
| Log_name | Pos | Event_type | Server_id | End_log_pos | Info |
+------------------+-----+-------------+-----------+-------------+---------------------------------------+
| mysql-bin.000001 | 4 | Format_desc | 1 | 120 | Server ver: 5.6.40-log, Binlog ver: 4 |
+------------------+-----+-------------+-----------+-------------+---------------------------------------+
1 row in set (0.00 sec)
```
- 事件介绍
- 在binlog中最小的记录单元为event
- 一个事务会被拆分成多个事件event
- 事件event特性
- 每个event都有一个开始位置start position和结束位置stop position
- 所谓的位置就是event对整个二进制的文件的相对位置。
- 对于一个二进制日志中前120个position是文件格式信息预留空间。
- MySQL第一个记录的事件都是从120开始的。
#### 14.4.1.2 row模式下binlog分析及数据恢复
一、查看初始binlog信息
```sql
mysql> show master status;
+------------------+----------+--------------+------------------+-------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+-------------------+
| mysql-bin.000001 | 120 | | | |
+------------------+----------+--------------+------------------+-------------------+
1 row in set (0.00 sec)
```
二、模拟数据变化
```sql
# 创建数据库和表
mysql> create database binlog;
mysql> use binlog;
mysql> create table binlog_table(id int);
# 查看现在的binlog信息
mysql> show master status;
+------------------+----------+--------------+------------------+-------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+-------------------+
| mysql-bin.000001 | 331 | | | |
+------------------+----------+--------------+------------------+-------------------+
1 row in set (0.00 sec)
# 插入数据(如果是非自动提交记得手动commit)
mysql> insert into binlog_table values(1);
mysql> show master status;
+------------------+----------+--------------+------------------+-------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+-------------------+
| mysql-bin.000001 | 533 | | | |
+------------------+----------+--------------+------------------+-------------------+
1 row in set (0.00 sec)
# 再次插入数据
mysql> insert into binlog_table values(2);
mysql> insert into binlog_table values(3);
mysql> show master status;
+------------------+----------+--------------+------------------+-------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+-------------------+
| mysql-bin.000001 | 937 | | | |
+------------------+----------+--------------+------------------+-------------------+
1 row in set (0.00 sec)
# 删除数据操作
mysql> delete from binlog_table where id=1;
mysql> show master status;
+------------------+----------+--------------+------------------+-------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+-------------------+
| mysql-bin.000001 | 1139 | | | |
+------------------+----------+--------------+------------------+-------------------+
1 row in set (0.00 sec)
# 更改数据操作
mysql> update binlog_table set id=22 where id=2;
mysql> show master status;
+------------------+----------+--------------+------------------+-------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+-------------------+
| mysql-bin.000001 | 1347 | | | |
+------------------+----------+--------------+------------------+-------------------+
1 row in set (0.00 sec)
# 查看数据操作
mysql> select * from binlog_table;
mysql> show master status;
+------------------+----------+--------------+------------------+-------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+-------------------+
| mysql-bin.000001 | 1347 | | | |
+------------------+----------+--------------+------------------+-------------------+
1 row in set (0.00 sec)
# 删表删库跑路
mysql> drop table binlog_table;
mysql> drop database binlog;
mysql> show master status;
+------------------+----------+--------------+------------------+-------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+-------------------+
| mysql-bin.000001 | 1565 | | | |
+------------------+----------+--------------+------------------+-------------------+
1 row in set (0.00 sec)
```
三、数据恢复
```sql
# 查看当前binlog事件
mysql> show binlog events in 'mysql-bin.000001';
# 还可以使用mysqlbinlog工具进行查看
[root@localhost ~]# mysqlbinlog --base64-output=decode-rows /application/mysql/data/mysql-bin.000001
[root@localhost ~]# mysqlbinlog --base64-output=decode-rows /application/mysql/data/mysql-bin.000001|grep -v SET
# 发现开始删库删表的开始位置是1347开始截取删库之前的binlog日志
[root@localhost ~]# mysqlbinlog --start-position=120 --stop-position=1347 /application/mysql/data/mysql-bin.000001 > /tmp/binlog.sql
# 恢复数据恢复之前临时关闭binlog否则会继续记录恢复的过程导致binlog变成脏日志
mysql> set sql_log_bin=0;
mysql> source /tmp/binlog.sql
# 检查数据恢复情况
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| binlog |
| mysql |
| performance_schema |
| test |
+--------------------+
5 rows in set (0.00 sec)
mysql> use binlog;
mysql> show tables;
+------------------+
| Tables_in_binlog |
+------------------+
| binlog_table |
+------------------+
1 row in set (0.00 sec)
mysql> select * from binlog_table;
+------+
| id |
+------+
| 22 |
| 3 |
+------+
2 rows in set (0.00 sec)
mysql> set sql_log_bin=1;
```
#### 14.4.1.3 查看某个数据库的binlog
一、刷新binlog日志
```sql
# 刷新一个新的binlog从新日志开始记录但是原来的日志也不会删除
mysql> flush logs;
mysql> show master status;
+------------------+----------+--------------+------------------+-------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+-------------------+
| mysql-bin.000002 | 120 | | | |
+------------------+----------+--------------+------------------+-------------------+
1 row in set (0.00 sec)
```
二、创建不同的库测试
```sql
# 创建db1、db2两个库
mysql> create database db1;
mysql> create database db2;
# 库db1操作
mysql> use db1
mysql> create table t1(id int);
mysql> insert into t1 values(1),(2),(3),(4),(5);
# 库db2操作
mysql> use db2
mysql> create table t2(id int);
mysql> insert into t2 values(1),(2),(3);
```
三、检查binlog日志
```sql
mysql> show binlog events in 'mysql-bin.000002';
# 只查看db1数据库的binlog
[root@localhost ~]# mysqlbinlog -d db1 --base64-output=decode-rows -vvv /application/mysql/data/mysql-bin.000002
```
### 14.4.2 其他操作
- 刷新binlog日志
- flush logs;
- 重启数据库时会刷新
- 二进制日志上限max_binlog_size
- 删除二进制日志
- 原则
- 在存储能力范围内,能多保留则多保留
- 基于上一次全备前的可以选择删除
- 删除方式
- 根据存在时间删除日志
```sql
# 临时生效
SET GLOBAL expire_logs_days = 7;
# 永久生效
[root@localhost data]# vim /etc/my.cnf
[mysqld]
expire_logs_days = 7
# 使用purge命令删除
mysql> PURGE BINARY LOGS BEFORE now() - INTERVAL 3 day;
# 根据文件名删除
mysql> PURGE BINARY LOGS TO 'mysql-bin.000002';
# 用reset master 重置binlog
mysql> reset master;
```
- 多binary logs文件跳过pos1500其他数据全部恢复示例
```bash
mysql> show binlog events; # 定位误操作的语句在哪一个binlog上
# 多Binlog拼接恢复000001全量 + 000002分段跳过1500 + 000003全量
[root@localhost data]# mysqlbinlog --no-defaults \
# 第一段mysql-bin.000001 全量恢复
/var/lib/mysql/mysql-bin.000001 \
# 第二段mysql-bin.000002 4→1499误操作前
--database=world --start-position=4 --stop-position=1499 /var/lib/mysql/mysql-bin.000002 \
# 第三段mysql-bin.000002 1501→末尾误操作后
--database=world --start-position=1501 /var/lib/mysql/mysql-bin.000002 \
# 第四段mysql-bin.000003 全量恢复
/var/lib/mysql/mysql-bin.000003 \
# 管道导入MySQL的world库需输入root密码
| mysql -uroot -p world
```
## 14.5 慢查询日志
- 是将mysql服务器中影响数据库性能的相关SQL语句记录到日志文件
- 通过对这些特殊的SQL语句分析改进以达到提高数据库性能的目的
### 14.5.1 开启方式(默认没有开启)
```shell
[root@localhost ~]# vim /etc/my.cnf
[mysqld]
# 指定是否开启慢查询日志
slow_query_log = 1
# 指定慢日志文件存放位置默认在data
slow_query_log_file=/application/mysql/data/slow.log
# 设定慢查询的阀值(默认10s)
long_query_time=0.05
# 不使用索引的慢查询日志是否记录到索引
log_queries_not_using_indexes
# 查询检查返回少于该参数指定行的SQL不被记录到慢查询日志
min_examined_row_limit=100
```
### 14.5.2 慢查询实验
一、模拟慢查询语句
```sql
# 导入SELECT练习中的world库
[root@localhost ~]# wget -O world.sql 'https://download.s21i.faiusr.com/23126342/0/0/ABUIABAAGAAgzcXwhQYozuPv2AE?f=world.sql&v=1622942413'
[root@localhost ~]# mysql -uroot -p123456 < world.sql
# 登录到数据库中
mysql> use world
# 创建一个新的表
mysql> create table t1 select * from city;
# 将t1表所有内容插入到t1表中多插入几次
mysql> insert into t1 select * from t1;
mysql> insert into t1 select * from t1;
mysql> insert into t1 select * from t1;
mysql> insert into t1 select * from t1;
# 搜索t1表中id>2000的数据
mysql> select * from t1 where id>2000;
```
二、查看慢日志
```bash
[root@localhost ~]# cat /application/mysql/data/slow.log
/application/mysql/bin/mysqld, Version: 5.6.40-log (MySQL Community Server (GPL)). started with:
Tcp port: 0 Unix socket: (null)
Time Id Command Argument
# Time: 250215 14:17:49
# User@Host: root[root] @ localhost [] Id: 2
# Query_time: 0.043734 Lock_time: 0.009772 Rows_sent: 0 Rows_examined: 4079
use world;
SET timestamp=1739600269;
create table t1 select * from city;
# Time: 250215 14:18:02
# User@Host: root[root] @ localhost [] Id: 2
# Query_time: 0.038587 Lock_time: 0.000171 Rows_sent: 0 Rows_examined: 8158
SET timestamp=1739600282;
insert into t1 select * from t1;
# Time: 250215 14:18:03
# User@Host: root[root] @ localhost [] Id: 2
# Query_time: 0.028329 Lock_time: 0.000063 Rows_sent: 0 Rows_examined: 16316
SET timestamp=1739600283;
insert into t1 select * from t1;
# Time: 250215 14:18:04
# User@Host: root[root] @ localhost [] Id: 2
# Query_time: 0.077498 Lock_time: 0.000064 Rows_sent: 0 Rows_examined: 32632
SET timestamp=1739600284;
insert into t1 select * from t1;
# Time: 250215 14:18:05
# User@Host: root[root] @ localhost [] Id: 2
# Query_time: 0.147896 Lock_time: 0.000062 Rows_sent: 0 Rows_examined: 65264
SET timestamp=1739600285;
insert into t1 select * from t1;
# Time: 250215 14:18:06
# User@Host: root[root] @ localhost [] Id: 2
# Query_time: 0.342130 Lock_time: 0.000066 Rows_sent: 0 Rows_examined: 130528
SET timestamp=1739600286;
insert into t1 select * from t1;
# Time: 250215 14:19:19
# User@Host: root[root] @ localhost [] Id: 2
# Query_time: 0.059468 Lock_time: 0.000121 Rows_sent: 66528 Rows_examined: 130528
SET timestamp=1739600359;
select * from t1 where id>2000;
```
三、使用mysqldumpslow进行慢日志分析
```bash
[root@localhost ~]# mysqldumpslow -s c -t 10 /application/mysql/data/slow.log
```
参数说明:
- -s是表示按照何种方式排序c、t、l、r分别是按照记录次数、时间、查询时间、返回的记录数来排序ac、at、al、ar表示相应的倒叙
- -t是top n的意思即为返回前面多少条的数据
- -g后边可以写一个正则匹配模式大小写不敏感的
例如:
```shell
#得到按照时间排序的前10条里面含有左连接的查询语句
[root@localhost ~]# mysqldumpslow -s t -t 10 -g "left join" /database/mysql/slow-log
```
### 14.5.3 第三方慢日志分析工具-Percona
Percona Toolkit 是由 Percona 公司开发的一套高级命令行工具,用于执行各种 MySQL、MongoDB 和系统任务。这些工具专为复杂任务设计能够帮助数据库管理员和运维人员解决手动操作难以完成的任务。Percona Toolkit 是 Percona 支持团队日常使用的工具,具有专业开发、正式测试和全面文档化的特点。
`pt-query-digest` 是 Percona Toolkit 中的一个重要工具,专门用于分析 MySQL 的慢查询日志。它的核心功能包括:
- **分析和汇总慢查询日志**:通过解析日志文件,找出执行时间长、负载高的查询。
- **生成详细的性能报告**:提供查询执行时间、锁定时间、查询效率排名等信息,帮助快速定位性能瓶颈。
- **支持多种输入类型**:可以分析 MySQL 慢查询日志、通用日志、二进制日志,甚至通过 tcpdump 捕获的网络数据。
- **灵活的过滤和排序**:支持通过正则表达式过滤特定查询,按执行时间、调用次数等排序。
#### 14.5.3.1 工具安装
```shell
[root@localhost ~]# wget https://downloads.percona.com/downloads/percona-toolkit/3.5.0/binary/redhat/9/x86_64/percona-toolkit-3.5.0-5.el9.x86_64.rpm
[root@localhost ~]# yum install -y percona-toolkit-3.5.0-5.el9.x86_64.rpm
[root@localhost ~]# pt-query-digest /application/mysql/data/slow.log
```
#### 14.5.3.2 慢日志分析示例
```bash
[root@localhost ~]# pt-query-digest /application/mysql/data/slow.log
pt-query-digest 的分析结果:
总体统计数据:
Queries examined: 总共分析的查询语句数量
Query time: 总查询时间
Min/Max/Avg query time: 最短、最长和平均查询时间
Variance: 查询时间的方差
Aggregated profile: 按查询类型统计的信息,如 SELECT、INSERT、UPDATE 等
Top Queries:
这部分列出了执行时间最长的前 10 个查询语句。
Rank: 查询在整体中的排名
Query ID: 查询的唯一标识
Response Time: 查询的总响应时间
Calls: 查询被执行的次数
R/Call: 每次调用的平均响应时间
V/M: 查询时间的方差与平均值的比率,反映了查询时间的离散程度
Item: 查询语句的文本
查询语句的详细信息:
Query ID: 查询的唯一标识
Database: 查询所针对的数据库
Users: 执行查询的用户
Hosts: 执行查询的主机
Query: 查询语句的文本
Exec time: 查询的总执行时间
Lock time: 查询获取的锁定时间
Rows sent: 查询返回的行数
Rows examined: 查询扫描的行数
Rows affected: 查询影响的行数
Bytes sent: 查询返回的字节数
Tmp tables: 查询使用的临时表数量
Tmp disk tables: 查询使用的临时磁盘表数量
Scan/Join tables: 查询扫描的表数量
```
# 15. 备份与恢复
**备份的原因:**
- 第一个是保护公司的数据.
- 第二个是让网站能7*24小时提供服务(用户体验)。
- 备份就是为了恢复。
- 尽量减少数据的丢失(公司的损失)
## 15.1 备份的类型
- 冷备份:
- 这些备份在用户不能访问数据时进行,因此无法读取或修改数据。这些脱机备份会阻止执行任何使用数据的活动。这些类型的备份不会干扰正常运行的系统的性能。但是,对于某些应用程序,会无法接受必须在一段较长的时间里锁定或完全阻止用户访问数据。
- 温备份:
- 这些备份在读取数据时进行,但在多数情况下,在进行备份时不能修改数据本身。这种中途备份类型的优点是不必完全锁定最终用户。但是,其不足之处在于无法在进行备份时修改数据集,这可能使这种类型的备份不适用于某些应用程序。在备份过程中无法修改数据可能产生性能问题。
- 热备份:
- 这些动态备份在读取或修改数据的过程中进行,很少中断或者不中断传输或处理数据的功能。使用热备份时,系统仍可供读取和修改数据的操作访问。
## 15.2 备份的方式
**逻辑备份:**MySQL软件自带的功能
- 基于SQL语句的备份
- binlog
- into outfile
- mysqldump
```sql
[root@localhost ~]# vim /etc/my.cnf
[mysqld]
secure_file_priv=/tmp
mysql> select * from world.city into outfile '/tmp/world_city.data';
* mysqldump
* replication
```
**物理备份:**通过二进制方式直接拖走所有数据、配置文件
- 基于数据文件的备份
- Xtrabackuppercona公司
**备份策略:**
- 全量备份 full
- 增量备份 increamental
**备份工具:**
- mysqldump逻辑
- mysql原生自带很好用的逻辑备份工具
- mysqlbinlog逻辑
- 实现binlog备份的原生态命令
- xtrabackup物理
- precona公司开发的性能很高的物理备份工具
## 15.3 mysqldump
`mysqldump` 是 MySQL 数据库备份工具,用于导出数据库结构和数据到 SQL 文件。
### 15.3.1 参数说明
**数据库连接参数**
- `-u` 或 `--user`:指定连接数据库的用户名。
- `-p` 或 `--password`:指定连接数据库的密码。如果省略密码,系统会提示输入。
- `-h` 或 `--host`:指定数据库服务器的主机名或 IP 地址。默认为本地主机(`localhost`)。
- `-P` 或 `--port`:指定数据库服务器的端口号。默认为 `3306`。
- `-S` 或 `--socket`:指定连接到本地 MySQL 服务器的 Unix 套接字文件路径。
------
**数据库和表选择参数**
- 数据库名:指定要备份的数据库名称。
- `-B` 或 `--databases`:备份多个数据库时使用。例如:`mysqldump -B db1 db2`。
- `--all-databases` 或 `-A`:备份所有数据库。
- `--tables`:指定要备份的表名。例如:`mysqldump db_name table1 table2`。
------
**备份内容和格式参数**
- `--no-data` 或 `-d`:只备份数据库结构,不备份数据。
- `--no-create-info` 或 `-t`:只备份数据,不备份表结构。
- `--complete-insert`:使用完整的 `INSERT` 语句(包含列名)。
- `--extended-insert`:使用多行 `INSERT` 语句,提高导入效率。
- `--skip-extended-insert`:禁用多行 `INSERT`,每行数据生成单独的 `INSERT` 语句。
- `--hex-blob`:将二进制数据以十六进制形式导出。
- `--set-gtid-purged`:控制是否记录 GTID 信息。选项为 `ON`、`OFF` 或 `AUTO`。
------
**性能和优化参数**
- `--quick`:对大表进行快速导出,直接将数据写入输出文件,减少内存占用。
- `--single-transaction`:在导出时启动一个事务,确保数据一致性,适用于 InnoDB 存储引擎。
- `--flush-logs`:在导出开始时刷新 MySQL 日志。
- `--master-data`:包含二进制日志信息,用于主从复制的备份。选项为 `1` 或 `2``2` 会将日志信息写入注释中。
- `--lock-tables`:在导出时锁定所有表,确保数据一致性。
- `--skip-lock-tables`:跳过锁定表的操作,适用于大表或高并发环境。
------
**其他参数**
- `--routines` 或 `-R`:导出存储过程和函数。
- `--triggers`:导出触发器。
- `--events`:导出事件调度器中的事件。
- `--ignore-table`:忽略指定的表。例如:`--ignore-table=db_name.table_name`。
- `--result-file` 或 `-r`:将输出结果写入指定文件,而不是标准输出。
- `--help`:显示帮助信息。
### 15.3.2 示例命令
1. **备份单个数据库**
```bash
[root@localhost ~]# mysqldump -uroot -p123456 db_name > backup.sql
```
2. **备份多个数据库**
```bash
[root@localhost ~]# mysqldump -uroot -p123456 --databases db1 db2 > backup.sql
```
3. **备份所有数据库**
```bash
[root@localhost ~]# mysqldump -uroot -p123456 --all-databases > all_backup.sql
```
4. **只备份表结构**
```bash
[root@localhost ~]# mysqldump -uroot -p123456 --no-data db_name > structure.sql
```
5. **备份数据但不锁定表**
```bash
[root@localhost ~]# mysqldump -uroot -p123456 --single-transaction db_name > backup.sql
```
### 15.3.3 **mysqldump特殊使用**
- -x锁表备份myisam温备份
- --single-transaction快照备份
```shell
[root@localhost ~]# mysqldump -uroot -p123 -A -R --triggers --master-data=2 -single-transaction > /backup/full.sql
# 加了文件末尾会多一行CHANGE MASTER TO MASTER_LOG_FILE='mysql-bin.000001', MASTER_LOG_POS=120;
# 不加master-data就不会记录binlog的位置导致后续不利于增量备份
```
- gzip:压缩备份
```shell
[root@localhost ~]# mysqldump -uroot -p123456 -A -R --triggers --master-data=2 single-transaction|gzip > /backup/full.sql.gz
[root@localhost ~]# gzip -d /backup/full.sql.gz
[root@localhost ~]# zcat /backup/full.sql.gz > linshi.sql
```
- 常用的热备份备份语句
```shell
mysqldump -uroot -p3307 -A -R --triggers --master-data=2 --single-transaction |gzip > /tmp/full_$(date +%F).sql.gz
```
- mysqldump的恢复
```sql
mysql> set sql_log_bin=0;
#先不记录二进制日志
mysql> source /backup/full.sql
#库内恢复操作
[root@localhost ~]# mysql -uroot -p123456 < /backup/full.sql
#库外恢复操作
```
**注意事项:**
- mysqldump在备份和恢复时都需要MySQL实例启动为前提
- 一般数据量级100G以内大约15-30分钟可以恢复PB、EB就需要考虑别的方式
- mysqldump是以覆盖的形式恢复数据的
### 15.3.4 实战案例-企业故障恢复
- 背景:
- 正在运行的网站系统MySQL数据库数据量25G日业务增量10-15M。
- 备份策略:
- 每天2300计划任务调用mysqldump执行全备脚本
- 故障时间点:
- 上午10点开发人员误删除一个核心业务表需要恢复
- 思路
- **停业务避免数据的二次伤害**
- 找一个临时的库,恢复前一天的全备
- 截取前一天2300到第二天10点误删除之间的binlog恢复到临时库
- 测试可用性和完整性
- 开启业务前的两种方式
- 直接使用临时库顶替原生产库,前端应用割接到新库
- 将误删除的表单独导出,然后导入到原生产环境
- 开启业务
#### 15.3.4.1 故障模拟
一、故障模拟
```sql
# 刷新binlog使内容更清晰
mysql> reset master;
mysql> show master status;
+------------------+----------+--------------+------------------+-------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+-------------------+
| mysql-bin.000001 | 120 | | | |
+------------------+----------+--------------+------------------+-------------------+
# 创建backup库
mysql> create database backup;
mysql> use backup
# 创建full表
mysql> create table full select * from world.city;
Query OK, 4079 rows affected (0.04 sec)
Records: 4079 Duplicates: 0 Warnings: 0
mysql> show tables;
+------------------+
| Tables_in_backup |
+------------------+
| full |
+------------------+
1 row in set (0.00 sec)
```
二、对数据库进行一次全备
```bash
[root@localhost ~]# mkdir /backup
[root@localhost ~]# mysqldump -uroot -p123456 -A -R --triggers --master-data=1 --single-transaction|gzip > /backup/full_$(date +%F).sql.gz
[root@localhost ~]# ll /backup/
total 2332
-rw-r--r-- 1 root root 2385815 Feb 15 15:31 full_2025-02-15.sql.gz
```
三、模拟从前一天晚上23点到10点前的数据变化
```sql
# 把full表中所有的countrycode都改成CHN
mysql> update full set countrycode='CHN' where 1=1;
# 删除id大于200的数据
mysql> delete from full where id>200;
# 产生一个新的表
mysql> create table new select * from mysql.user;
```
四、模拟第二天上午10点误删new表
```sql
mysql> drop table new;
mysql> show tables;
+------------------+
| Tables_in_backup |
+------------------+
| full |
+------------------+
1 row in set (0.00 sec)
```
#### 15.3.4.2 **数据恢复**
一、启动一个新的数据库实例
```bash
[root@localhost ~]# mysqld_safe --defaults-file=/data/3307/my.cnf &
#解压全备数据文件,如果用同一个机器的不同实例则不需要这步
[root@localhost ~]# scp /backup/full_2024-08-16.sql.gz root@192.168.88.20:/tmp
```
二、准备恢复
```bash
mysql> show binlog events in 'mysql-bin.000001'\G
# 解压全备数据文件
[root@localhost ~]# gzip -d /backup/full_2025-02-15.sql.gz
[root@localhost ~]# ll /backup/
total 6600
-rw-r--r-- 1 root root 6754549 Feb 15 15:31 full_2025-02-15.sql
# 找到全备数据的binlog位置点
[root@localhost ~]# head -50 /backup/full_2025-02-15.sql | grep -i 'change master to'
CHANGE MASTER TO MASTER_LOG_FILE='mysql-bin.000001', MASTER_LOG_POS=134046;
# 在生产环境中找到drop删除前的位置点
mysql> show binlog events in 'mysql-bin.000001'\G
*************************** 81. row ***************************
Log_name: mysql-bin.000001
Pos: 510614
Event_type: Query
Server_id: 1
End_log_pos: 510734
Info: use `backup`; DROP TABLE `new` /* generated by server */
# 截取二进制日志把备份点和drop之间的信息倒入到inc.sql
[root@localhost ~]# mysqlbinlog -uroot -p123456 --start-position=134046 --stop-position=510614 /application/mysql/data/mysql-bin.000001 > /tmp/inc.sql
# 如果是新库是远程主机就将截取好的binlog日志发送到远程主机上本机不需要发送
```
三、在新库内恢复数据
```bash
[root@localhost ~]# mysql -uroot -p123456 -S /data/3307/mysql.sock
#不记录二进制日志
mysql> set sql_log_bin=0;
#恢复全备数据
mysql> source /backup/full_2025-02-15.sql
mysql> use backup
mysql> show tables;
+------------------+
| Tables_in_backup |
+------------------+
| full |
+------------------+
1 row in set (0.00 sec)
# 此时只恢复了前一天23点的全被数据所以并没有new表
# 再次恢复我们截取好的binlog日志(增量日志)
mysql> source /tmp/inc.sql
mysql> show tables;
+------------------+
| Tables_in_backup |
+------------------+
| full |
| new |
+------------------+
mysql> select user,host from new;
+------+-----------------------+
| user | host |
+------+-----------------------+
| root | localhost |
| root | localhost.localdomain |
| root | 127.0.0.1 |
| root | ::1 |
| | localhost |
| | localhost.localdomain |
+------+-----------------------+
6 rows in set (0.00 sec)
```
这个时候我们就在新库中完全恢复好了我们数据库所有的信息如果想要恢复生产数据库我们直接让生产数据库恢复增量备份的数据即可。或者是从新库中导出new表的数据直接恢复到生产服务器上即可
```bash
[root@localhost ~]# mysqldump -uroot -p123456 -S /data/3307/mysql.sock backup new > /tmp/new.sql
# 导出new表,发送到生产数据库此时生产数据库backup库里没有new表被无良开发drop掉了可直接从new.sql倒入
mysql> use backup
# 进入backup库此时生产环境的backup
mysql> source /tmp/new.sql
```
## 15.4 物理备份Xtrabackup
Xtrabackup 是由 Percona 公司开源的一款 MySQL 物理热备份工具,广泛应用于 MySQL 和 MariaDB 数据库的备份。它支持非阻塞备份,能够在备份过程中不中断事务处理,同时不会显著增加数据库服务器的负载
- Xtrabackup安装
```shell
# 下载并安装Percona官方仓库配置包适配RHEL/CentOS 8+
yum install -y https://repo.percona.com/yum/percona-release-latest.noarch.rpm
# 启用xtrabackup 2.4仓库(默认可能禁用,需显式开启)
percona-release enable-only tools release
# 刷新yum缓存加载新仓库信息
yum clean all && yum makecache
# 直接安装yum会自动匹配系统版本并安装所有依赖
yum install -y percona-xtrabackup-24
# 查看版本,确认安装成功
xtrabackup --version
# 预期输出xtrabackup version 2.4.xx based on MySQL server 5.7.xx (Linux (x86_64))
```
- 备份方式(物理备份)
- 对于非innodb表比如myisam是直接锁表cp数据文件属于一种温备。
- 对于innodb的表支持事务不锁表cp数据页最终以数据文件方式保存下来并且把redo和undo一并备走属于热备方式。
- 备份时读取配置文件/etc/my.cnf需要注明socket=/application/mysql/tmp/mysql.sock文件的位置
- 全量备份
```shell
[root@db01 data]# innobackupex --user=root --password=123456 -S /tmp/mysql.sock /backup
#全备
[root@db01 ~]# innobackupex --user=root --password=123456 --no-timestamp -S /tmp/mysql.sock /backup/full
#避免时间戳,自定义路径名
[root@db01 backup]# ll /backup/full
#查看备份路径中的内容
-rw-r----- 1 root root 21 Aug 16 06:23 xtrabackup_binlog_info
#记录binlog文件名和binlog的位置点
-rw-r----- 1 root root 117 Aug 16 06:23 xtrabackup_checkpoints
#备份时刻立即将已经commit过的内存中的数据页刷新到磁盘
#备份时刻有可能会有其他数据写入,已备走的数据文件就不会再发生变化了
#在备份过程中备份软件会一直监控着redo和undo一旦有变化会将日志一并备走
-rw-r----- 1 root root 485 Aug 16 06:23 xtrabackup_info
#备份汇总信息
-rw-r----- 1 root root 2560 Aug 16 06:23 xtrabackup_logfile
#备份的redo文件
* 准备备份
* 将redo进行重做已提交的写到数据文件未提交的使用undo回滚模拟CSR的过程
[root@db01 full]# innobackupex --user=root --password=123456 --apply-log -S /tmp/mysql.sock /backup/full
```
- 恢复备份
- 前提1被恢复的目录是空的
- 前提2被恢复的数据库的实例是关闭的
```shell
[root@db01 full]# /etc/init.d/mysqld stop
#停库
[root@db01 full]# cd /application/mysql
#进入mysql目录
[root@db01 mysql]# rm -fr data/
#删除data目录在生产中可以备份一下
[root@db01 mysql]# innobackupex --copy-back /backup/full
#拷贝数据
[root@db01 mysql]# chown -R mysql.mysql /application/mysql/data/
#授权
[root@db01 mysql]# /etc/init.d/mysqld start
#启动MySQL
```
- 增量备份及恢复
- 基于上一次备份进行增量
- 增量备份无法单独恢复,必须基于全备进行恢复
- 所有增量必须要按顺序合并到全备当中
```shell
[root@mysql-db01 ~]# innobackupex --user=root --password=123456 --no-timestamp -S /tmp/mysql.sock /backup/full
#不使用之前的全备,执行一次全备
```
- 模拟数据变化
```sql
mysql> create database inc1;
mysql> use inc1
mysql> create table inc1_tab(id int);
mysql> insert into inc1_tab values(1),(2),(3);
mysql> commit;
mysql> select * from inc1_tab;
```
- 第一次增量备份
```shell
[root@db01 ~]# innobackupex --user=root --password=123456 --no-timestamp --incremental -S /tmp/mysql.sock --incremental-basedir=/backup/full/ /backup/inc1
参数说明:
--incremental开启增量备份功能
--incremental-basedir上一次备份的路径
```
- 再次模拟数据变化
```sql
mysql> create database inc2;
mysql> use inc2
mysql> create table inc2_tab(id int);
mysql> insert into inc2_tab values(1),(2),(3);
mysql> commit;
```
- 第二次增量备份
```shell
[root@db01 ~]# innobackupex --user=root --password=123456 --no-timestamp --incremental -S /tmp/mysql.sock --incremental-basedir=/backup/inc1/ /backup/inc2
```
- 增量恢复
```shell
[root@db01 ~]# rm -fr /application/mysql/data/
#破坏数据
```
- 准备备份
- full+inc1+inc2
- 需要将inc1和inc2按顺序合并到full中
- 分步骤进行--apply-log
- 第一步在全备中apply-log时只应用redo不应用undo
```shell
[root@db01 ~]# innobackupex --apply-log --redo-only /backup/full/
```
- 第二步合并inc1合并到full中并且apply-log只应用redo不应用undo
```shell
[root@db01 ~]# innobackupex --apply-log --redo-only --incremental-dir=/backup/inc1/ /backup/full/
```
- 第三步合并inc2合并到full中redo和undo都应用
```shell
[root@db01 ~]# innobackupex --apply-log --incremental-dir=/backup/inc2/ /backup/full/
```
- 第四步整体full执行apply-logredo和undo都应用
```shell
[root@db01 mysql]# innobackupex --apply-log /backup/full/
[root@db01 mysql]# systemctl stop mysqld
[root@db01 mysql]# innobackupex --copy-back /backup/full/
[root@db01 mysql]# chown -R mysql.mysql /application/mysql/data/
[root@db01 mysql]# systemctl start mysqld
```
# 16. MySQL的主从复制
## 16.1 主从复制原理
<img src="01.Mysql/sql_MS-1739583976576111.jpg" alt="img" style="zoom:80%;" />
- 复制是 MySQL 的一项功能,允许服务器将更改从一个实例复制到另一个实例。
- 主服务器将所有数据和结构更改记录到二进制日志中。
- 从属服务器从主服务器请求该二进制日志并在本地应用其内容。
- IO请求主库获取上一次执行过的新的事件并存放到relaylog
- SQL从relaylog中将sql语句翻译给从库执行
- 主从复制的前提
- 两台或两台以上的数据库实例
- 主库要开启二进制日志
- 主库要有复制用户
- 主库的server_id和从库不同
- 从库需要在开启复制功能前要获取到主库之前的数据主库备份并且记录binlog当时位置
- 从库在第一次开启主从复制时时必须获知主库ipportuserpasswordlogfilepos
- 从库要开启相关线程IO、SQL
- 从库需要记录复制相关用户信息,还应该记录到上次已经从主库请求到哪个二进制日志
- 从库请求过来的binlog首先要存下来并且执行binlog执行过的信息保存下来
- 主从复制涉及到的文件和线程
- 主库:
- 主库binlog记录主库发生过的修改事件
- dump thread给从库传送TP二进制日志线程
- 从库:
- relay-log中继日志存储所有主库TP过来的binlog事件在SQL thread执行完毕数据持久化后清空但写入relaylog.info文件
- relaylog.info类似于master的binlog文件
- master.info存储复制用户信息**上次请求到的主库binlog位置点**
- IO thread接收主库发来的binlog日志也是从库请求主库的线程
- SQL thread执行主库TP过来的日志
- 原理
- 通过change master to语句告诉从库主库的ipportuserpasswordfilepos
- 从库通过start slave命令开启复制必要的IO线程和SQL线程
- 从库通过IO线程拿着change master to用户密码相关信息连接主库验证合法性
- 从库连接成功后会根据binlog的pos问主库有没有比这个更新的
- 主库接收到从库请求后比较一下binlog信息如果有就将最新数据通过dump线程给从库IO线程
- 从库通过IO线程接收到主库发来的binlog事件存储到TCP/IP缓存中并返回ACK更新master.info
- 将TCP/IP缓存中的内容存到relay-log中
- SQL线程读取relay-log.info读取到上次已经执行过的relay-log位置点继续执行后续的relay-log日志执行完成后更新relay-log.info
## 16.2 【实战】MySQL主从复制
本实例中需要两台MySQL数据库最好是在两个不同的机器上面。其中db01是masterdb02是slave。
并且两台机器上面具有不同的server_id
#### 16.2.1 主库操作(db01)
- 修改配置文件
```shell
[root@db01 ~]# vim /etc/my.cnf
[mysqld] #在mysqld标签下配置
server_id =1
#主库server-id为1从库的server_id必须与主库不同
log_bin=mysql-bin
#开启binlog日志
[root@db01 ~]# systemctl restart mysqld
```
- 导出一份全备数据给从库(在已经使用的数据库中,如果是后续搭建主从,则需要做如下操作。确保已有的数据一致)
```shell
[root@db01 ~]# mysqldump -uroot -p123456 -A -R --triggers --master-data=2 --single-transaction > /tmp/full.sql
[root@db01 ~]# scp /tmp/full.sql root@192.168.88.140:/tmp/
```
- 创建主从复制用户
```shell
[root@db01 ~]# mysql -uroot -p123456
#登录数据库
mysql> grant replication slave on *.* to slave@'192.168.88.%' identified by '123456';
mysql> flush privileges;
#创建slave用户
```
#### 16.2.2 从库操作(db02)
1. 修改配置文件
```bash
[root@db02 ~]# vim /etc/my.cnf
#修改db02配置文件
[mysqld]
#在mysqld标签下配置
server_id =5
#主库server-id为1从库不等于1
log_bin=mysql-bin
#开启binlog日志
[root@db02 ~]# systemctl restart mysqld
```
2. 记录主库当前的binlog位置
```bash
[root@db01 ~]# mysql -uroot -p123456
mysql> show master status;
```
3. 再从库上配置主库等信息
```bash
[root@db02 ~]# mysql -uroot -p123456
#登陆数据库
mysql> change master to
master_host='192.168.88.10',
master_user='slave',
master_password='123456',
master_log_file='mysql-bin.000001',
master_log_pos=120;
# master_auto_position=1; # 与前两句冲突表示自动适配master的file和positionMariaDB5.5不支持GTID特性。此时未开启GTID
# master_auto_position=1 表示开启 MySQL 的基于 GTID 的主从复制功能。
# GTID(Global Transaction Identifier)是一种基于事务的复制方式,相比传统的基于文件和位置的复制方式,GTID 复制更加灵活和可靠。
# 当 master_auto_position=1 时,slave 会自动找到主库上最新的 GTID 位置,而不需要手动指定日志文件和位置。这样可以避免主从同步时由于位置不匹配而导致的问题。
```
#### 16.2.3 开启主从复制
```sql
# 执行change master to 语句后
mysql> start slave;
mysql> show slave status\G
# 看到Slave_IO_Running: Yes Slave_SQL_Running: Yes表示运行成功
# 从数据库:/application/mysql/data目录下出现master.info文件记录了同步的索引号
# 测试方法:在主里面库建表插入内容,从里面可以看到主新增的内容表示同步成功。
```
## 16.3 主从复制基本故障处理
- IO线程报错
- user password ip port
- 网络:不同,延迟高,防火墙
- 没有跳过反向解析[mysqld]里面加入skip-name-resolve
- 请求binlog
- binlog不存在或者损坏
- 更新relay-log和master.info
- SQL线程
- relay-log出现问题
- 从库做写入了
- 操作对象已存在create
- 操作对象不存在insert update delete drop truncate alter
- 约束问题、数据类型、列属性
### 16.3.1 处理方法一
```sql
mysql> stop slave;
#临时停止同步
mysql> set global sql_slave_skip_counter=1;
#将同步指针向下移动一个可重复操作告诉这个执行不了的binlog条目不执行去执行下一条。
mysql> start slave;
#开启同步
```
### 16.3.2 处理方法二
```shell
[root@db01 ~]# vim /etc/my.cnf
#编辑配置文件
slave-skip-errors=1032,1062,1007
#在[mysqld]标签下添加以下参数
```
但是以上操作都是有风险存在的
### 16.3.3 处理方法三
```sql
-- 临时开启(从库)
SET GLOBAL read_only=1;
SET GLOBAL super_read_only=1; -- MySQL5.7+禁止super权限用户写包括root
-- 永久开启修改从库my.cnf重启生效
[mysqld]
read_only=1
super_read_only=1 -- 关键:彻底禁止所有用户写从库
```
## 16.4 延时从库
- 普通的主从复制可能存在不足
- 逻辑损坏怎么办?
- 不能保证主库的操作,从库一定能做
- 高可用自动failover
- 过滤复制
- 企业中一般会延时3-6小时
- 延时从库配置方法
```sql
mysql>stop slave;
#停止主从
mysql>CHANGE MASTER TO MASTER_DELAY = 180;
#设置延时为180秒
mysql>start slave;
#开启主从
mysql> show slave status \G
SQL_Delay: 60
#查看状态
mysql> stop slave;
#停止主从
mysql> CHANGE MASTER TO MASTER_DELAY = 0;
#设置延时为0
mysql> start slave;
#开启主从
```
- 恢复数据
- 停止主从
- 导出从库数据
- 主库导入数据
## 16.5 半同步复制
从MYSQL5.5开始支持半自动复制。之前版本的MySQL Replication都是异步asynchronous主库在执行完一些事务后是不会管备库的进度的。如果备库不幸落后而更不幸的是主库此时又出现Crash例如宕机这时备库中的数据就是不完整的。简而言之在主库发生故障的时候我们无法使用备库来继续提供数据一致的服务了。
![img](01.Mysql/remote_mem_larger_local_disk-1739583976576112.jpg)
半同步复制Semi synchronous Replication则一定程度上保证提交的事务已经传给了至少一个备库。
IO在收到了读写后会发送ACK报告已经收到了。
![img](01.Mysql/semi_synchronized_dual_master-1739583976576113.jpg)
出发点是保证主从数据一致性问题,安全的考虑。
- 半同步复制开启方法
- 安装(主库)
```shell
[root@db01 ~]# mysql -uroot -p123456
#登录数据库
mysql> show global variables like 'have_dynamic_loading';
#查看是否有动态支持 have_dynamic_loading=YES
mysql> INSTALL PLUGIN rpl_semi_sync_master SONAME'semisync_master.so';
#安装自带插件
mysql> SET GLOBAL rpl_semi_sync_master_enabled = 1;
#启动插件
mysql> SET GLOBAL rpl_semi_sync_master_timeout = 1000;
#设置超时
[root@db01 ~]# vim /etc/my.cnf
#修改配置文件
[mysqld]
rpl_semi_sync_master_enabled=1
rpl_semi_sync_master_timeout=1000 #多长时间认为超时单位ms
#在[mysqld]标签下添加如下内容(不用重启库)
mysql> show variables like'rpl%';
mysql> show global status like 'rpl_semi%';
#检查安装
```
**从库安装:**
```bash
[root@mysql-db02 ~]# mysql -uroot -p123456
#登录数据库
mysql> INSTALL PLUGIN rpl_semi_sync_slave SONAME'semisync_slave.so';
#安装slave半同步插件
mysql> SET GLOBAL rpl_semi_sync_slave_enabled = 1;
#启动插件
mysql> stop slave io_thread;
mysql> start slave io_thread;
#重启io线程使其生效
[root@mysql-db02 ~]# vim /etc/my.cnf
#编辑配置文件(不需要重启数据库)
[mysqld]
rpl_semi_sync_slave_enabled =1
#在[mysqld]标签下添加如下内容
```
**注:相关参数说明:**
- rpl_semi_sync_master_timeout=milliseconds
- 设置此参数值ms,为了防止半同步复制在没有收到确认的情况下发生堵塞如果Master在超时之前没有收到任何确认将恢复到正常的异步复制并继续执行没有半同步的复制操作。
- rpl_semi_sync_master_wait_no_slave={ON|OFF}
- 如果一个事务被提交,但Master没有任何Slave的连接这时不可能将事务发送到其它地方保护起来。默认情况下Master会在时间限制范围内继续等待Slave的连接并确认该事务已经被正确的写到磁盘上。
- 可以使用此参数选项关闭这种行为在这种情况下如果没有Slave连接Master就会恢复到异步复制。
**测试半同步:**
```sql
mysql> create database test1;
Query OK, 1 row affected (0.04 sec)
mysql> create database test2;
Query OK, 1 row affected (0.00 sec)
#创建两个数据库test1和test2
mysql> show global status like 'rpl_semi%';
#查看复制状态Rpl_semi_sync_master_status状态是ON
+--------------------------------------------+-------+
| Variable_name | Value |
+--------------------------------------------+-------+
| Rpl_semi_sync_master_clients | 1 |
| Rpl_semi_sync_master_net_avg_wait_time | 768 |
| Rpl_semi_sync_master_net_wait_time | 1497 |
| Rpl_semi_sync_master_net_waits | 2 |
| Rpl_semi_sync_master_no_times | 0 |
| Rpl_semi_sync_master_no_tx | 0 |
| Rpl_semi_sync_master_status | ON |
| Rpl_semi_sync_master_timefunc_failures | 0 |
| Rpl_semi_sync_master_tx_avg_wait_time | 884 |
| Rpl_semi_sync_master_tx_wait_time | 1769 |
| Rpl_semi_sync_master_tx_waits | 2 |
| Rpl_semi_sync_master_wait_pos_backtraverse | 0 |
| Rpl_semi_sync_master_wait_sessions | 0 |
#此行显示2表示刚才创建的两个库执行了半同步
| Rpl_semi_sync_master_yes_tx | 2 |
+--------------------------------------------+-------+
14 rows in set (0.06 sec)
mysql> show databases;
#从库查看
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| test |
| test1 |
| test2 |
+--------------------+
# 主库
mysql> SET GLOBAL rpl_semi_sync_master_enabled = 0;
#关闭半同步1:开启 0:关闭)
mysql> show global status like 'rpl_semi%';
#查看半同步状态
+--------------------------------------------+-------+
| Variable_name | Value |
+--------------------------------------------+-------+
| Rpl_semi_sync_master_clients | 1 |
| Rpl_semi_sync_master_net_avg_wait_time | 768 |
| Rpl_semi_sync_master_net_wait_time | 1497 |
| Rpl_semi_sync_master_net_waits | 2 |
| Rpl_semi_sync_master_no_times | 0 |
| Rpl_semi_sync_master_no_tx | 0 |
| Rpl_semi_sync_master_status | OFF | #状态为关闭
| Rpl_semi_sync_master_timefunc_failures | 0 |
| Rpl_semi_sync_master_tx_avg_wait_time | 884 |
| Rpl_semi_sync_master_tx_wait_time | 1769 |
| Rpl_semi_sync_master_tx_waits | 2 |
| Rpl_semi_sync_master_wait_pos_backtraverse | 0 |
| Rpl_semi_sync_master_wait_sessions | 0 |
| Rpl_semi_sync_master_yes_tx | 2 |
+--------------------------------------------+-------+
14 rows in set (0.00 sec)
mysql> create database test3;
Query OK, 1 row affected (0.00 sec)
mysql> create database test4;
Query OK, 1 row affected (0.00 sec)
#再一次创建两个库
mysql> show global status like 'rpl_semi%';
#再一次查看半同步状态此时Rpl_semi_sync_master_status变成OFF
+--------------------------------------------+-------+
| Variable_name | Value |
+--------------------------------------------+-------+
| Rpl_semi_sync_master_clients | 1 |
| Rpl_semi_sync_master_net_avg_wait_time | 768 |
| Rpl_semi_sync_master_net_wait_time | 1497 |
| Rpl_semi_sync_master_net_waits | 2 |
| Rpl_semi_sync_master_no_times | 0 |
| Rpl_semi_sync_master_no_tx | 0 |
| Rpl_semi_sync_master_status | OFF |
| Rpl_semi_sync_master_timefunc_failures | 0 |
| Rpl_semi_sync_master_tx_avg_wait_time | 884 |
| Rpl_semi_sync_master_tx_wait_time | 1769 |
| Rpl_semi_sync_master_tx_waits | 2 |
| Rpl_semi_sync_master_wait_pos_backtraverse | 0 |
| Rpl_semi_sync_master_wait_sessions | 0 |
#此行还是显示2则证明刚才的那两条并没有执行半同步否则应该是4
| Rpl_semi_sync_master_yes_tx | 2 |
+--------------------------------------------+-------+
14 rows in set (0.00 sec)
注:不难发现,在查询半同步状态是,开启半同步,查询会有延迟时间,关闭之后则没有
```
- 某公司程序员不小心删除了核心业务数据已知公司没有全量备份但是有延时从库1个小时)。现在要求恢复数据库
思路:
1. 停止业务数据库
2. 停止延时从库的slave
3. 检查业务数据库bin_log找到slave上最新的pos和删除前的pos
4. 导入从库,检查数据是否完整
5. 恢复业务数据
- 模拟故障:配置延时从库,然后主库随便增删改,然后删除表,尝试恢复
## 16.6 过滤复制
- 主库:
- 白名单:只记录白名单中列出的库的二进制日志
- binlog-do-db
- 黑名单:不记录黑名单列出的库的二进制日志
- binlog-ignore-db
- 从库:
- 白名单:只执行白名单中列出的库或者表的中继日志
- --replicate-do-db=test
- --replicate-do-table=test.t1
- --replicate-wild-do-table=test.t2
- 黑名单:不执行黑名单中列出的库或者表的中继日志
- --replicate-ignore-db
- --replicate-ignore-table
- --replicate-wild-ignore-table
- 复制过滤配置
```shell
[root@db01 data]# vim /data/3307/my.cnf
replicate-do-db=world
#在[mysqld]标签下添加
mysqladmin -S /data/3307/mysql.sock -p123456 shutdown
#关闭MySQL
mysqld_safe --defaults-file=/data/3307/my.cnf &
#启动MySQL
#设置主从的server_id如法炮制设置主从关系,记得change master to时候加上参数master_port = 3306,
```
- 测试复制过滤:
- 第一次测试:
- 主库:
```sql
[root@db02 ~]# mysql -uroot -p123 -S /data/3308/mysql.sock
mysql> use world
mysql> create table t1(id int);
* 从库查看结果
[root@db02 ~]# mysql -uroot -p123 -S /data/3307/mysql.sock
mysql> use world
mysql> show tables;
```
- 第二次测试
- 主库
```shell
[root@db02 ~]# mysql -uroot -p123 -S /data/3308/mysql.sock
mysql> use test
mysql> create table tb1(id int);
* 从库查看结果
[root@db02 ~]# mysql -uroot -p123 -S /data/3307/mysql.sock
mysql> use test
mysql> show tables;
```
# 17. MHA高可用架构
MHAMaster High Availability目前在MySQL高可用方面是一个相对成熟的解决方案它由日本DeNA公司youshimaton现就职于Facebook公司开发是一套优秀的作为MySQL高可用性环境下故障切换和主从提升的高可用软件。在MySQL故障切换过程中MHA能做到在0~30秒之内自动完成数据库的故障切换操作并且在进行故障切换的过程中MHA能在最大程度上保证数据的一致性以达到真正意义上的高可用。
![img](01.Mysql/MHA_topo-1739583976576115.jpg)
MHA能够在较短的时间内实现自动故障检测和故障转移通常在0.5-2秒以内;在复制框架中MHA能够很好地解决复制过程中的数据一致性问题由于不需要在现有的replication中添加额外的服务器仅需要一个manager节点而一个Manager能管理多套复制所以能大大地节约服务器的数量;另外,安装简单,无性能损耗,以及不需要修改现有的复制部署也是它的优势之处。
MHA还提供在线主库切换的功能能够安全地切换当前运行的主库到一个新的主库中(通过将从库提升为主库),大概0.5-2秒内即可完成。
MHA由两部分组成MHA Manager管理节点和MHA Node数据节点。MHA Manager可以独立部署在一台独立的机器上管理多个Master-Slave集群也可以部署在一台Slave上。当Master出现故障时它可以自动将最新数据的Slave提升为新的Master,然后将所有其他的Slave重新指向新的Master。整个故障转移过程对应用程序是完全透明的。
## 17.1 工作流程
1. 把宕机的master二进制日志保存下来。
2. 找到binlog位置点最新的slave。
3. 在binlog位置点最新的slave上用relay log差异日志修复其它slave。因为relay log修复比bin log快所以不用master的bin logslave没有bin log
4. 将宕机的master上保存下来的二进制日志恢复到含有最新位置点的slave上。
5. 将含有最新位置点binlog所在的slave提升为master。
6. 将其它slave重新指向新提升的master并开启主从复制。
<img src="01.Mysql/MHA_process-1739583976576116.jpg" alt="img" style="zoom:80%;" />
## 17.2 MHA工具介绍
- MHA软件由两部分组成Manager工具包和Node工具包
- Manager工具包主要包括以下几个工具
| masterha_check_ssh | 检查MHA的ssh-key |
| :----------------------- | :---------------------- |
| masterha_check_repl | 检查主从复制情况 |
| masterha_manger | 启动MHA |
| masterha_check_status | 检测MHA的运行状态 |
| masterha_master_monitor | 检测master是否宕机 |
| masterha_master_switch | 手动故障转移 |
| masterha_conf_host | 手动添加server信息 |
| masterha_secondary_check | 建立TCP连接从远程服务器 |
| masterha_stop | 停止MHA |
- Node工具包主要包括以下几个工具
| save_binary_logs | 保存宕机的master的binlog |
| :-------------------- | :----------------------- |
| apply_diff_relay_logs | 识别relay log的差异 |
| filter_mysqlbinlog | 防止回滚事件 |
| purge_relay_logs | 清除中继日志 |
- MHA优点总结
- Masterfailover and slave promotion can be done very quickly
- 自动故障转移快
- Mastercrash does not result in data inconsistency
- 主库崩溃不存在数据一致性问题
- Noneed to modify current MySQL settings (MHA works with regular MySQL)
- 不需要对当前mysql环境做重大修改
- Noneed to increase lots of servers
- 不需要添加额外的服务器(仅一台manager就可管理上百个replication)
- Noperformance penalty
- 性能优秀可工作在半同步复制和异步复制当监控mysql状态时仅需要每隔N秒向master发送ping包(默认3秒)所以对性能无影响。你可以理解为MHA的性能和简单的主从复制框架性能一样。
- Works with any storage engine
- 只要replication支持的存储引擎MHA都支持不会局限于innodb
## 17.3 MHA实验环境
- 搭建三台mysql数据库
```shell
wget https://downloads.mysql.com/archives/get/p/23/file/mysql-5.6.40-linux-glibc2.12-x86_64.tar.gz
tar xzvf mysql-5.6.40-linux-glibc2.12-x86_64.tar.gz
mkdir /application
mv mysql-5.6.40-linux-glibc2.12-x86_64 /application/mysql-5.6.40
ln -s /application/mysql-5.6.40 /application/mysql
cd /application/mysql/support-files
cp my-default.cnf /etc/my.cnf
cp是否覆盖"/etc/my.cnf" y
cp mysql.server /etc/init.d/mysqld
cd /application/mysql/scripts
useradd mysql -s /sbin/nologin -M
yum -y install autoconf
./mysql_install_db --user=mysql --basedir=/application/mysql --data=/application/mysql/data
vim /etc/profile.d/mysql.sh
export PATH="/application/mysql/bin:$PATH"
source /etc/profile
sed -i 's#/usr/local#/application#g' /etc/init.d/mysqld /application/mysql/bin/mysqld_safe
vim /usr/lib/systemd/system/mysqld.service
[Unit]
Description=MySQL Server
Documentation=man:mysqld(8)
Documentation=https://dev.mysql.com/doc/refman/en/using-systemd.html
After=network.target
After=syslog.target
[Install]
WantedBy=multi-user.target
[Service]
User=mysql
Group=mysql
ExecStart=/usr/local/mysql/bin/mysqld --defaults-file=/etc/my.cnf
LimitNOFILE = 5000
systemctl start mysqld
systemctl enable mysqld
mysqladmin -uroot password '123456'
mysql -uroot -p123456
```
## 17.4 基于GTID的主从复制
- 先决条件
- 主库和从库都要开启binlog
- 主库和从库server-id不同
- 要有主从复制用户
主库操作
- 修改配置文件
```shell
[root@mysql-db01 ~]# vim /etc/my.cnf
#编辑mysql配置文件
[mysqld]
#在mysqld标签下配置
server_id =1
#主库server-id为1从库不等于1
log_bin=mysql-bin
#开启binlog日志
#如果3台设备是在装了mysql后克隆的mysql的UUID则相同需要修改为不同值
[root@mysql-db01 ~]# vim /application/mysql/data/auto.cnf
server-uuid=8108d02e-be0a-11ec-8a15-000c2956d2e2
```
- 创建主从复制用户,**每台设备都要配置因为slave有可能变成master**
```shell
[root@mysql-db01 ~]# mysql -uroot -p123456
#登录数据库
mysql> grant replication slave on *.* to slave@'192.168.88.%' identified by '123456';
#创建slave用户
```
从库操作
- 修改配置文件
```shell
[root@mysql-db02 ~]# vim /etc/my.cnf
#修改mysql-db02配置文件
[mysqld]
#在mysqld标签下配置
server_id =5
#主库server-id为1从库必须大于1
log_bin=mysql-bin
#开启binlog日志
[root@mysql-db02 ~]# systemctl restart mysqld
#重启mysql
[root@mysql-db03 ~]# vim /etc/my.cnf
#修改mysql-db03配置文件
[mysqld]
#在mysqld标签下配置
server_id =10
#主库server-id为1从库必须大于1
log_bin=mysql-bin
#开启binlog日志
[root@mysql-db03 ~]# systemctl restart mysqld
#重启mysql
```
在以往如果是基于binlog日志的主从复制则必须要记住主库的master状态信息。
```sql
mysql> show master status;
+------------------+----------+
| File | Position |
+------------------+----------+
| mysql-bin.000002 | 120 |
+------------------+----------+
```
**主、从库都要开启GTID**
```shell
mysql> show global variables like '%gtid%';
#没开启之前先看一下GTID的状态
+--------------------------+-------+
| Variable_name | Value |
+--------------------------+-------+
| enforce_gtid_consistency | OFF |
| gtid_executed | |
| gtid_mode | OFF |
| gtid_owned | |
| gtid_purged | |
+--------------------------+-------+
[root@mysql-db01 ~]# vim /etc/my.cnf
#编辑mysql配置文件主库从库都需要修改
[mysqld]
#在[mysqld]标签下添加
gtid_mode=ON
log_slave_updates
#重要开启slave的binlog同步
enforce_gtid_consistency
#开启GTID特性
[root@mysql-db01 ~]# systemctl restart mysqld
#重启数据库
mysql> show global variables like '%gtid%';
#检查GTID状态
+--------------------------+-------+
| Variable_name | Value |
+--------------------------+-------+
| enforce_gtid_consistency | ON | #执行GTID一致
| gtid_executed | |
| gtid_mode | ON | #开启GTID模块
| gtid_owned | |
| gtid_purged | |
+--------------------------+-------+
```
**注主库从库都需要开启GTID否则在做主从复制的时候就会报错因为slave可能变成master**
```sql
[root@mysql-db02 ~]# mysql -uroot -p123456
mysql> change master to
master_host='192.168.88.10',
master_user='slave',
master_password='123456',
master_auto_position=1;
#如果GTID没有开的话
ERROR 1777 (HY000): CHANGE MASTER TO MASTER_AUTO_POSITION = 1 can only be executed when @@GLOBAL.GTID_MODE = ON.
```
配置主从复制
```shell
[root@mysql-db02 ~]# mysql -uroot -p123456
#登录数据库
mysql> change master to
#配置复制主机信息
-> master_host='10.0.0.51',
#主库IP
-> master_user='rep',
#主库复制用户
-> master_password='123456',
#主库复制用户的密码
-> master_auto_position=1;
#GTID位置点
mysql> start slave;
#开启slave
mysql> show slave status\G
#查看slave状态
*************************** 1. row ***************************
Slave_IO_State: Waiting for master to send event
Master_Host: 10.0.0.51
Master_User: rep
Master_Port: 3306
Connect_Retry: 60
Master_Log_File: mysql-bin.000003
Read_Master_Log_Pos: 403
Relay_Log_File: mysql-db02-relay-bin.000002
Relay_Log_Pos: 613
Relay_Master_Log_File: mysql-bin.000003
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Replicate_Do_DB:
Replicate_Ignore_DB:
Replicate_Do_Table:
Replicate_Ignore_Table:
Replicate_Wild_Do_Table:
Replicate_Wild_Ignore_Table:
Last_Errno: 0
Last_Error:
Skip_Counter: 0
Exec_Master_Log_Pos: 403
Relay_Log_Space: 822
Until_Condition: None
```
- 从库设置
```shell
[root@mysql-db02 ~]# mysql -uroot -p123456
#登录从库
mysql> set global relay_log_purge = 0;
#禁用自动删除relay log 功能
mysql> set global read_only=1;
#设置只读
[root@mysql-db02 ~]# vim /etc/my.cnf
#编辑配置文件
[mysqld]
#在mysqld标签下添加
relay_log_purge = 0
#禁用自动删除relay log 永久生效
```
## 17.5 部署MHA
环境准备(所有节点)
下载MHA工具包
```bash
[root@localhost ~]# wget "https://download.s21i.faiusr.com/23126342/0/0/ABUIABBPGAAg3OHUiAYolpPt7AQ.zip?f=mysql-master-ha.zip&v=1628778716" -O mysql-master-ha.zip
```
```shell
[root@localhost ~]# mkdir /application/tools
[root@mysql-db01 ~]# unzip mysql-master-ha.zip -d /application/tools/
[root@mysql-db01 ~]# cd /application/tools/mysql-master-ha/
#进入安装包存放目录
[root@mysql-db01 tools]# ll
mha4mysql-manager-0.56-0.el6.noarch.rpm
mha4mysql-manager-0.56.tar.gz
mha4mysql-node-0.56-0.el6.noarch.rpm
mha4mysql-node-0.56.tar.gz
[root@mysql-db01 ~]# yum install perl-DBD-MySQL -y
#安装依赖包
[root@mysql-db01 tools]# yum install -y mha4mysql-node-0.56-0.el6.noarch.rpm
Preparing... ########################################### [100%]
1:mha4mysql-node ########################################### [100%]
#安装node包所有节点都要安装node包
[root@mysql-db01 tools]# mysql -uroot -p123456
#登录数据库,主库,从库会自动同步
mysql> grant all privileges on *.* to mha@'192.168.88.%' identified by 'mha';
#添加mha管理账号
mysql> select user,host from mysql.user;
#查看是否添加成功
mysql> select user,host from mysql.user;
#主库上创建,从库会自动复制(在从库上查看)
```
- 命令软连接(所有节点)
```shell
[root@mysql-db01 ~]# ln -s /application/mysql/bin/mysqlbinlog /usr/bin/mysqlbinlog
[root@mysql-db01 ~]# ln -s /application/mysql/bin/mysql /usr/bin/mysql
#如果不创建命令软连接检测mha复制情况的时候会报错写入环境变量
```
- 部署管理节点mha-manager:mysql-db03最好用第四个节点安装mha-manager
```shell
[root@mysql-db03 ~]# yum -y install epel-release
#使用epel源
[root@mysql-db03 ~]# yum install -y perl-Config-Tiny epel-release perl-Log-Dispatch perl-Parallel-ForkManager perl-Time-HiRes
#安装manager依赖包
[root@mysql-db03 tools]# yum install -y mha4mysql-manager-0.56-0.el6.noarch.rpm
Preparing... ########################################### [100%]
1:mha4mysql-manager ########################################### [100%]
#安装manager包
```
- 编辑配置文件
```shell
[root@mysql-db03 ~]# mkdir -p /etc/mha
#创建配置文件目录
[root@mysql-db03 ~]# mkdir -p /var/log/mha/app1
#创建日志目录
[root@mysql-db03 ~]# vim /etc/mha/app1.cnf
#编辑mha配置文件
[server default]
manager_log=/var/log/mha/app1/manager
manager_workdir=/var/log/mha/app1
master_binlog_dir=/application/mysql/data
user=mha
password=mha
ping_interval=2
repl_password=123456
repl_user=rep
ssh_user=root
[server1]
hostname=10.0.0.51
port=3306
[server2]
candidate_master=1
check_repl_delay=0
hostname=10.0.0.52
port=3306
[server3]
hostname=10.0.0.53
port=3306
```
配置文件详解
```shell
[server default]
manager_workdir=/var/log/masterha/app1
#设置manager的工作目录
manager_log=/var/log/masterha/app1/manager.log
#设置manager的日志
master_binlog_dir=/data/mysql
#设置master 保存binlog的位置以便MHA可以找到master的日志我这里的也就是mysql的数据目录
master_ip_failover_script= /usr/local/bin/master_ip_failover
#设置自动failover时候的切换脚本
master_ip_online_change_script= /usr/local/bin/master_ip_online_change
#设置手动切换时候的切换脚本
password=123456
#设置mysql中root用户的密码这个密码是前文中创建监控用户的那个密码
user=root
#设置监控用户root
ping_interval=1
#设置监控主库发送ping包的时间间隔尝试三次没有回应的时候自动进行failover
remote_workdir=/tmp
#设置远端mysql在发生切换时binlog的保存位置
repl_password=123456
#设置复制用户的密码
repl_user=rep
#设置复制环境中的复制用户名
report_script=/usr/local/send_report
#设置发生切换后发送的报警的脚本
#一旦MHA到server02的监控之间出现问题MHA Manager将会尝试从server03登录到server02
secondary_check_script= /usr/local/bin/masterha_secondary_check -s server03 -s server02 --user=root --master_host=server02 --master_ip=192.168.0.50 --master_port=3306
shutdown_script=""
#设置故障发生后关闭故障主机脚本(该脚本的主要作用是关闭主机防止发生脑裂,这里没有使用)
ssh_user=root
#设置ssh的登录用户名
[server1]
hostname=10.0.0.51
port=3306
[server2]
hostname=10.0.0.52
port=3306
candidate_master=1
# 设置为候选master如果设置该参数以后发生主从切换以后将会将此从库提升为主库即使这个主库不是集群中事件最新的slave。
check_repl_delay=0
# 默认情况下如果一个slave落后master 100M的relay logs的话MHA将不会选择该slave作为一个新的master因为对于这个slave的恢复需要花费很长时间通过设置check_repl_delay=0,MHA触发切换在选择一个新的master的时候将会忽略复制延时这个参数对于设置了candidate_master=1的主机非常有用因为这个候选主在切换的过程中一定是新的master
```
- 配置ssh信任所有节点
```shell
[root@mysql-db01 ~]# ssh-keygen -t dsa -P '' -f ~/.ssh/id_dsa >/dev/null 2>&1
#创建秘钥对
[root@mysql-db01 ~]# ssh-copy-id -i /root/.ssh/id_dsa.pub root@192.168.88.10
[root@mysql-db01 ~]# ssh-copy-id -i /root/.ssh/id_dsa.pub root@192.168.88.20
[root@mysql-db01 ~]# ssh-copy-id -i /root/.ssh/id_dsa.pub root@192.168.88.30
#发送公钥,包括自己
```
- 启动测试
```shell
[root@mysql-db03 ~]# masterha_check_ssh --conf=/etc/mha/app1.cnf
#测试ssh
#看到如下字样,则测试成功
Tue Mar 7 01:03:33 2017 - [info] All SSH connection tests passed successfully.
[root@mysql-db03 ~]# masterha_check_repl --conf=/etc/mha/app1.cnf
#测试复制
#看到如下字样,则测试成功
#若不在slave库上创建用户会失败按理说应该slave会同步master的库但创建slave用户是创建主从关系前
#mysql> grant replication slave on *.* to rep@'10.0.0.%' identified by '123456';
MySQL Replication Health is OK.
```
- 启动MHA
```shell
[root@mysql-db03 ~]# nohup masterha_manager --conf=/etc/mha/app1.cnf --remove_dead_master_conf --ignore_last_failover < /dev/null > /var/log/mha/app1/manager.log 2>&1 &
#启动
[root@mysql-db03 ~]# masterha_manager --conf=/etc/mha/app1.cnf --remove_dead_master_conf --ignore_last_failover --shutdown
#关闭
[root@mysql-db03 ~]# masterha_check_status --conf=/etc/mha/app1.cnf
#查看mha是否运行正常正常会显示master的IP
```
- 切换master测试
```shell
[root@mysql-db02 ~]# mysql -uroot -p123456
#登录数据库db02
mysql> show slave status\G
#检查复制情况
*************************** 1. row ***************************
Slave_IO_State: Waiting for master to send event
Master_Host: 10.0.0.51
Master_User: rep
Master_Port: 3306
Connect_Retry: 60
Master_Log_File: mysql-bin.000006
Read_Master_Log_Pos: 191
Relay_Log_File: mysql-db02-relay-bin.000002
Relay_Log_Pos: 361
Relay_Master_Log_File: mysql-bin.000006
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
[root@mysql-db03 ~]# mysql -uroot -p123456
#登录数据库db03
mysql> show slave status\G
#检查复制情况
*************************** 1. row ***************************
Slave_IO_State: Waiting for master to send event
Master_Host: 10.0.0.51
Master_User: rep
Master_Port: 3306
Connect_Retry: 60
Master_Log_File: mysql-bin.000006
Read_Master_Log_Pos: 191
Relay_Log_File: mysql-db03-relay-bin.000002
Relay_Log_Pos: 361
Relay_Master_Log_File: mysql-bin.000006
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
[root@mysql-db01 ~]# /etc/init.d/mysqld stop
#停掉主库
Shutting down MySQL..... SUCCESS!
[root@mysql-db02 ~]# mysql -uroot -p123456
#登录数据库db02
mysql> show slave status\G
#查看slave状态
Empty set (0.00 sec)
#db02的slave已经为空
[root@mysql-db03 ~]# mysql -uroot -p123456
#登录数据库db03
mysql> show slave status\G
#查看slave状态
*************************** 1. row ***************************
Slave_IO_State: Waiting for master to send event
Master_Host: 10.0.0.52
Master_User: rep
Master_Port: 3306
Connect_Retry: 60
Master_Log_File: mysql-bin.000006
Read_Master_Log_Pos: 191
Relay_Log_File: mysql-db03-relay-bin.000002
Relay_Log_Pos: 361
Relay_Master_Log_File: mysql-bin.000006
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
```
此时停掉主库后再开启db01db01正常了只能通过**手动方式以从库身份**加入
```sql
[root@mysql-db01 ~]# mysql -uroot -123456
change master to
master_host='192.168.88.136',
master_user='slave',
master_password='123456',
master_auto_position=1;
-> start slave;
```
## 17.6 配置vIP漂移
- VIP漂移的两种方式
- 通过keepalived的方式管理虚拟IP的漂移与后面Nginx负载均衡有关
- 通过MHA自带脚本方式管理虚拟IP的漂移
- MHA脚本方式
- 修改配置文件failover文件https://github.com/yoshinorim/mha4mysql-manager/blob/master/samples/scripts/master_ip_failover
```shell
[root@mysql-db03 ~]# vim /etc/mha/app1.cnf
#编辑配置文件
[server default]
#在[server default]标签下添加
master_ip_failover_script=/etc/mha/master_ip_failover
#使用MHA自带脚本在下载的mha文件mysql-master-ha.zip解压后的文件里
#tar xzvf mha4mysql-manager-0.56.tar.gz
#cd mha4mysql-manager-0.56/sample/script
#master-ip-failover文件就是自带的
#编辑脚本该文件从wget而来
[root@mysql-db03 ~]# vim /etc/mha/master_ip_failover
#根据配置文件中脚本路径编辑
my $vip = '10.0.0.55/24';
my $key = '0';
#网卡名要改对可能是ens33
my $ssh_start_vip = "/sbin/ifconfig ens33:$key $vip";
my $ssh_stop_vip = "/sbin/ifconfig ens33:$key down";
#修改以下几行内容
[root@mysql-db03 ~]# chmod +x /etc/mha/master_ip_failover
#添加执行权限否则mha无法启动
[root@mysql-db03 ~]# yum install net-tools
#安装IFconfig每台设备都要安装否则脚本执行失败
* 手动绑定vIP假设db01是master
[root@mysql-db01 ~]# ifconfig ens33:0 192.168.88.88/24
#绑定vip第一次要在master上手工配置后面不需要了
[root@mysql-db01 ~]# ip a |grep eth0
#查看vip
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
inet 10.0.0.51/24 brd 10.0.0.255 scope global eth0
inet 10.0.0.55/24 brd 10.0.0.255 scope global secondary eth0:0
*安装dos2unix因为从wget获取的master_ip_failover在windows下编辑换行符与linux不一样需要转换
[root@mysql-db03 mha]# yum install dos2unix
[root@mysql-db03 mha]# dos2unix master_ip_failover
*重启mha
[root@mysql-db03 ~]#masterha_manager --conf=/etc/mha/app1.cnf --remove_dead_master_conf --ignore_last_failover --shutdown
#关闭
[root@mysql-db03 ~]# nohup masterha_manager --conf=/etc/mha/app1.cnf --remove_dead_master_conf --ignore_last_failover < /dev/null > /var/log/mha/app1/manager.log 2>&1 &
#启动
[root@mysql-db03 ~]# masterha_check_status --conf=/etc/mha/app1.cnf
#查看mha是否运行正常正常会显示master的IP
* 测试ip漂移
#登录db02
[root@mysql-db02 ~]# mysql -uroot -p123456
#查看slave信息
mysql> show slave status\G
*************************** 1. row ***************************
Slave_IO_State: Waiting for master to send event
Master_Host: 10.0.0.51
Master_User: rep
Master_Port: 3306
Connect_Retry: 60
Master_Log_File: mysql-bin.000007
Read_Master_Log_Pos: 191
Relay_Log_File: mysql-db02-relay-bin.000002
Relay_Log_Pos: 361
Relay_Master_Log_File: mysql-bin.000007
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
#停掉主库
[root@mysql-db01 ~]# /etc/init.d/mysqld stop
Shutting down MySQL..... SUCCESS!
#在db03上查看从库slave信息
mysql> show slave status\G
*************************** 1. row ***************************
Slave_IO_State: Waiting for master to send event
Master_Host: 10.0.0.52
Master_User: rep
Master_Port: 3306
Connect_Retry: 60
Master_Log_File: mysql-bin.000006
Read_Master_Log_Pos: 191
Relay_Log_File: mysql-db03-relay-bin.000002
Relay_Log_Pos: 361
Relay_Master_Log_File: mysql-bin.000006
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
#在db01上查看vip信息
[root@mysql-db01 ~]# ip a |grep eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
inet 10.0.0.51/24 brd 10.0.0.255 scope global eth0
#在db02上查看vip信息
[root@mysql-db02 ~]# ip a |grep eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
inet 10.0.0.52/24 brd 10.0.0.255 scope global eth0
inet 10.0.0.55/24 brd 10.0.0.255 scope global secondary eth0:0
```