Fork me on GitHub

MySQL 基础知识梳理

第 1 章 MySQL 常见安装方式

MySQL 的安装方式有多种,但是对于不同场景,会有最适合该场景的 MySQL 安装方式,下面就介绍一下 MySQL 常见的安装方法,包括 rpm 安装,yum 安装,通用二进制安装以及源码编译安装,以 CentOS6.9 操作系统为例。

一、rpm 安装

安装速度较快,通常适用于企业中大规模部署 mysql,安装步骤如下:

(1)首先下载 MySQL 的 rpm 安装包, 如下:

客户端:http://dev.mysql.com/get/Down…

服务端:http://dev.mysql.com/get/Down…

(2)下载完成之后,上传至服务器的指定软件目录下,比如:/home/software;

(3)首先查看主机上是否已经安装过 mysql,使用如下命令查看:

[root@WB-BLOG ~]# rpm -qa | grep -i mysql*

(4)如果存在,需要先卸载原有 mysql,使用如下命令:

[root@WB-BLOG ~]# rpm -e --nodeps mysql-libs

注:–nodeps 表示无依赖卸载 mysql-libs,防止卸载依赖的库而导致后续安装出错

(5)添加 mysql 用户:

[root@WB-BLOG software]# useradd mysql -s /sbin/nologin -M

(6)卸载完成之后,开始安装,首先安装服务端,然后再安装客户端:

服务端安装:

[root@WB-BLOG ~]# rpm -ivh MySQL-server-5.6.32-1.linux_glibc2.5.x86_64.rpm

注:上述命令执行完毕之后,会自动初始化数据库,数据目录默认在 /var/lib/mysql 下,查看控制台实时日志中是否有 ERROR 信息,如果无 ERROR 信息,表示服务端安装完成,Warning 信息可以忽略。安装完成之后,会在 /root/.mysql_secret 文件中生成 mysql 的默认密码,可以通过 cat 命令查看即可:cat /root/.mysql_secret

客户端安装:

[root@WB-BLOG ~]# rpm -ivh MySQL-client-5.6.32-1.linux_glibc2.5.x86_64.rpm

注:安装客户端的目的是可以再服务器上使用到 mysql 的一些客户端连接命令,比如:mysql 命令,如果确定不在服务器上操作 mysql,可以不安装此客户端。

(7)安装完成之后,添加 mysql 的配置文件 my.cnf,对于 mysql,改配置文件有一个加载顺序,mysql 启动的时候会按照这个顺序去加载,顺序为:/etc/my.cnf,basedir/my.cnf,datadir/my.cnf,~/.my.cnf

编辑 /etc/my.cnf 配置文件,写入如下内容:

[mysql]
socket = /tmp/mysql.sock
[mysqld]
port = 3306
socket = /tmp/mysql.sock
character-set-server = UTF8
innodb_file_per_table = 1
innodb_flush_log_at_trx_commit = 2

(8)编辑完配置文件之后,保存。然后启动 mysql 数据库,如下:

[root@WB-BLOG ~]# service mysql start

(9)启动之后,使用如下 mysql 命令行工具登陆 mysql,默认密码为 /root/.mysql_secret 中生成的,登陆 mysql 之后,完成如下的初始工作:

[root@WB-BLOG mysql]# mysql -uroot -pBcbwbAXM_kUL8lpE -h127.0.0.1 -P3306

a. 修改 root 密码,有两种方法:

方法一:在 shell 命令行中使用”mysqladmin”命令修改

[root@WB-BLOG mysql]# mysqladmin -uroot -pBcbwbAXM_kUL8lpE password 'root'

注:修改密码的时候需要使用 password 函数,而且密码尽可能设置复杂一些;

b. 修改远程连接权限:

mysql> select user,host,password from mysql.user; #查看当前已有用户及权限
mysql> update user set host = '192.168.0.%' where host = 'wb-blog';

注:”set host = ‘192.168.0.%’”表示只允许 192.168.0 网段内的主机使用’root’用户远程连接,此处也可以指定 ip 段或者对所有用户开放,可根据实际场景配置

mysql> flush privileges; #刷新权限,让 mysql 重读权限表

c. 删除多余及不安全的用户:

mysql> drop user 'root'@'::1';   #该用户为 ipv6 的用户,暂时没用,可以删掉

d. 删除无用的数据库:

mysql> drop database test;   #该数据库在初始化的时候是默认添加的,无用,可删除

至此,rpm 方式的 MySQL 安装及初始化完毕。

二、通用二进制安装

通用二进制安装的优点就是可以同时在一台主机上安装多个不同版本的 mysql,而且在安装完成之后包括了一些常用的库文件,安装步骤如下:

(1)首先下载通用二进制安装包,下载地址为:https://dev.mysql.com/get/Dow…

(2)使用 yum 命令安装 mysql 的依赖库,mysql 主要有如下两个依赖库:

[root@WB-BLOG mysql]# yum install -y ncurses-devel libaio-devel

注:如果没有网络的情况下,使用 yum 安装会提示错误。此时可以下载依赖库所对应的 rpm 包上传至服务器完成安装;或者还可以使用 iso 镜像文件搭建私有的 yum 源,后面写文章介绍如何在局域网或者本地搭建及配置私有得的 yum 源。

(3)安装完成之后,添加 mysql 用户:

[root@WB-BLOG ~]# useradd mysql -s /sbin/nologin -M

(4)解压 mysql 通用二进制安装包,并重命名目录:

[root@WB-BLOG home]# tar xf mysql-5.6.40-linux-glibc2.12-x86_64.tar.gz -C /usr/local/
[root@WB-BLOG home]# cd /usr/local/
[root@WB-BLOG local]# mv mysql-5.6.40-linux-glibc2.12-x86_64.tar.gz/ mysql-5.6.40

(5)创建 mysql 的数据目录,并授权 mysql 用户。注意,数据目录最好别放在系统盘上,如果数据量后期比较大的话,最好事先选择一个空间比较大的数据盘作为 mysql 的存储目录,方便后期数据备份及迁移

[root@WB-BLOG local]# mkdir -pv /mysql_data/
[root@WB-BLOG local]# chown -R mysql.mysql /mysql_data

(6)编辑 mysql 的配置文件 my.cnf,可以从 mysql 解压目录中拷贝一份到 /etc/my.cnf 下,并修改,在配置文件中指定 mysql 的一些配置参数,如下:

[mysql]
socket = /mysql_data/mysql.sock
[mysqld]
basedir = /usr/local/mysql-5.6.40
datadir = /mysql_data/
character-set-server = UTF8
port = 3306
server_id = 3   #后期做 mysql 主从使用,可暂不配置
socket = /mysql_data/mysql.sock
innodb_file_per_table = 1
innodb_flush_log_at_trx_commit = 2

(7)初始化 mysql 数据库:

[root@WB-BLOG local]# cd /usr/local/mysql-5.6.39/
[root@WB-BLOG mysql-5.6.39]# ./scripts/mysql_install_db --basedir=/usr/local/mysql-5.6.39 --datadir=/mysql_data --user=mysql

注意:看到有如下所示的两个单行的 OK 字样,表示初始化成功.

Installing MySQL system tables...
OK
Filling help tables...
OK

(8)拷贝 mysql 的启动脚本文件至 /etc/init.d/ 目录下,并重命名为 mysqld,授予其可执行权限:

[root@WB-BLOG mysql-5.6.39]# cp /usr/local/mysql-5.6.40/support-files/mysql.server /etc/init.d/mysqld
[root@WB-BLOG mysql-5.6.39]# chmod +x /etc/init.d/mysqld

(9)修改 mysql 启动脚本中的默认安装路径:

[root@WB-BLOG mysql-5.6.39]# sed -i "s#/usr/local/mysql#/usr/local/mysql-5.6.40#g" /etc/init.d/mysqld

(10)将 mysqld 添加至系统服务,并设置自动启动:

[root@WB-BLOG mysql-5.6.39]# chkconfig --level 2345 mysqld on  #配置
[root@WB-BLOG mysql-5.6.39]# chkconfig --list mysqld   #查看

(11)使用跳过授权表方式启动 mysql 数据库,并登陆 mysql,设置密码,然后完成 mysql 使用前的初始化准备工作:

[root@WB-BLOG mysql-5.6.39]# /usr/local/mysql-5.6.39/bin/mysqld_safe --defaults-file=/etc/my.cnf --skip-grant-tables &
[root@WB-BLOG mysql-5.6.39]# mysql -uroot -p  #然后两次回车
mysql> update mysql.user set password = password('root') where user = 'root';  #修改密码:
mysql> delete from mysql.user where host = '::1'; #删除多余用户
mysql> flush privileges;   #重读权限表
mysql> drop database test;   #清理多余数据库

(12)退出 mysql 命令行,使用 mysqladmin 命令关闭数据库,并重新启动,使数据库再次使用权限认证:

mysql> \q
[root@WB-BLOG mysql-5.6.39]# ./bin/mysqladmin -uroot -proot shutdown  #需要使用刚才修改的密码
[root@WB-BLOG mysql-5.6.39]# service mysqld start

(13)配置 mysql 的环境变量,方便使用客户端连接:

[root@WB-BLOG mysql-5.6.39]# vim /etc/profile
添加如下内容:
MYSQL_HOME=/usr/local/mysql-5.6.39
PATH=$PATH:$JAVA_HOME/bin:$MYSQL_HOME/bin
[root@WB-BLOG mysql-5.6.39]# source /etc/profile# 使配置文件生效

至此,通用二进制方式的 mysql 安装及初始化完毕。

三、源码编译安装 mysql

该方式安装过程比较慢,机器性能不好的情况下,大约需要 30 分钟左右,通常适用于 mysql 定制化的安装,比如需要加入一些第三方的插件及依赖库等,安装方法如下:

(1)源码安装需要使用到 cmake,首先下载 cmake 和 mysql 的源码,上传至服务器:

cmake 下载地址:https://cmake.org/files/v2.8/…

mysql 源码下载地址:https://dev.mysql.com/get/Dow…

(2)使用 yum 方式安装 gcc 和 gcc-c++,否则编译 cmake 和 mysql 的时候会报错,如下:

[root@WB-BLOG mysql-5.6.39]# yum install -y gcc gcc-c++

(3)解压 cmake,并进入 cmake 软件目录:

[root@WB-BLOG software]# tar xf cmake-2.8.8.tar.gz
[root@WB-BLOG software]# cd cmake-2.8.8
[root@WB-BLOG cmake-2.8.8]# ./configure
[root@WB-BLOG cmake-2.8.8]# gmake
[root@WB-BLOG cmake-2.8.8]# gmake install

(4)安装 mysql 的依赖库:ncurses-devel

[root@WB-BLOG software]# yum install -y ncurses-devel

(5)解压 mysql:

[root@WB-BLOG software]# tar xf mysql-5.6.35.tar.gz && cd mysql-5.6.35
[root@WB-BLOG software]# mv mysql-5.6.35 /usr/local

(6)创建 mysql 组和 mysql 用户:

[root@WB-BLOG mysql-5.6.35]# groupadd mysql
[root@WB-BLOG mysql-5.6.35]# useradd mysql -s /sbin/nologin -M -g mysql

(7)创建 mysql 的数据目录并授权:

[root@WB-BLOG mysql-5.6.35]# mkdir -pv /mysql_data/
[root@WB-BLOG mysql-5.6.35]# chown -R mysql.mysql /mysql_data/

(8)使用 cmake 编译 mysql,如下的编译选项为最基本的,更多选项可以参考官方文档:

[root@WB-BLOG mysql-5.6.35]# cmake . -DCMAKE_INSTALL_PREFIX=/usr/local/mysql-5.6.35 -DMYSQL_DATADIR=/mysql_data/ -DMYSQL_UNIX_ADDR=/mysql_data/mysql.sock -DDEFAULT_CHARSET=utf8 -DDEFAULT_COLLATION=utf8_general_ci -DEXTRA_CHARSETS=gbk,gb2312,utf8,ascii -DENABLED_LOCAL_INFILE=ON -DWITH_INNOBASE_STORAGE_ENGINE=1 -DWITH_FEDERATED_STORAGE_ENGINE=1 -DWITH_BLACKHOLE_STORAGE_ENGINE=1 -DWITHOUT_EXAMPLE_STORAGE_ENGINE=1 -DWITHOUT_PARTITION_STORAGE_ENGINE=0 -DWITH_FAST_MUTEXES=1 -DWITH_ZLIB=bundled -DENABLED_LOCAL_INFILE=1 -DWITH_READLINE=1 -DWITH_EMBEDDED_SERVER=1 -DWITH_DEBUG=0 -DWITH_PARTITION_STORAGE_ENGINE=1

…等待编译…

[root@WB-BLOG mysql-5.6.35]# make
[root@WB-BLOG mysql-5.6.35]# make install

(9)如果上述过程正常结束,表示 mysql 编译安装成功,否则停下来检查编译过程中的错误,通常编译期的错误多数为缺少类库导致,可安装相关类库后重新编译。然后编辑 mysql 的配置文件 my.cnf,写入如下内容:

[mysql]
socket = /mysql_data/mysql.sock
[mysqld]
character-set-server = UTF8
basedir = /usr/local/mysql-5.6.35
datadir = /mysql_data
port = 3306
server_id = 3
socket = /mysql_data/mysql.sock
innodb_file_per_table = 1
skip-name-resolve
innodb_flush_log_at_trx_commit = 2

(10)初始化 mysql 数据库:

[root@WB-BLOG mysql-5.6.35]# cd /usr/local/mysql-5.6.35/
[root@WB-BLOG mysql-5.6.35]# ./scripts/mysql_install_db --basedir=/usr/local/mysql-5.6.35 --datadir=/mysql_data/ --user=mysql

(11)看到如下的字样,表示 mysql 数据库初始化成功:

Installing MySQL system tables...
OK
Filling help tables...
OK

(12)拷贝启动脚本到 /etc/init.d 目录下:

[root@WB-BLOG mysql-5.6.35]# cp /usr/local/mysql-5.6.35/support-files/mysql.server.sh /etc/init.d/mysqld
[root@WB-BLOG mysql-5.6.35]# vim /etc/init.d/mysqld  #编辑脚本,修改 basedir 和 datadir 目录为安装的目录

找到 basedir 和 datadir,然后修改为如下内容,并保存退出:

basedir=/usr/local/mysql-5.6.35
datadir=/mysql_data/

(13)授权脚本的执行权限,使用跳过授权表的方式启动,然后修改密码,删除多余的用户和数据库,并配置环境变量,具体步骤同“通用二进制安装中的(11),(12),(13)”,此处后续步骤略。

[root@WB-BLOG mysql-5.6.35]# chmod +x /etc/init.d/mysqld
...

至此,源码编译安装 mysql 介绍完毕。

第 2 章 MySQL 基本操作之—建库,建表,删库,删表

MySQL 数据库环境搭建好之后,就一块开始 MySQL 的学习之旅吧?本次主要介绍 MySQL 的基本操作之”建库,建表,删库,删表”。

一、基本操作之建库(非图形界面工具操作)

1、建库之前,首先需要连接到 MySQL 数据库,常见的连接工具工具为:”mysql”客户端命令,基本用法如下:

[root@WB-BLOG ~]# mysql <options>

options 中常用的选项有:

-u: 指定连接数据库实例所使用的用户名

-p: 指定连接数据库实例所使用的密码

-h: 指定需要连接的目标数据实例所在主机的 ip 地址或者域名,如果是服务器本地连接,可省略

-P: 指定需要连接的目标数据库实例所监听的 tcp 端口,注意是大写的 P。如果使用的是默认端口,此选项可省略

…该命令还有其他很多不太常用的选项,可以自行使用”mysql –help”命令查看,此处略。

示例:使用用户名为 root 密码为 root 的用户连接 192.168.0.10 服务器上端口为 3306 的数据库实例:

[root@WB-BLOG ~]# mysql -uroot -proot -h127.0.0.1 -P3306

注:除了 -p 指定的密码的选项之外,其余的参数和对应的值之间可以不加空格,也可加空格,但是 -p 参数和密码之间不可有空格,否则会被认为另外一个参数,需要重新输入密码。

2、进入 mysql 命令行之后,就可以创建数据库了,首先查看当前已经存在的数据库,使用 show 命令,show 命令的使用选项如下:

mysql> show <options>

options 常见的选项如下:

tables: 查看当前库下的所有表

databases: 查看当前实例下的所有数据库,不一定是所有库,和登陆用户的权限有关

create database db_name:show 后接 create database 命令,表示查看名称为 db_name 的数据库的创建过程

create database table_name:show 后接 create table 命令,表示查看名称为 table_name 的表的创建过程

warnings: 查看 sql 执行的警告信息

events: 查看当前数据库所对应的事件信息

binary logs | master logs: 查看当前数据库实例对应的二进制日志文件信息(bin_log_pos 和 bin_log_file)

status: 查看当前数据库实例的运行状态信息

index|indexes from table_name: 查看指定表中的索引信息

…其他不常用选项此处暂时略,后续再介绍。

示例:查看当前登录用户有权查看的所有数据库:

mysql> show databases;
+--------------------+
| Database   |
+--------------------+
| information_schema |
| mysql  |
| performance_schema |
| test   |
+--------------------+
4 rows in set (0.00 sec)

由于使用的是 root 用户登录,故可以看到所有数据库;如果用某个普通用户登录,只能看到该用户有权查看的所有库,具体权限管理后期介绍。

3、创建数据库,使用 create 命令,create 命令用法如下:

mysql> create <options>

options 常见选项如下:

database db_name [option]: 后接 database 关键字表示创建名称为 db_name 的数据库,option 选项中可以指定字符集

table table_name [option]: 后接 table 关键字表示创建名称为 table_name 的表,option 选项中可以指定字符集

index index_name on table_name(field): 后接 index 关键字表示在 table_name 表的 field 字段上创建名称为 index_name 的索引

procedure pro_name [option]: 后接 procedure 关键字表示在当前库的某个表上创建一个存储过程,option 选项表示具体的创建过程

…其他选项此处暂不介绍,后续再介绍。

示例:创建一个名称为 db_shop 的数据库,使用 UTF8 字符集,如下:

mysql> create database db_shop character set UTF8;
Query OK, 1 row affected (0.21 sec)
mysql> show databases;  #查看创建的数据库
+--------------------+
| Database   |
+--------------------+
| information_schema |
| db_shop|
| mysql  |
| performance_schema |
| test   |
+--------------------+
5 rows in set (0.00 sec)
mysql> show create database db_shop;  #查看 db_shop 数据库的创建过程
+----------+------------------------------------------------------------------+
| Database | Create Database  |
+----------+------------------------------------------------------------------+
| db_shop  | CREATE DATABASE `db_shop` /*!40100 DEFAULT CHARACTER SET utf8 */ |
+----------+------------------------------------------------------------------+
1 row in set (0.00 sec)

4. 创建表,在建表之前首先需要先选择数据库,使用 use 命令,用法如下:

mysql> use <db_name>
示例:在 db_shop 库中创建一个名称为 t_goods 的表,使用 UTF8 字符集,表中字段包括 id(bigint),goods_name(varchar(50)),goods_price(bigint)[金钱类可以使用字符串或者分来存储,此处使用分来存储],create_time(datetime),创建过程如下:

mysql> use db_shop;  #选库
mysql> create table t_goods(id bigint primary key auto_increment,goods_name varchar(50) not null default '',goods_price bigint,create_time datetime) default character set UTF8;  #建表
Query OK, 0 rows affected (0.76 sec)
mysql> show tables;  #查看表
+-------------------+
| Tables_in_db_shop |
+-------------------+
| t_goods   |
+-------------------+
1 row in set (0.00 sec)
mysql> show create table t_goods \G #查看创建库的过程,\G 表示按行展示
*************************** 1. row ***************************
   Table: t_goods
Create Table: CREATE TABLE `t_goods` (`id` bigint(20) NOT NULL AUTO_INCREMENT,
  `goods_name` varchar(50) NOT NULL DEFAULT '',
  `goods_price` bigint(20) DEFAULT NULL,
  `create_time` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1 row in set (0.00 sec)

注:MySQL 常用数据类型如下,详细介绍请查看官方手册,后期 sql 优化会重点对比讲解每种类型的优缺点:

数值型整型:tinyint,smallint,mediumint,int,bigint

浮点数类型:double,float,decimal

字符串类型:char,varchar,varbinary

长文本类型:text

二进制类型:binary,blob

日期类型:timestamp,datetime,time,date,year

枚举类型:enum

集合类型:set

5、删除表,使用”drop table”命令,用法如下:

mysql> drop table <table_name>

示例:删除名称为 t_goods 的表:

mysql> drop table t_goods;  #删除名称为 t_goods 的表
Query OK, 0 rows affected (0.16 sec)
mysql> show tables;   #查看当前库下的所有表
Empty set (0.00 sec)

6、删除数据库,使用”drop database”命令,用法如下:

mysql> drop database <db_name>;

示例:删除名称为 db_shop 的数据库:

mysql> drop database db_shop;
Query OK, 0 rows affected (0.00 sec)
mysql> show databases;
+--------------------+
| Database   |
+--------------------+
| information_schema |
| mysql  |
| performance_schema |
| test   |
+--------------------+
4 rows in set (0.01 sec)

7、退出 MySQL 的方法:

(1)方法 1:Ctrl+C

(2)方法 2:”quit”命令

(3)方法 3:\q

至此,数据库的基本操作之建库,建表,删库,删表操作介绍完毕,下一篇将介绍 MySQL 数据库的常见操作,包括 DDL(数据定义语言),DCL(数据控制语言),DML(数据操纵语言),DQL(数据查询语言)。

第 3 章 MySQL 基本操作之 -DDL,DML,DQL,DCL

MySQL 基本操作之 DDL(数据定义语言),DML(数据操纵语言),DQL(数据查询语言),DCL(数据控制语言)

一、DDL–数据定义语言

作用:数据定义语言主要用来定义数据库中的各类对象,包括用户、库、表、视图、索引、触发器、事件、存储过程和函数等。

常见的 DDL 操作的基本用法如下:

CREATE USER #创建用户

CREATE DATABASE #创建数据库

CREATE TABLE #创建表

CREATE VIEW #创建视图

CREATE INDEX #创建索引

CREATE TRIGGER #创建触发器

CREATE EVENT #创建事件

CREATE PROCEDURE #创建存储过程

CREATE FUNCTION #创建自定义函数

…其他不常用的 DDL(如:TABLESPACE)操作可自行查阅资料…

1、创建用户:

详细用法:CREATE USER ‘username‘@’[ip/domain/netmask]’

参数解释:

username: 表示登陆 MySQL 实例的用户名
[ip/domain/ip range]: 表示数据库实例允许的登陆 ip,域名或者 ip 段

示例:创建一个名称为 bingwang,登陆 ip 为 192.168.0.10 的用户:

mysql> CREATE USER 'bingwang'@'192.168.0.10';

2、创建数据库,示例如下:

详细用法:

CREATE DATABASE db_name;

示例如下:

# 创建一个名称为 test_db,字符集为 utf8 的数据库
mysql> CREATE DATABASE test_db DEFAULT CHARSET UTF8;

3、创建表:

详细用法:CREATE TABLE table_name;

示例如下:

# 创建一个名称为 t_test,字符集为 utf8,存储引擎为 InnoDB,字符校验集为 utf8_general_ci 的表:
mysql> CREATE TABLE t_test (
   id INT NOT NULL AUTO_INCREMENT,
   name VARCHAR(50),
   PRIMARY KEY(id)
   ) ENGINE = InnoDB DEFAUL CHARSET = UTF8 COLLATE = utf8_general_ci;

4、创建视图:

详细用法:

CREATE VIEW view_name as <SELECT phrase>;
参数解释: <SELECT phrase>: 查询语句

示例如下:

# 创建一个视图 t_view,用来查询 t_test 中的 ID 为 1 或者 2 的数据:
mysql> CREATE VIEW test_view AS SELECT * FROM t_test WHERE id IN (1,2);
#查看创建视图的过程:
mysql> SHOW CREATE VIEW test_view;

5、创建索引,有两种方法,CREATE 和 ALTER,下面先介绍一下 CREATE:
详细用法:

CREATE [UNIQUE] INDEX index_name ON table_name(field[num]) <OPTIONS>;

参数解释:

UNIQUE: 表示创建的索引类型为唯一索引,如果创建的为一般索引可以忽略该选项
table_name: 表名称
field: 表中的某个字段。num 为可选参数,如果 field 为字符创类型,表示给该字段的前 num 个字符创建索引
OPTIONS: 表示可选选项,可以指定索引使用的算法,比如:USING BTREE。不指定默认为 BTREE;

示例如下:

(1)给 t_test 表中的 name 字段添加一个唯一索引,使用 BTREE 作为其索引算法:

mysql> CREATE UNIQUE INDEX name_ind ON t_test(name) USING BTREE;
mysql> SHOW [INDEX/INDEXES] FROM t_test;  #查看 t_test 表中的索引,[INDEX/INDEXES]两个关键字都可以

(2)给 t_test 表中的 name 字段的前 5 个字符创建一般索引,使用 BTREE 作为其索引算法:

mysql> CREATE INDEX name_index ON t_test(name(5));

关于索引的更多用法及优化在后面的文章中会详细讲解。

6、创建触发器:

详细用法:

CREATE TRIGGER trigger_name trigger_time trigger_event FOR EACH ROW 
BEGIN 
  trigger_stmt 
END;

示例:创建触发器内容稍多,此处先稍微提一下,后面专门章节介绍;

7、创建存储过程:

详细用法:

CREATE PROCEDURE procedure_name([proc_parameter[,...]])
BEGIN
  ... 存储过程体
END

示例:创建存储过程内容稍多,此处先稍微提一下,后面专门章节介绍;

8、创建自定义函数:

详细用法:

CREATE FUNCTION function_name([func_parameter[,...]])
RETURNS type
BEGIN
... 函数体
END

示例:创建自定义函数内容稍多,此处先稍微提一下,后面专门章节介绍;
至此,简单的 DDL 操作介绍完成。

二、DML–数据操纵语言

作用:用来操作数据库中的表对象,主要包括的操作有:INSERT,UPDATE,DELETE

常见的 DML 的基本操作方法如下:

# 给表中添加数据
INSERT INTO ...
#修改表中的数据
UPDATE table_name SET ...
#删除表中的数据
DELETE FROM table_name WHERE <condition>;
注:<condition>: 表示 DML 操作时的条件

1、向表中插入数据:

详细用法:

mysql> INSERT INTO table_name(field1,field2,[,...]) values(value1,value2),(value3,value4),...;

示例:向学生表中插入一条数据,name:’xiaohong’, age:24, gender:’M’ ,如下:

(1)创建表:

 mysql> CREATE TABLE student(
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) NOT NULL DEFAULT '',
age TINYINT,
gender ENUM('F','M')
 ) ENGINE = InnoDB DEFAULT CHARSET = UTF8;

(2)插入数据:

mysql> INSERT INTO student(name,age,gender) VALUES('xiaohong',24,'M');
Query OK, 1 row affected (0.09 sec)
mysql> SELECT * FROM student;
+----+----------+------+--------+
| id | name | age  | gender |
+----+----------+------+--------+
|  1 | xiaohong |   24 | M  |
+----+----------+------+--------+
1 row in set (0.37 sec)

注:主键如果自动递增,插入时可不用指定;

2、修改表中的数据:

详细用法:

UPDATE table_name SET field1 = value1, field2 = value2, ... ,  WHERE <condition>;

示例:将 student 表中 id 为 1 的记录中的 name 值修改为:”xiaohua”,如下:

mysql> UPDATE STUDENT SET name = 'xiaohua' WHERE id = 1;
Query OK, 1 row affected (0.67 sec)
Rows matched: 1  Changed: 1  Warnings: 0
mysql> SELECT * FROM student;
+----+---------+------+--------+
| id | name| age  | gender |
+----+---------+------+--------+
|  1 | xiaohua |   24 | M  |
+----+---------+------+--------+
1 row in set (0.00 sec)

注意:此处修改的时候,一定得注意加条件,否则整个表都会被改。讲个技巧:在 MySQL 的命令中执行操作的时候,可以在登录 MySQL 时添加 -U 选项,如果忘加条件,会被阻止执行 sql 语句。登录命令如下:

[root@WB-BLOG ~]# mysql -uroot -proot -U -h127.0.0.1 -P3306
mysql> UPDATE STUDENT SET name = 'hong';
ERROR 1175 (HY000): You are using safe update mode and you tried to update a table without a WHERE that uses a KEY column

可见:登录之后如果不加条件执行 UPDATE 语句,会被阻止;

3、删除表中的数据:

详细用法:

mysql> DELETE FROM table_name WHERE <condition>;

示例:删除 student 表中 id 为 1 的记录,如下:

mysql> DELETE FROM student WHERE id = 1;
Query OK, 1 row affected (0.37 sec)
mysql> SELECT * FROM student;
Empty set (0.00 sec)

注意:注意!注意!!再注意!!!,该操作非常危险,命令行中操作时,需要万分注意。可以使用登录时加 -U 参数的方式,防止忘加条件而删除所有数据,加了 -U 参数之后,如果不加条件,会被阻止,执行结果如下:

mysql> DELETE FROM student;
ERROR 1175 (HY000): You are using safe update mode and you tried to update a table without a WHERE that uses a KEY column

至此,DML 操作介绍完毕。

三、DQL–数据查询语言

作用:主要用来查看表中的数据,也是平时使用最多的操作,主要命令为:SELECT

基本用法如下:

mysql> SELECT fields FROM table_name WHERE <condition>;

注意事项:

fields: 表示要查询的字段列表,可以使用代替,但是在程序中最好别写,因为使用 * 一方面会降低 SQL 的查询效率,查询到一些用不到的字段;另一方面,使用一些 ORM 框架时,如果数据库中字段有变动,可能会立刻导致程序报错。

1、简单不加条件的单表查询:

用法:

mysql> SELECT * FROM table;

示例略。

2、单表中的条件查询:

常见的条件:>,>=,<,<= ,=,<>,!=,IN,NOT IN,LIKE,NOT LIKE,REGEXP

示例:

# 查询年龄大于 23 的记录
mysql> SELECT * FROM student WHERE age > 23;
#查询年龄大于等于 24 的记录,和上面 age>23 结果相同
mysql> SELECT * FROM student WHERE age >= 24;
#查询年龄小于 24 的记录
mysql> SELECT * FROM student WHERE age < 24;
#查询年龄小于等于 24 的记录
mysql> SELECT * FROM student WHERE age <= 24;
#查询姓名等于 xiaohong 的记录
mysql> SELECT * FROM student WHERE name = 'xiaohong';
#查询姓名不等于 xiaohong 的记录
mysql> SELECT * FROM student WHERE name <> 'xiaohong'; 
#查询姓名不等于 xiaohong 的记录
mysql> SELECT * FROM student WHERE name != 'xiaohong';
#查询姓名为 xiaohong 或者 xiaohui 的记录
mysql> SELECT * FROM student WHERE name in ('xiaohong','xiaohui');
#查询姓名不是 xiaohong 和 xiaohui 的记录等价于:where name != xiaohong and name != xiaohui
mysql> SELECT * FROM student WHERE name not in ('xiaohong','xiaohui');
#查询姓名以 xiao 开头的记录
mysql> SELECT * FROM student WHERE name like 'xiao%';
#查询姓名以 xiaohon 开头的记录,后面模糊匹配一位,如:xiaohong,xiaohoni
mysql> SELECT * FROM student WHERE name like 'xiaohon_';
#查询姓名中包含 ao 字符创的记录
mysql> SELECT * FROM student WHERE name like '%ao%';
#查询以 hong 结尾的记录
mysql> SELECT * FROM student WHERE name not like '%hong';
#使用正则表达式查询姓名以 xiao 开头的记录
mysql> SELECT * FROM student WHERE name REGEXP('^xiao');
#使用正则表达式查询姓名以 hong 结尾的记录
mysql> SELECT * FROM student WHERE name REGEXP('hong$');
#支持的其他复杂的正则表达式,请参阅资料:

正则表达式教程:http://www.runoob.com/regexp/…

注意:

(1)当某个字段上有索引时,使用上述的反向查询或者前模糊查询,如:<>,!=,NOT LIKE,NOT IN,LIKE “%test”,将会不走索引;

(2)查询中的潜在问题:如果某个字段在创建表结构的时候未设置非空,则使用 WHERE name!=”BING”的时候,将不会包含 name 为 NULL 的记录;

示例:查询 student 表中年龄大于”xiaohong”年龄的记录的数量:

mysql> SELECT COUNT(*) FROM student WHERE age > (SELECT age FROM student WHERE name = 'xiaohong');
+----------+
| COUNT(*) |
+----------+
|2 |
+----------+
1 row in set (0.46 sec)

3、分页查询:

用法:

mysql> SELECT * FROM table_name LIMIT start,num;

参数解释:

start: 开始位置,默认从 0 开始;

num: 偏移量,即:从开始位置向后查询的数据条数;

示例:查询 test 表中,第二页的数据,每页显示 10 条,如下:

mysql> SELECT * FROM student LIMIT 1,10;

4、使用 ORDER BY 对查询结果进行排序:

用法:

SELECT * FROM table_name <where condition> ORDER BY <field> ASC/DESC;

示例:从 student 表中查询出所有年龄大于 20 的学生记录,并且按照年龄 age 倒序排列,如下:

SELECT * FROM student WHERE age > 20 ORDER BY age DESC;

注意:如果在排序时 ORDER BY 之后没有添加 DESC 和 ASC 关键字,默认按照 ASC 升序排列;

5、使用 GROUP BY 对查询结果集进行分组

基本用法:

mysql> SELECT res FROM table_name <where condition> GROUP BY <field>;

示例:查询 student 表中男生和女生的数量:

mysql> SELECT gender,COUNT(*) FROM student GROUP BY gender;

6、使用 GROUP BY 之后,在使用 HAVING 完成分组之后的条件查询

基本用法:

SELECT res FROM table_name <where condition> GROUP BY <field> <having condition>;

示例:查询 student_course 表中有 3 门成绩大于等于 80 的学生学号

(1)创建测试表结构:

mysql> CREATE TABLE student_course(sno INT(11) NOT NULL,
cno INT(11) NOT NULL,
grade SMALLINT NOT NULL DEFAULT 0
)ENGINE = InnoDB DEFAULT CHARSET = UTF8;

(2)插入测试数据:

INSERT INTO student_course(sno,cno,grade) VALUES(1,100,79);
INSERT INTO student_course(sno,cno,grade) VALUES(1,101,89);
INSERT INTO student_course(sno,cno,grade) VALUES(1,102,87);
INSERT INTO student_course(sno,cno,grade) VALUES(1,103,99);
INSERT INTO student_course(sno,cno,grade) VALUES(2,100,90);
INSERT INTO student_course(sno,cno,grade) VALUES(2,101,80);
INSERT INTO student_course(sno,cno,grade) VALUES(2,102,77);
INSERT INTO student_course(sno,cno,grade) VALUES(2,103,79);
INSERT INTO student_course(sno,cno,grade) VALUES(3,100,89);
INSERT INTO student_course(sno,cno,grade) VALUES(3,101,90);
INSERT INTO student_course(sno,cno,grade) VALUES(3,102,83);
INSERT INTO student_course(sno,cno,grade) VALUES(3,103,91);

(3)查询:

mysql> SELECT sno,SUM(CASE WHEN grade > 80 THEN 1 ELSE 0 END) num FROM student_course GROUP BY sno HAVING num >= 3; 
+-----+------+
| sno | num  |
+-----+------+
|   1 |3 |
|   3 |4 |
+-----+------+
2 rows in set (0.45 sec)

四、DCL–数据控制语言

作用:用来授予或回收访问数据库的某种特权,并控制数据库操纵事务发生的时间及效果。

1、GRANT 授予用户权限:

基本用法:

mysql> GRANT priv_type ON <object_type> TO user <WITH {GRANT OPTION | resource_option} ...>;

示例:给用户 jerry 授予对 test_db 数据库的增删改查权限,允许该用户从 IP 为’192.168.0.10’的网络登录

(1)方法一:

mysql> GRANT INSERT,SELECT,UPDATE,DELETE ON test_db.* TO 'jerry'@'192.168.0.10' IDENTIFIED BY 'password' WITH GRANT OPTION;

(2)方法二:

mysql> CREATE USER 'jerry'@'192.168.0.10' IDENTIFIED BY 'password';
mysql> GRANT INSERT,SELECT,UPDATE,DELETE ON test_db.* TO 'jerry'@'192.168.0.10';

2、REVOKE 收回用户权限:

基本用法:

mysql> REVOKE priv_type ON <object_type> FROM 'jerry'@'192.168.0.10';

示例:收回用户对 test_db 库的删除权限:

mysql> REVOKE DELETE ON test_db.* FROM 'jerry'@'192.168.0.10';

3、查看给某个用户所授予的权限:

基本用法:

mysql> SHOW GRANTS FOR user;

示例:查询给‘jerry‘@’192.168.0.10’所授予的所有权限:

mysql> SHOW GRANTS FOR 'jerry'@'192.168.0.10';

4、查询可授予的所有权限,使用技巧:

(1)首先将某个库 (如:test_db) 的所有权限授予给用户‘jerry‘@’localhost’

mysql> GRANT ALL ON test_db.* TO 'jerry'@'localhost' IDENTIFIED BY 'jerry';

(2)收回某个权限,如:查询权限

mysql> REVOKE SELECT ON test_db.* FROM 'jerry'@'localhost';

(3)查看剩余权限,就可以查到除了查询权限之外的权限,再加上查询权限即可授予的所有权限

mysql> SHOW GRANTS FOR 'jerry'@'localhost';

至此,MySQL 的基本操作 DDL,DML,DQL,DCL 介绍完毕。

第 4 章 MySQL 数据库 DDL 之触发器应用

上面简单介绍了一下 MySQL 的基本操作之 DDL、DML、DQL、DCL,在 DDL 中简单提了一下触发器,存储过程和函数,本篇文章将详细介绍触发器!

1、触发器作用:简单来说,触发器就是绑定在某个表上的一个特定数据库对象,当在这个表上发生某种触发器所监听的操作时,将会触发某种动作。

2、触发器用法:

CREATE TRIGGER trigger_name trigger_event trigger_time ON table_name FOR EACH ROW
BEGIN
...trigger_statement... #触发器的逻辑实现
END

参数解释:
trigger_name: 触发器的名称
trigger_event: 触发的事件,包括:INSERT,UPDATE,DELETE
trigger_time: 触发的时间点,包括:BEFORE(事件之前触发),AFTER(事件之后触发)
table_name: 触发器所在表
trigger_statement: 触发器被触发之后,所执行的数据库操作逻辑,可以为单一的数据库操作,或者一系列数据库操作集合,也可以包含一些判断等处理逻辑;
注意:
(1)同一张表中不能同时存在两个类型一样的触发器;
(2)触发事件和触发时间点总共可以组成 3 组 6 种不同的触发器,分别为:(BEFORE INSERT,AFTER INSERT)、(BEFORE UPDATE,AFTER UPDATE)、(BEFORE DELETE,AFTER DELETE);
(3)触发事件:
① INSERT:在插入数据的时候触发,插入数据的动作包括 INSERT,LOAD DATA,REPLACE 操作,即:发生这三种操作时,都会触发 INSERT 类型的触发器;
② UPDATE:数据发生变更时触发,即:发生了 UPDATE 操作;
③ DELETE:从表中删除某一行的时候触发,即:发生了 DELETE 或者 REPLACE 操作;
(4)创建触发器的时候,由于在触发器的 trigger_statement 语句中有逻辑,而每个逻辑都会有结束符,默认为”;”,故需要在创建之前先定义定界符。防止 SQL 语句在执行之前被存储引擎(存储引擎:MySQL 数据库的插件,后续介绍)解析的时候碰见”;”而提前结束,提示语法错误。
3、示例:
(1)示例 1:现在 test_db 中有两个表,一个为员工信息表 t_emp,一个为部门统计表 t_dept_statis,他们的表结构分别如下所示:
员工信息表:
CREATE TABLE t_emp (
id INT(11) NOT NULL AUTO_INCREMENT,
name VARCHAR(50) NOT NULL DEFAULT ‘’, #员工姓名
age TINYINT(4) DEFAULT NULL, #年龄
gender ENUM(‘F’,’M’) DEFAULT NULL, #性别
dept_id INT, #部门编号
PRIMARY KEY (id)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=UTF8;
部门员工数量统计表:
CREATE TABLE t_dept_statis(
id INT PRIMARY KEY AUTO_INCREMENT,
emp_count INT, #员工数量,初始化为 0
dept_id INT, #部门编号
update_time DATETIME #更新时间
) ENGINE = InnoDB DEFAULT CHARSET = UTF8;
初始化的统计数据,插入两条统计数据,分别为编号为 1 和 2 的两个部门,初始化员工数量为 0:
mysql> INSERT INTO t_dept_statis(emp_count,dept_id,update_time) VALUES(0,1,NOW());
mysql> INSERT INTO t_dept_statis(emp_count,dept_id,update_time) VALUES(0,2,NOW());
需求:使用触发器实现每新增一条员工记录,部门信息统计表中就可以自动统计出员工数有变化的部门的员工总数量。这个需求可能不合适,但是完全可以说明触发器的用法:
mysql> \d $ #建立定界符,可以使用”DELIMITER $”,和”\d $”等价
mysql> CREATE TRIGGER dep_tri AFTER INSERT ON t_emp FOR EACH ROW
BEGIN
DECLARE num INT;
SET num = (SELECT emp_count FROM t_dept_statis WHERE dept_id = new.dept_id);
UPDATE t_dept_statis SET emp_count = num + 1 ,dept_id = new.dept_id, update_time = now() where dept_id = new.dept_id;
END$
mysql> \d ; #重新还原定界符为默认的定界符”;”

# 查看 t_emp 中的数据,此时是空的
mysql> SELECT FROM t_emp;
Empty set (0.00 sec)
查看 t_dept_statis 中的数据,此时有两条初始化数据,员工数量为 0
mysql> SELECT
FROM t_dept_statis;
+—-+———–+———+————-+
| id | emp_count | dept_id | update_time |
+—-+———–+———+————-+
| 1 | 0 | 1 | NULL |
| 2 | 0 | 2 | NULL |
+—-+———–+———+————-+
向 t_emp 中插入一条数据,然后查看 t_dept_statis 表,会发现,员工数量会自动统计
mysql> INSERT INTO t_emp(name,age,gender,dept_id) values(‘emp01’,23,’F’,1);
Query OK, 1 row affected (0.00 sec)
mysql> SELECT FROM t_dept_statis;
+—-+———–+———+———————+
| id | emp_count | dept_id | update_time |
+—-+———–+———+———————+
| 1 | 1 | 1 | 2018-05-14 22:51:15 |
| 2 | 0 | 2 | NULL |
+—-+———–+———+———————+
再次向 t_emp 中插入一条数据,然后查看 t_dept_statis 表,会发现,员工数量会再次统计
mysql> INSERT INTO t_emp(name,age,gender,dept_id) values(‘emp03’,26,’M’,2);
Query OK, 1 row affected (0.15 sec)
mysql> SELECT
FROM t_dept_statis;
+—-+———–+———+———————+
| id | emp_count | dept_id | update_time |
+—-+———–+———+———————+
| 1 | 1 | 1 | 2018-05-14 22:51:15 |
| 2 | 1 | 2 | 2018-05-14 22:51:30 |
+—-+———–+———+———————+
查看 t_emp 中的数据,会发现目前有两条记录,部门 1 和部门二中各有一条,统计表已经通过触发器实现了员工数量的自动统计:
mysql> SELECT * FROM t_emp;
+—-+——-+——+——–+———+
| id | name | age | gender | dept_id |
+—-+——-+——+——–+———+
| 1 | emp01 | 23 | F | 1 |
| 2 | emp03 | 26 | M | 2 |
+—-+——-+——+——–+———+
2 rows in set (0.00 sec)
(2)示例 2:在 test_db 中有一张用户表 t_user 和 t_user_bak,表结构相同,如下所示:
mysql> CREATE TABLE t_user(id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) NOT NULL DEFAULT ‘’, #用户名
age TINYINT NOT NULL DEFAULT 0, #年龄
create_time DATETIME NOT NULL #创建时间
) ENGINE = InnoDB DEFAULT CHARSET = UTF8;

mysql> CREATE TABLE t_user_bak(id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) NOT NULL DEFAULT ‘’, #用户名
age TINYINT NOT NULL DEFAULT 0, #年龄
create_time DATETIME NOT NULL #创建时间
) ENGINE = InnoDB DEFAULT CHARSET = UTF8;
创建测试数据,插入如下几条数据:
mysql> INSERT INTO t_user(name,age,create_time) VALUES(‘name01’,23,NOW());
mysql> INSERT INTO t_user(name,age,create_time) VALUES(‘name02’,25,NOW());

需求:如果 t_user 表中的数据被修改,则将修改前的数据先备份到 t_user_bak 表中,使用触发器实现:
mysql> \d $
mysql> CREATE TRIGGER user_bak_tri BEFORE UPDATE ON t_user FOR EACH ROW
BEGIN
INSERT INTO t_user_bak(name,age,create_time) VALUES(old.name,old.age,NOW());
END$
mysql> \d ;
查询 t_user_bak 表中的数据,此时为空:
mysql> SELECT FROM t_user_bak;
Empty set (0.00 sec)
修改 t_user 表中 id 为 1 的数据,然后再次查看 t_user_bak 表中的数据:
mysql> UPDATE t_user SET name = ‘name001’ WHERE name = ‘name01’;
mysql> SELECT
FROM t_user_bak;
+—-+——–+—–+———————+
| id | name | age | create_time |
+—-+——–+—–+———————+
| 1 | name01 | 23 | 2018-05-15 05:07:40 |
+—-+——–+—–+———————+
1 row in set (0.00 sec)
可见,数据已经自动备份到 t_user_bak 中。
4、触发器中的 new 和 old 关键字:
(1)作用:用来访问受触发器影响的行中的列
(2)用法:
a、在 INSERT 操作中,new 表示将要插入(BEFORE INSERT)或者已经插入(AFTER INSERT)表中的数据;
b、在 UPDATE 操作中,new 表示将要插入或者已经插入的新数据,而 old 表示将要插入或者已经插入的原数据;
c、在 DELETE 操作中,old 表示将要删除或者已经被删除的原数据;
d、OLD 是只读的,而 NEW 则可以在触发器中使用 SET 赋值,这样不会再次触发触发器,造成循环调用;
5、触发器管理:
(1)查看已经创建好的触发器:
语法:
mysql> USE db_name; #选择数据库
mysql> SHOW TRIGGERS; #查看选择的数据库中已经创建的所有触发器
mysql> SHOW CREATE TRIGGER trigger_name; #查看某个触发器的创建过程
示例:查看 test_db 库中已经创建好的所有触发器:
mysql> USE test_db; #选择数据库
mysql> SHOW TRIGGERS \G #查看该库中的触发器
*** 1. row ***
Trigger: dep_tri
Event: INSERT
Table: t_emp
Statement: BEGIN
DECLARE num INT;
SET num = (SELECT emp_count FROM t_dept_statis WHERE dept_id = new.dept_id);
UPDATE t_dept_statis SET emp_count = num + 1 ,dept_id = new.dept_id, update_time = now() where dept_id = new.dept_id;
END
Timing: AFTER
Created: NULL
sql_mode: STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION
Definer: root@127.0.0.1
character_set_client: utf8
collation_connection: utf8_general_ci
Database Collation: utf8_general_ci
2 rows in set (0.13 sec)
参数解释:
Trigger:触发器名称
Event:触发器所绑定的事件,即:发生什么操作时会执行触发器程序
Table:触发器所在的表
Statement:触发器的逻辑
Timing:触发器的事件
Created:表示创建时间
sql_mode:sql 模式,STRICT_TRANS_TABLES 表示当一个数据不能插入到一个事务表中,则中断当前操作,NO_ENGINE_SUBSTITUTION 表示编译的时候如果没有选择默认存储引擎,则会使用一个默认的存储引擎,并提示一个错误;
Definer:创建触发器的用户
character_set_client:客户端使用的字符集
collation_connection:连接数据库使用的字符校验集
Database Collation:数据库使用的字符校验集
除此之外,还可以使用 information_schema 库中的 trigger 表查看已经存在的触发器,如下:
mysql> USE information_schema;
mysql> SELECT TRIGGER_SCHEMA AS ‘db_name’,EVENT_OBJECT_TABLE as ‘table_name’,TRIGGER_NAME as ‘trigger_name’,ACTION_STATEMENT AS ‘trigger_statement’ FROM TRIGGERS \G
*** 1. row ***
db_name: test_db
table_name: t_emp
trigger_name: dep_tri
trigger_statement: BEGIN
DECLARE num INT;
SET num = (SELECT emp_count FROM t_dept_statis WHERE dept_id = new.dept_id);
UPDATE t_dept_statis SET emp_count = num + 1 ,dept_id = new.dept_id, update_time = now() where dept_id = new.dept_id;
END
2 rows in set (0.02 sec)
(2)删除指定的触发器:
语法:
mysql> DROP TRIGGER trigger_name;
示例:删除 t_emp 表上的 dep_tri 索引:
mysql> DROP TRIGGER dep_tri;
6、触发器的优缺点:
优点:
可以方便而且高效的维护数据;
缺点:
a、高并发场景下容易导致死锁,拖死数据库,成为数据库瓶颈,故高并发场景下一定要慎用;
b、触发器比较多的时候不容易迁移,而且表之间数据导入和导出可能会导致无意中触发某个触发器,造成数据错误,故对于数据量比较大,而且数据库模型非常复杂的情况下慎用;
7、事务场景下的注意要点:
MySQL 中使用了插件式的存储引擎(存储引擎后文会详细介绍),对于 InnoDB 事务型的存储引擎,如果 SQL 语句执行错误,或者触发器执行错误,会发生什么结果呢?
(1)如果触发器或者 SQL 语句执行过程中出现错误,则会发生事务的回滚;
(2)SQL 语句如果执行失败,则 AFTER 类型的触发器不会执行;
(3)如果 AFTER 类型的触发器执行失败,则触发此触发器的 SQL 语句将会回滚;
(4)如果 BEFORE 类型的触发器执行失败,则触发此触发程序的 SQL 语句将会执行失败;
至此,触发器相关内容介绍完毕
第 5 章 MySQL 数据库 DDL 操作之存储过程和函数
• mysql
保存
标签:至少 1 个,最多 5 个
mysql×
上篇文章介绍了 MySQL 数据库 DDL 操作中的触发器,本章将详细介绍 MySQL 数据库 DDL 操作中的存储过程和函数,存储过程和函数在某些复杂业务场景下还是有很大作用的。
1、定义和作用:
存储过程和函数是数据库中预先编译好的一个为了完成特定功能的 SQL 语句集。通过存储过程和函数,可以完成一些具有负责处理逻辑的数据库操作,同时可以减少应用程序和数据库服务器之间的数据传输,提升数据库的数据处理效率。
2、使用存储过程和函数的前提:
(1)创建存储过程和函数时,需要用户具备”CREATE ROUTINE”的权限;
(2)在删除和修改存储过程和函数时,需要用户具备”ALTER ROUTINE”的权限;
(3)在执行存储过程和函数时,需要用户具备”EXECUTE”的权限;
3、储存过程的创建和修改:
(1)存储过程创建的语法:
CREATE PROCEDURE p_name ([procedure_parameter[,…]])
BEGIN
[characteristic …]
…procedure_statement… #存储过程的逻辑处理语句
END
参数解释:
procedure_name:存储过程的名称
procedure_parameter:存储过程的参数,可以包含多个参数,procedure_parameter 中又包含了存储过程参数
类型、参数名称和参数的具体数据类型,它的定义格式为:[IN|OUT|INOUT] parameter_name field_type
characteristic: 表示要修改存储过程的哪个部分,该参数的取值包括以下几种:
a. CONTAINS SQL,表示子程序包含 SQL 语句,但是,不包含读或写数据的语句
b. NO SQL,表示子程序中,不包含 SQL 语句
c. READS SQL DATA,表示子程序中,包含读数据的语句
d. MODIFIES DATA,表示子程序中,包含写数据的语句
e. SQL SECURITY {DEFINER | INVOKER},指明谁有权限来执行
DEFINER,表示只有定义者,自己才能够执行
INVOKER,表示调用者可以执行
f. COMMENT “conte”,表示注释信息
g. LANGUAGE SQL,表示存储过程使用 SQL 语句来实现[默认],为后期引入其他语言实现做准备
存储过程参数类型 [IN|OUT|INOUT] 说明:
IN:表示该参数为输入参数,即:调用存储过程时所要接收的输入参数,为一个具体的值
OUT:表示该参数为输出参数,即:存储过程在被调用时,需要接收的一个外部变量,通常使用 @加变量名定义,比如:”@a”。在存储过程处理完结果之后,可以将结果放在该外部变量中,供外部查看;
INOUT:表示该参数即可作为输入参数,接收一个具体的值;同时也可以作为输出参数,通过传入外部变量,完成在存储过程外部查看变量的值;
(2)示例应用:
示例 1:创建一个存储过程,查询表出指定表中的记录数,并可以在存储过程外部查看:
修改默认定界符为 $,也可使用 DELIMITER 命令完成
mysql> \d $
创建存储过程:
mysql> CREATE PROCEDURE p_count(OUT param INT)
BEGIN
SELECT COUNT() FROM t_user INTO param FROM t_user;
END $
将修改的定界符恢复至默认定界符
mysql> \d ;
使用 CALL 命令调用存储过程,传入 @a 变量,该变量在存储过程中被赋值:
mysql> CALL p_count(@a);
查看通过存储过程所查询到的表中记录数量:
注:查询自定义变量使用”@< 变量名 >”,如果查询系统中的变量,可以使用”@@< 变量名 >”,后期系统参数调优时介绍,此处可以先了解
mysql> SELECT @a;
+——–+
| @a |
+——–+
| 3 |
+——–+
1 row in set (0.00 sec)
示例 2:创建一个存储过程,封装 MySQL 的 LIMIT 函数,实现分页:
创建存储过程:
mysql> \d $
mysql> CREATE PROCEDURE p_page(IN pageNo INT,IN pageSize INT)
BEGIN
SELECT
FROM t_user LIMIT pageNo,pageSize;
END $
mysql> \d ;
调用存储过程,根据具体传入的值查询记录:
mysql> CALL p_page(1,10);
注意:
(1)如果使用的是普通用户登录 MySQL 来执行存储过程,则需要注意权限问题。如果用户对某个表没有查询权限,则该用户如果调用了某个包含对该表有查询操作的存储过程,会调用失败;
(2)在一个存储过程中可以调用其他的函数或者存储过程,如上示例 2,可以调用系统函数 LIMIT;
(3)在存储过程中可以完成事务的操作,比如:”提交事务 (COMMIT) 和回滚事务 (ROLLBACK)”,但是不能完成”LOAD DATA INFILE”操作;
(3)存储过程的修改:
目前,存储过程暂时不支持直接修改其逻辑语句,只支持修改一些存储过程的特征,也就是对 characteristic 进行修改。
语法:
ALTER PROCEDURE procedure_name [characteristic …];
示例:对上述的 p_page 存储过程进行修改,指明调用者可以执行该存储过程:
mysql> ALTER PROCEDURE p_page SQL SECURITY INVOKER;
4、函数的创建和修改:
(1)函数创建的语法:
CREATE FUNCTION function_name([function_parameter[,…]])
RETURNS type
BEGIN
[chracteristic …]
…function statement… #函数中的实现逻辑
END
参数解释:
function_name:函数名称
function_parameter:函数的参数,它的定义格式为:”param_name field_type”
[characteristic]:表示要修改的函数的哪个部分,包括的内容和存储过程相同
RETURNS type:表示返回值的声明
(2)示例应用:
示例 1:创建一个函数,对于输入的内容,会在内容之前拼接”hello”字符串,在结束位置拼接”!”符号,实现如下:
创建函数:
mysql> CREATE FUNCTION function_hello(str CHAR(20))
RETURNS CHAR(50) DETERMINISTIC
RETURN CONCAT(“Hello “,str,’!’);
调用函数,传入字符串”Tomcat”,输出为:”Hello ,Tomcat!”,如下:
mysql> SELECT function_hello(“Tomcat”);
+————————–+
| function_hello(“Tomcat”) |
+————————–+
| Hello Tomcat! |
+————————–+
1 row in set (0.00 sec)
说明:
RETURNS CHAR(50):表示返回值类型为 CHAR(50)
RETURN CONCAT(‘Hello ‘,str,’!’):表示具体的返回值是使用 CONCAT 函数拼接得到的
DETERMINISTIC:指明存储过程执行的结果是否正确。DETERMINISTIC 表示结果是确定的。每次执行存储过程时,相同的输入会得到相同的输出。
示例 2:实现一个加法功能的函数,输入两个 INT 类型的值,计算这两个数的和:
定义函数:
mysql> CREATE FUNCTION function_add1(num1 VARCHAR(20),num2 VARCHAR(20))
RETURNS INT DETERMINISTIC
RETURN (IF(num1 REGEXP “[0-9]{1,}”,num1,0) + IF(num2 REGEXP “[0-9]{1,}”,num2,0));

调用,传入 10 和 20,计算出这两个数的和为 30,如下:
mysql> SELECT function_add(10,20);
+———————+
| function_add(10,20) |
+———————+
| 30 |
+———————+
1 row in set (0.00 sec)
注意:该函数中对入参做了简单判断,判断是否为数值,如果不为数值,默认当做 0 处理;
(3)函数的修改:
上面修改存储过程中介绍了使用 ALTER 完成的方法,此处直接通过修改系统中的 mysql 表中的数据来修改函数,对应的表为:mysql 库中的 proc
示例:将 function_hello 函数的定义者修改为”tomcat”@”localhost”。要修改的用户必须提前存在,修改方式如下:
选库:
mysql> USE mysql;
修改函数的定义者:
mysql> UPDATE proc SET definer = ‘tomcat@localhost’ WHERE name = ‘function_hello’;
刷新权限,如果不执行该操作,修改的结果不会生效:
mysql> FLUSH PRIVILEGES;
5、存储过程和函数的查看和删除:
(1)查看已经创建的存储过程或者函数:
语法:
SHOW <PROCEDURE|FUNCTION> STATUS LIKE “pattern”;
参数解释:
pattern:表示存储过程或者函数名称的匹配串,支持模糊匹配
示例 1:查看创建的 p_page 存储过程:
mysql> SHOW PROCEDURE STATUS LIKE “p_page” \G
*** 1. row ***
Db: test
Name: p_page
Type: PROCEDURE
Definer: root@127.0.0.1
Modified: 2018-05-16 11:56:56
Created: 2018-05-16 11:21:45
Security_type: INVOKER
Comment:
character_set_client: utf8
collation_connection: utf8_general_ci
Database Collation: utf8_general_ci
1 row in set (0.00 sec)
示例 2:查看创建的函数”function_hello”:
mysql> SHOW FUNCTION STATUS LIKE ‘%hello’ \G
*** 1. row ***
Db: test
Name: function_hello
Type: FUNCTION
Definer: root@127.0.0.1
Modified: 2018-05-16 12:09:17
Created: 2018-05-16 12:09:17
Security_type: DEFINER
Comment:
character_set_client: utf8
collation_connection: utf8_general_ci
Database Collation: utf8_general_ci
1 row in set (0.01 sec)
(2)查看存储过程或者函数的定义:
语法:
SHOW CREATE <PROCEDURE|FUNCTION> <procedure_name|function_name>;
示例 1:查看触发器 p_page 的创建过程:
选库:
mysql> USE test;
查看创建过程:
mysql> SHOW CREATE PROCEDURE p_page \G
*** 1. row ***
Procedure: p_page
sql_mode: STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION
Create Procedure: CREATE DEFINER=root@127.0.0.1 PROCEDURE p_page(IN pageNo INT,IN pageSize INT)
SQL SECURITY INVOKER
begin
select from t_user limit pageNo,pageSize ;
end
character_set_client: utf8
collation_connection: utf8_general_ci
Database Collation: utf8_general_ci
1 row in set (0.00 sec)
示例 2:查看函数 function_hello 的创建过程:
选库:
mysql> USE test;
查看创建过程:
mysql> SHOW CREATE FUNCTION function_hello \G
**
1. row ***
Function: function_hello
sql_mode: STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION
Create Function: CREATE DEFINER=root@127.0.0.1 FUNCTION function_hello(str CHAR(20)) RETURNS char(50) CHARSET utf8
DETERMINISTIC
RETURN CONCAT(“Hello “,str,’!’)
character_set_client: utf8
collation_connection: utf8_general_ci
Database Collation: utf8_general_ci
1 row in set (0.00 sec)
注意:
查看存储过程和函数的定义还可以通过系统库 information_schema 中的 routines 表来查看,主要包括的字段有:ROUTINE_SCHEMA,ROUTINE_NAME,ROUTINE_BODY,ROUTINE_COMMENT,DEFINER
mysql> SELECT ROUTINE_SCHEMA,ROUTINE_NAME,ROUTINE_BODY,ROUTINE_COMMENT,DEFINER FROM information_schema.routines;
解释:
ROUTINE_SCHEMA: 存储过程或者函数所在的库
ROUTINE_NAME: 存储过程或者函数名称
ROUTINE_BODY: 存储过程或者函数体
ROUTINE_COMMENT: 注释信息
DEFINER: 存储过程或者函数的定义者
6、删除存储过程和函数:
语法:
DROP <PROCEDURE|FUNCTION> <procedure_name|function_name>;
示例 1:删除存储过程 p_page:
mysql> USE test;
mysql> DROP PROCEDURE p_page;
示例 2:删除函数 function_hello:
mysql> USE test;
mysql> DROP FUNCTION function_hello;
7、变量的定义和赋值:
(1)变量的定义:
语法:
DECLARE var_name type ;
参数解释:
var_name: 表示参数名称
type: 表示参数类型
var_value: 表示参数的默认初始值
示例 1:定义一个 INT 类型的变量 SUM,默认值为 0:
DECLARE SUM INT DEFAULT 0;
示例 2:定义一个 VARCHAR(20)类型的变量 STR,无初始值:
DECLARE STR VARCHAR(20);
(2)变量的赋值:
语法:
a. 直接通过 SET 赋值:
SET var_name = var_value;
b. 通过 SELECT 查询到结果之后再赋值:
SELECT INTO var_name ;
示例 1:给 VARCHAR(20)类型的变量 STR 赋一个初始值为””
mysql> SET STR = “”;
示例 2:查询表 t_user 中的记录数量,并将结果赋值给 INT 类型的 STU_COUNT 变量
mysql> SELECT COUNT(*) INTO STU_COUNT FROM t_user;
8、条件定义和处理:
语法:
a. 条件的定义:
DECLARE condition_name CONDITION FOR condition_value;
condition_value:
SQLSTATE sql_state_value
参数解释:
condition_name: 表示条件的名称
condition_value: 表示条件中所关注的执行结果,通常为 SQL 的执行状态
b. 条件的处理:
DECLARE handler_name HANDLER FOR condition_value do_statement;
参数解释:
handler_name: 表示处理器的名称,常用的有:”CONTINUE”,”SQLWARNING”,”NOT FOUND”,”SQLEXCEPTION”
condition_value: 表示执行的结果,当处理器匹配到该结果时,会执行后面的 do_statement
示例:在一个存储过程中连续给 t_user 表中插入相同的数据,如果插入失败,则继续向下执行,而不退出
mysql> \d $
mysql> CREATE PROCEDURE p_insert()
BEGIN
DECLARE CONTINUE HANDLER FOR SQLSTATE ‘23000’ SET @state = 1;
INSERT INTO t_user(id,name,age) VALUES(1,’bing’,23);
INSERT INTO t_user(id,name,age) VALUES(1,’bing’,23);
SET @val = 666;
END $
mysql> \d ;
mysql> SELECT @state,@val;
+——–+——+
| @state | @val |
+——–+——+
| 1 | 666 |
+——–+——+
1 row in set (0.00 sec)
结果分析:在上述的存储过程中,第二次插入 t_user 表中的记录由于主键冲突,故会插入失败。如果不定义条件处理的话。”SET @val=666”不会被执行,最后查出来的 @val 也不会为 666,同样,@state 也不会为 1,现在查出来的结果分别为 1 和 666,表示处理器起作用了。主键冲突的时候会提示”23000”状态码.
9、游标的使用:
(1)定义:
简单的理解,游标就是一个查询结果集的出口。在这个出口,可以完成对结果的筛选和其他操作。
(2)语法:
a. 声明:
DECLARE cursor_name CURSOR FOR select_statement;
b.OPEN: 打开游标
OPEN cursor_name;
c.FETCH:将游标的结果保存到某些中间变量中
FETCH cursor_name INTO var_name,…;
d.CLOSE:关闭游标
CLOSE cursor_name;
(3)示例:
使用游标统计出学生表 t_student 中男生和女生的总人数:
创建测试表结构:
CREATE TABLE t_student(
id INT PRIMARY KEY AUTO_INCREMENT,
gender VARCHAR(1) DEFAULT ‘0’ COMMENT “0- 男,1- 女”,
name VARCHAR(50) DEFAULT ‘’
) ENGINE = InnoDB DEFAULT CHARSET = UTF8;
测试数据:
INSERT INTO t_student(gender,name) VALUES(‘1’,’name01’);
INSERT INTO t_student(gender,name) VALUES(‘0’,’name03’);
INSERT INTO t_student(gender,name) VALUES(‘0’,’name06’);
INSERT INTO t_student(gender,name) VALUES(‘0’,’name08’);
创建存储过程:
mysql> \d $
mysql> CREATE PROCEDURE student_count()
BEGIN
DECLARE str VARCHAR(1);
DECLARE gender_str CURSOR FOR SELECT gender FROM t_student;
DECLARE EXIT HANDLER FOR NOT FOUND CLOSE gender_str;
SET @m_count = 0;
SET @f_count = 0;

    OPEN gender_str;
    REPEAT
        FETCH gender_str INTO str;
        IF str = '1' THEN
            SET @m_count = @m_count + 1;
        ELSE
            SET @f_count = @f_count + 1;
        END IF;
    UNTIL 0 END REPEAT;
    CLOSE gender_str;
END $

mysql> \d ;
调用存储过程:
mysql> CALL student_count();
Query OK, 0 rows affected (0.00 sec)
查看统计结果,M 表示女生数量,F 表示男生数量,可见,已经使用游标统计完毕:
mysql> SELECT @m_count AS ‘M’,@f_count AS “F” FROM DUAL;
+——+——+
| M | F |
+——+——+
| 1 | 3 |
+——+——+
1 row in set (0.00 sec)
10、存储过程和函数中的流程控制,主要介绍一下常用的 IF,CASE,LOOP,REPEAT,WHILE 流程控制:
(1)IF
语法:
IF search_conditoin THEN statement
elseif search_condition THEN state
END IF
示例:已经在上述的游标中使用到了,可自行查看。
(2)CASE
语法:
CASE case_value
WHEN when_value THEN statement
WHEN when_value THEN statement
… #可有多个判断语句
ELSE statement
END CASE
示例:将上述判断学生性别中使用的 IF 改为 CASE 如下:
CASE str
WHEN ‘1’ THEN
SET @m_count = @m_count + 1;
ELSE
SET @f_count = @m_count + 1;
END CASE;
(3)LOOP
语法:
[loop_label:] LOOP
statement
END LOOP [loop_label];
示例:
LOOP 通常用在循环语句中,处于 BEGIN…END 之间,如果没有退出语句的话,会一直循环下去,造成死循环,可以和 LEAVE 一块使用,如下,求 1+2+…+100 的和:
创建存储过程:
mysql> \d $
mysql> CREATE PROCEDURE p_getsum()
BEGIN
SET @sum = 0;
SET @i = 0;
label:LOOP
SET @i = @i + 1;
SET @sum = @sum + @i;
IF @i = 100 THEN
LEAVE label;
END IF;
END LOOP label;
END $
mysql> \d ;
调用存储过程并查看结果:
mysql> CALL p_getsum();
mysql> SELECT @sum;
+——+
| @sum |
+——+
| 5050 |
+——+
1 row in set (0.00 sec)
说明:
LEAVE:通常用在循环结构中,用来退出指定标记的循环,如上”LEAVE label”,表示跳出名称为 label 的循环,即:退出 LOOP 循环。
(4)REPEAT
语法:
[label:]REPEAT
statement
UNTIL search_condition
END REPEAT [label]
示例:求 1+2+…+100 的和:
创建存储过程:
mysql> \d $
mysql> CREATE PROCEDURE p_getsum1()
BEGIN
SET @i = 0;
SET @SUM = 0;
REPEAT
SET @i = @i + 1;
SET @sum = @sum + @i;
UNTIL @i > 99
END REPEAT;
SELECT @sum FROM DUAL;
END $
mysql> \d ;
调用存储过程,显示 1+2+…+100 的值:
mysql> CALL p_getsum1();
+——+
| @sum |
+——+
| 5050 |
+——+
1 row in set (0.01 sec)
Query OK, 0 rows affected (0.01 sec)
(5)WHILE
语法:
[label:]WHILE search_condition DO
statement
END WHILE [label];
示例:求 1+2+…+100 的和:
创建存储过程:
mysql> \d $
mysql> CREATE PROCEDURE p_getsum()
BEGIN
SET @i = 0;
SET @sum = 0;
WHILE @i < 100 DO
SET @i = @i + 1;
SET @sum = @sum + @i;
END WHILE;
SELECT @sum FROM DUAL;
END $
\d ;
调用存储过程,查看结果:
CALL p_getsum();
+——+
| @sum |
+——+
| 5050 |
+——+
1 row in set (0.00 sec)
Query OK, 0 rows affected (0.00 sec)
此处举了一个简单的求和的例子,说明了一下存储过程中的流程控制,负责的流程控制也是由这些简单的组合而成的。简单的掌握了,就可以在这个基础上写出更复杂的存储过程。
11、存储过程的优缺点:
优点:
(1)可以完成实时的复杂报表数据处理及统计;
(2)可以很好的解决某些业务系统和数据库之间的耦合度,比如政府、银行或者金融系统中,一旦存储过程调试完毕,会稳定运行,减少很大一部分不必要的系统间交互;
缺点:
(1)互联网行业大并发场景,存储过程会由于访问量大,而且同时操作多张表,有可能会造成死锁,不好排查,导致数据库出现瓶颈。所以应该慎用,最好别用;
(2)迁移会比较麻烦,如果存储过程中用到了多张表,必须先保证表结构迁移正确,否则存储过程迁移时会出现错误;
(3)如果数据库中的某些表结构变化了,可能需要重新删除并创建存储过程,可扩展性较差;
(4)存储过程多数情况下都是由 DBA 编写,普通开发人员不容易掌握;
至此,存储过程和函数相关的内容介绍完毕
第 6 章 MySQL 数据库 DDL 操作之事件调度器
MySQL 中的事件调度器是在 5.1 版本之后新增的,可以在数据库中定时触发某种操作,类似于 Spring 中的 Quartz 定时任务或者 Linux 中的 crontab 任务调度器,下面将介绍 MySQL 中事件调度器的用法。
1、调度器的创建:
(1)语法:
CREATE EVENT event_name ON SCHEDULE <time_frequency> DO <event_statement>;
参数解释:
event_name:表示自定义的事件调度器的名称,放在 CREATE EVENT 关键字之后;
time_frequency:表示该事件调度器什么时间执行以及执行周期为多少;
event_statement:表示该事件调度器中要执行的具体操作或者事件,可以为一个语句,也可以为一个语句块,即:由 BEGIN…END 包含的语句块,中间可以添加一些执行逻辑,需要使用 \d 指定定界符。除此之外,还可以在 event_statement 中调用其他的存储过程和函数;
(2)示例:
示例 1:创建一张测试表 t_test,每隔 10 秒钟向该表中插入一条记录,操作如下:
创建表结构:
mysql> CREATE TABLE t_test(
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(10) NOT NULL DEFAULT ‘’,
create_time DATETIME
) ENGINE = InnoDB DEFAULT CHARSET = UTF8;
创建事件调度器:
mysql> CREATE EVENT insert_event
ON SCHEDULE EVERY 10 SECOND
DO
INSERT INTO t_test(name,create_time) VALUES(‘test_name’,NOW());
10 秒之后查询 t_test 表,结果如下:
mysql> SELECT FROM t_test;
Empty set (0.00 sec)
发现并没有起作用,10 秒之后并未插入数据,这是由于事件调度器未打开导致,通过如下命令查看事件调度器的状态,发现结果为 OFF,表示未打开:
mysql> SHOW VARIABLES LIKE ‘event_scheduler’;
+—————–+——-+
| Variable_name | Value |
+—————–+——-+
| event_scheduler | OFF |
+—————–+——-+
1 row in set (0.01 sec)
使用如下命令打开事件调度器:
mysql> SET GLOBAL event_scheduler = ON;
或者使用:mysql> SET @@global.event_scheduler = ON;
Query OK, 0 rows affected (0.11 sec)
再次查看,已经打开事件调度器:
mysql> SHOW VARIABLES LIKE ‘event_scheduler’;
+—————–+——-+
| Variable_name | Value |
+—————–+——-+
| event_scheduler | ON |
+—————–+——-+
1 row in set (0.00 sec)
打开事件调度器的开关之后,在经过 10 秒,查看 t_test 表,发现事件调度器已经正常执行了,如下:
mysql> SELECT
FROM t_test;
+—-+———–+———————+
| id | name | create_time |
+—-+———–+———————+
| 1 | test_name | 2018-05-16 16:58:45 |
+—-+———–+———————+
注意:上述使用”SET GLOBAL”命令只能全局修改服务器参数,如果数据库重启,该参数会失效。如果要永久修改,需要修改 MySQL 的配置文件,编辑 /etc/my.cnf,在 [mysqld] 中添加如下内容:
[mysqld]
event_scheduler = ON #添加该项

示例 2:上述示例是每 10 秒给 t_test 表中插入一条记录,表中的记录会快速增多,现在通过另外一个事件调度器,完成每 1 分钟清空一次 t_test 表中的记录,如下:
创建事件调度器:
mysql> CREATE EVENT clear_event
ON SCHEDULE EVERY 1 MINUTE
DO
TRUNCATE TABLE t_test;
创建完成之后,立刻查看,会发现 t_test 表中的数据已经被清空,再 10 秒后差生的数据,在 1 分钟之后又会被再次清空:
mysql> SELECT FROM t_test;
Empty set (0.00 sec)
示例 3:定期清理 t_log 日志表,并将清除时间及清除的记录数量写入 t_delete_log 中,通过事件调度器和存储过程实现:
创建 t_delete_log 表:
CREATE TABLE t_delete_log(
id INT PRIMARY KEY AUTO_INCREMENT,
delete_time DATETIME,
delete_count INT DEFAULT 0
) ENGINE = InnoDB DEFAULT CHARSET = UTF8;
创建存储过程:
mysql> \d $
mysql> CREATE PROCEDURE p_clear()
BEGIN
DECLARE d_count INT DEFAULT 0;
SET AUTOCOMMIT = 0;
SET d_count = (SELECT COUNT(
) FROM t_log);

    # 判断是否有待清除的数据
    IF d_count > 0 THEN
        TRUNCATE t_log;
        INSERT INTO t_delete_log(delete_time,delete_count) VALUES(NOW(),d_count);
    END IF;
    COMMIT;
END $

mysql> \d ;
创建事件调度器:
mysql> CREATE EVENT student_clear_event
ON SCHEDULE EVERY 1 MINUTE
DO
CALL p_clear();
上述事件调度器会每隔 1 分钟调用一次清除表数据的存储过程,完成一次历史数据清理及记录归档,综合使用到了事件调度器和存储过程。
2、调度器信息的查看:
语法:
SHOW EVENTS ; #查看已有调度器的信息,可以添加 LIKE 对调度器的名称进行筛选
SHOW CREATE EVENT event_name; #查看指定名称的调度器的创建信息
SHOW PROCESSLIST; #如果用户具有 PROCESS 权限,可以使用该命令查看调度器的线程状态
SELECT FROM information_schema.event; #也可以从系统库中查看
输出结果中重要参数说明:
Db: 表示调度器所在的数据库
Name: 表示调度器的名称,可以使用 Like 条件过滤查看
Definer: 表示调度器的定义者
Time zone: 表示调度器使用的时区,SYSTEM 表示使用系统默认的时区
Interval value: 表示调度器时间周期的单位,包括:YEAR | QUARTER | MONTH | DAY | HOUR | MINUTE |
WEEK | SECOND | YEAR_MONTH | DAY_HOUR | DAY_MINUTE | DAY_SECOND | HOUR_MINUTE | HOUR_SECOND | MINUTE_SECOND
Starts: 表示调度器的开始执时间
Status:表示调度器的可用状态,包括:ENABLE,DISABLE,ENABLE ON SLAVE
示例 1:查看在 test 库中创建的事件调度器,可以发现刚才创建的调度器器 insert_event 和 clear_event,如下:
mysql> USE test;
mysql> SHOW EVENTS \G
*** 1. row ***
Db: test
Name: clear_event
Definer: root@127.0.0.1
Time zone: SYSTEM
Type: RECURRING
Execute at: NULL
Interval value: 1
Interval field: MINUTE
Starts: 2018-05-16 17:08:43
Ends: NULL
Status: ENABLED
Originator: 3
character_set_client: utf8
collation_connection: utf8_general_ci
Database Collation: utf8_general_ci
*** 2. row ***
Db: test
Name: insert_event
Definer: root@127.0.0.1
Time zone: SYSTEM
Type: RECURRING
Execute at: NULL
Interval value: 10
Interval field: SECOND
Starts: 2018-05-16 16:53:55
Ends: NULL
Status: ENABLED
Originator: 3
character_set_client: utf8
collation_connection: utf8_general_ci
Database Collation: utf8_general_ci
2 rows in set (0.00 sec)
示例 2:查看触发器 clear_event 的创建信息:
mysql> USE test;
mysql> SHOW CREATE EVENT clear_event;
*** 1. row ***
Event: clear_event
sql_mode: STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION
time_zone: SYSTEM
Create Event: CREATE DEFINER=root@127.0.0.1 EVENT clear_event ON SCHEDULE EVERY 1 MINUTE STARTS ‘2018-05-16 17:08:43’ ON COMPLETION NOT PRESERVE ENABLE DO TRUNCATE TABLE t_test
character_set_client: utf8
collation_connection: utf8_general_ci
Database Collation: utf8_general_ci
1 row in set (0.00 sec)
示例 3:查看调度器的线程状态:
mysql> SHOW PROCESSLIST \G
*** 1. row ***
Id: 1
User: event_scheduler
Host: localhost
db: NULL
Command: Daemon
Time: 55
State: Waiting for next activation
Info: NULL
3、调度器的修改:
语法:
ALTER
[DEFINER = { user | CURRENT_USER}]
EVENT event_name
[ON SCHEDULE schedule]
[ON COMPLETION [NOT] PRESERVE]
[RENAME TO new_event_name] #修改名称,支持 RENAME 语法
[ENABLE | DISABLE | DISABLE ON SLAVE] #将其修改为可用或者不可用
[COMMENT ‘string’] #添加注释信息
[DO event_body]
示例:
示例 1:将 test 库中的 insert_event 事件调度器改名为 save_event,操作如下:
mysql> USE test;
mysql> ALTER EVENT insert_event RENAME TO save_event;
mysql> SHOW EVENTS LIKE ‘save_event’\G
*** 1. row ***
Db: test
Name: save_event
Definer: root@127.0.0.1
Time zone: SYSTEM
Type: RECURRING
Execute at: NULL
Interval value: 10
Interval field: SECOND
Starts: 2018-05-16 16:53:55
Ends: NULL
Status: ENABLED
Originator: 3
character_set_client: utf8
collation_connection: utf8_general_ci
Database Collation: utf8_general_ci
1 row in set (0.00 sec)

# 查看,发现已经修改。
示例 2:将 save_event 调度器改为不可用:
mysql> USE test;
mysql> ALTER EVENT save_event DISABLE;

# 查看 save_event,发现 Status 已经变为 DISABLE,表示该调度器已经不可用了
mysql> SHOW EVENTS LIKE ‘save_event’\G
*** 1. row ***
Db: test
Name: save_event
Definer: root@127.0.0.1
Time zone: SYSTEM
Type: RECURRING
Execute at: NULL
Interval value: 10
Interval field: SECOND
Starts: 2018-05-16 16:53:55
Ends: NULL
Status: DISABLED
Originator: 3
character_set_client: utf8
collation_connection: utf8_general_ci
Database Collation: utf8_general_ci
1 row in set (0.00 sec)

# 修改完成之后,再次查看 save_event,已经发现没有新数据插入了,表示修改生效。
4、调度器的禁用和删除:
语法:
ALTER EVENT event_name DISABLE; #禁用某个调度器
DROP EVENT [IF EXISTS] event_name; #删除某个调度器
示例:禁用和删除 save_event 事件调度器并查看:
mysql> USE test;

# 如果某个调度器不用了,可以先把其禁用
mysql> ALTER EVENT save_event DISABLE;

# 确定删除之后,可以使用 DROP 完成调度器删除操作
mysql> DROP EVENT save_event;
Query OK, 0 rows affected (0.00 sec)

mysql> SHOW EVENTS LIKE ‘save_event’;
Empty set (0.00 sec)
5、调度器的优缺点及适用场景:
(1)优点:
a. 可以实现类似于操作系统层面的定时任务调度;
b. 可以再数据库层面解决事件的调度,由 DBA 统一维护,不依赖于操作系统层面的事件调度,有效防止了系统维护人员误操作调度器而导致的错误;
(2)缺点:
a. 在服务器繁忙的情况下,或者功能对于性能要求很高的情况下,使用调度器会对性能产生一定影响,因为调度器也是在后台开启线程一直在运行及判断;
b. 需要具有 SUPER 权限的用户才可以创建调度器,而 SUPER 用户权限一般开发人员是不可能具有的,需要 DBA 专门创建;
(3)适用场景:
a. 历史数据定期统计
b. 过期数据清除
至此,MySQL 事件调度器的内容介绍完毕
第 7 章 MySQL 数据库之索引的应用
前面几篇文章详细介绍了 MySQL 数据库的 DML,DDL,DCL,DQL 常用操作,本篇文章将介绍 MySQL 中一块对于开发和维护都比较重要的内容–MySQL 索引的应用!
1、索引的作用
(1)如果索引为唯一索引,可以保证数据库中每一行数据的唯一性
(2)索引如果创建的合适,会大幅度提高数据库的查询性能,这也是索引最大的作用
(3)索引能够使得在查询过程中,使用到数据库的查询优化器,极大提高系统的性能
2、索引的分类
(1)按照数据结构和使用的算法划分:
B+Tree 索引
内部实现采用了 B+Tree 数据结构,数据全部存在叶子节点上。本质上是一棵平衡排序树,由二叉树进化而来,各个叶结点由指针相连,按照从左到右的顺序读取叶子结点上的数据,会得到一个有序的数列。
Hash 索引
内部实现采用了 Hash 算法,数据的保存方式为一对一,一个键对应一条唯一的记录,类似于 Redis 或者 Memcached 中的 K-V 存储结构。
R-Tree 索引
内部实现采用了 R-Tree 数据结构,R-Tree 是一种空间索引的数据结构,它是 B 树向多维空间发展的另外一种形式,在地理位置测绘领域有所应用,其他场景几乎没有应用,了解即可。
(2)按照类型划分:
普通索引
普通索引是一种最基本的索引,只是为了提高数据的查询效率,也是开发中使用比较多的一种索引,允许索引列重复。
主键索引
用来唯一标识数据库中的一条记录,常用于保证数据库中记录的参照完整性,既不可为空,也不能重复。
唯一索引
用来唯一标识数据库中的一条记录,但是与主键索引稍有不同,唯一索引允许索引列的值为空,但是不允许索引列的值发生重复。
联合索引 / 组合索引
指在数据库表中的某几个字段上同时建立的索引,即:这个索引会关联不止一个列。使用的时候需要特别注意,这种索引遵循最左前缀匹配规则,在下面的索引使用中会详细介绍。
全文索引
用来完成某一段文字中的关键字查找,可以简单理解为 like 的加强版,不过使用方法和 like 不同,全文索引比较像一个搜索引擎。它目前支持的数据类型有:char,varchar 和 text 类型。
3、索引的创建和查看
(1)索引的创建
语法:
方法一:使用 CREATE INDEX 方法
CREATE INDEX index_name ON table_name(<field1,field2,…>);
方法二:使用修改表结构的方法
ALTER TABLE table_name ADD INDEX index_name ON table_name(<field1,field2,…>);
方法三:在创建表的时候指定
CREATE TABLE table_name (
field1 INT NOT NULL AUTO_INCREMENT,
field2 INT ,
field3 INT ,
PRIMARY KEY(field_name),
UNIQUE index_name(field(len)),
INDEX index_name(field(len))
);
示例:
示例 1:创建一张 t_user 测试表,字段包含:[id(主键),user_no(用户编号),login_name(登录名称),login_pass(登录密码),phone(手机号)],要求:id 作为主键,user_no 列上建立唯一索引,login_name 和 login_pass 两个列上建立联合索引,phone 列上建立普通索引:
方法一:创建表的时候指定
mysql> USE test;
mysql> CREATE TABLE t_user(
id INT NOT NULL AUTO_INCREMENT,
user_no VARCHAR(30) NOT NULL,
login_name VARCHAR(50) NOT NULL,
login_pass VARCHAR(50) NOT NULL,
phone VARCHAR(15) NOT NULL,
PRIMARY KEY(id), #主键索引
UNIQUE user_no_ind(user_no), #唯一索引
INDEX name_pass_ind(login_name,login_pass), #联合索引
INDEX phone_ind(phone) #普通索引
)ENGINE = InnoDB DEFAULT CHARSET = UTF8;
方法二:使用 CREATE INDEX 创建索引,此种方式不能创建主键索引
mysql> USE test;

# 创建表的时候先不指定索引:
mysql> CREATE TABLE t_user(
id INT NOT NULL AUTO_INCREMENT,
user_no VARCHAR(30) NOT NULL,
login_name VARCHAR(50) NOT NULL,
login_pass VARCHAR(50) NOT NULL,
phone VARCHAR(15) NOT NULL,
PRIMARY KEY (id)
) ENGINE = InnoDB DEFAULT CHARSET = UTF8;

# 使用 CREATE INDEX 命令创建表上的索引
mysql> CREATE UNIQUE INDEX user_no_ind ON t_user(user_no);
mysql> mysql> CREATE INDEX name_pass_ind ON t_user(login_name,login_pass);
mysql> CREATE INDEX phone_ind ON t_user(phone);
方法三:使用 ALTER TABLE 修改表结构的方式创建索引
mysql> USE test;

# 创建表结构
mysql> CREATE TABLE t_user(
id INT NOT NULL,
user_no VARCHAR(30) NOT NULL,
login_name VARCHAR(50) NOT NULL,
login_pass VARCHAR(50) NOT NULL,
phone VARCHAR(15) NOT NULL
) ENGINE = InnoDB DEFAULT CHARSET = UTF8;

# 使用 ALTER TABLE 命令创建索引
mysql> ALTER TABLE t_user ADD PRIMARY KEY(id);
mysql> ALTER TABLE t_user ADD UNIQUE INDEX user_no_ind(user_no);
mysql> ALTER TABLE t_user ADD INDEX name_pass_ind(login_name,login_pass);
mysql> ALTER TABLE t_user ADD INDEX phone_ind(phone);
示例 2:创建一张帖子内容表,并在帖子内容列创建全文索引
方法一:创建表结构的时候指定索引
mysql> USE test;
mysql> CREATE TABLE t_note(
id BIGINT NOT NULL AUTO_INCREMENT,
note_content TEXT NOT NULL,
create_time DATETIME,
PRIMARY KEY(id),
FULLTEXT(note_content) #添加全文索引
) ENGINE = InnoDB DEFAULT CHARSET = UTF8;
方法二:使用 CREATE INDEX 方式创建全文索引
mysql> USE test;
mysql> CREATE TABLE t_note(
id BIGINT NOT NULL AUTO_INCREMENT,
note_content TEXT NOT NULL,
create_time DATETIME,
PRIMARY KEY(id)
) ENGINE = InnoDB DEFAULT CHARSET = UTF8;

# 添加全文索引
mysql> CREATE FULLTEXT INDEX note_ind ON t_note(note_content);
方法三:使用 ALTER TABLE 修改表结构的方式创建全文索引
mysql> USE test;
mysql> CREATE TABLE t_note(
id BIGINT NOT NULL AUTO_INCREMENT,
note_content TEXT NOT NULL,
create_time DATETIME,
PRIMARY KEY(id)
) ENGINE = InnoDB DEFAULT CHARSET = UTF8;

# 添加全文索引
mysql> ALTER TABLE t_note ADD FULLTEXT note_ind(note_content);
(2)索引信息的查看
语法:
SHOW INDEX FROM table_name ;
SHOW INDEXES FROM table_name ;
注意:SHOW 后面可以为 INDEX 或者 INDEXES,可以使用 WHERE 条件根据索引名称查看索引信息
示例:查看 t_user 表上所创建的索引
mysql> USE t_user;
mysql> SHOW INDEXES FROM t_user \G
*** 1. row ***
Table: t_user
Non_unique: 0
Key_name: PRIMARY
Seq_in_index: 1
Column_name: id
Collation: A
Cardinality: 0
Sub_part: NULL
Packed: NULL
Null:
Index_type: BTREE
Comment:
Index_comment:
…其他行略…
输出字段解释:
Table:索引所在的表名称
Non_unique:是否为非唯一的索引,对唯一索引和主键索引,此值为 0,因为主键和唯一键必须唯一
Key_name:索引的名称
Seq_in_index:索引中该列的位置,在
Column_name:索引所在列的列名称
Collation:列使用哪种方式存储在索引中,B+ 数索引总是 A,表示排过序的。对于其他的如 Hash 索引,此处可能为 NULL,因为 Hash 索引并未排序
Cardinality:索引中非胃一直的数目的估计值,通常用于优化器去判断是否查询时使本索引
Sub_part:是否只是用了列的一部分作为索引。比如:在某个非常长的字段上的前多少个字符上创建索引的情况
Packed:关键字如何被压缩,Null 表示未被压缩
Null:索引的列中是否含有 Null 值,主键索引,此处为空,表示不含有 Null 值
Index_type:索引类型,InnoDB 存储引擎,此处为 B+ 树
Comment:索引列的注释
Index_comment:索引的注释
注意:上述字段中,Cardinality 字段相对来说比较重要,可以通过该字段来判断当前的索引是否最优,通常如果索引的利用率比较高的话,这个值会比较接近于表中的记录数,即:和表中的记录数接近于 1:1,但是这个值并不是实时维护,索引当相差比较大的时候,可以使用”ANALYZE TABLE table_name”命令去更新下这个值,有利于优化器对索引使用的判断。
4、索引的修改和删除
(1)索引的删除
语法:
DROP INDEX index_name ON table_name;
示例:
示例 1:删除 t_user 表中 phone 列上的 phone_ind 索引
mysql> USE test;
mysql> DROP INDEX phone_ind ON t_user;
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
示例 2:删除 t_user 表中的 id 列上的主键索引
mysql> USE test;
mysql> ALTER TABLE t_user DROP PRIMARY KEY;
Query OK, 0 rows affected (0.19 sec)
Records: 0 Duplicates: 0 Warnings: 0
(2)索引的修改
索引的修改过程其实是先删除索引,在重新创建索引,可以按照上述的删除索引和创建索引步骤完成。
5、索引使用注意事项
(1)使用场景
a. 业务场景中,读多写少的场景
b.SQL 查询场景中,常用于 WHERE 语句之后的过滤条件;区分度大于 80%;WHERE 语句之后的过滤字段在过滤时不参与运算;
(2)以下的情况,对于有索引的列,查询时也不会使用索引
a. 当优化器判断使用索引和不适用索引差别不大时,将不会使用索引,比如:性别列创建的索引
b. 查询条件中发生计算,优化器将不使用索引,比如:WHERE SUBSTR(name,5) = ‘BING’
c. 查询条件中包含了隐士类型转换,比如:WHERE phone = 13520277199
d. 反向查询不会使用索引,比如:!=,<>,NOT IN,NOT LIKE 等,比如:WHERE name != ‘BING’;
e.LIKE 的左模糊匹配,将不会使用索引,比如:WHERE name LIKE ‘%BING’,右匹配查询会走索引;
f. 联合索引中,不满足左前缀规则,则 MySQL 不会使用索引。比如:对于 name,pass,user_no 列的联合索引,下述情况将不会使用索引:
WHERE pass = ‘value’
WHERE user_no = ‘value’
WHERE pass = ‘123’ AND user_no = ‘123’
而如下的情况将会使用到索引:
WHERE name = ‘bing’
WHERE name = ‘bing’ AND pass = ‘123’;
WHERE name = ‘bing’ AND user_no = ‘021250’;
WHERE pass = ‘123’ AND name = ‘bing’;
WHERE name = ‘bing’ AND pass = ‘123’ AND user_no = ‘021250’;
g. 某个带索引的列和不带索引的列中间使用 OR 连接,则带索引的列也不会使用索引,如:user_no 列带有索引,phone_未带索引,则:
不会使用索引:WHERE user_no = ‘123’ OR phone = ‘13520277898’
会使用索引:WHERE user_no = ‘123’ AND phone = ‘15265648758’
h. 如果在联合索引中有范围查询,如果字段之间使用 OR 连接,则整个查询条件不会使用索引,如果字段之间使用 AND 连接,则从第一个范围查询开始之后的条件都不会使用索引
比如:name,score,usre_no 列上的联合索引,则:
不会使用索引:WHERE name = ‘bing’ OR score = 123 OR user_no = ‘02311’;
不会使用索引:WHERE name = ‘bing’ AND score = 123 OR user_no = ‘01231’;
会使用索引:WHERE name = ‘bing’ AND score = 123 AND user_no = ‘021321’;
name 列会使用索引,name 之后的列不会使用索引:WHERE name = ‘bing’ AND score > 120 AND user_no = ‘021321’;
6、执行计划查看
语法:
EXPLAIN

# 从初始化的控制台日志判断是否初始化成功,看到两个单行的 OK 表示成功,如下
2018-05-22 04:58:48 0 [Note] /usr/local/mysql-5.6.39/bin/mysqld (mysqld 5.6.39-log) starting as process 3642 …
OK

2018-05-22 04:58:54 0 [Note] /usr/local/mysql-5.6.39/bin/mysqld (mysqld 5.6.39-log) starting as process 3664 …
OK

# 查看 3306 和 3307 实例的数据目录是否正常,是否有初始化之后的系统表
[root@WB-BLOG mysql-5.6.39]# ls /mysql_data/3306/data/
ibdata1 ib_logfile0 ib_logfile1 mysql performance_schema test
[root@WB-BLOG mysql-5.6.39]# ls /mysql_data/3307/data/
ibdata1 ib_logfile0 ib_logfile1 mysql performance_schema test
8、使用 mysqld_safe 命令测试实例是否可以正常启动
[root@WB-BLOG mysql-5.6.39]# cd bin/
[root@WB-BLOG bin]# ./mysqld_safe –defaults-file=/mysql_data/3306/my.cnf –datadir=/mysql_data/3306/data/ &

# 查看进程是否正常启动
[root@WB-BLOG bin]# netstat -tunlp | grep mysql
tcp 0 0 :::3306 :::* LISTEN 4050/mysqld
如上结果表示 3306 实例启动正常,可以用此方法测试 3307 是否可以正常启动。
9、手动编写针对每个实例的启动脚本
(1)修改 3306 和 3307 实例的密码,修改方式为使用跳过授权表的方式启动,然后登陆修改,可以参考第一篇博文,MySQL 的多种安装方式中有介绍,使用的命令如下,不再详述
[root@WB-BLOG 3306]# /usr/local/mysql-5.6.39/bin/mysqld_safe –defaults-file=/mysql_data/3306/my.cnf –datadir=/mysql_data/3306/data/ –skip-grant-tables &
[root@WB-BLOG ~]# mysql -uroot -p -P3306 -S /mysql_data/3306/data/mysql.sock
mysql> update user set password = password(‘root’);
mysql> flush privileges;
(2)编写 3306 实例的启动脚本,如下:
[root@WB-BLOG bin]# cd /mysql_data/3306/
[root@WB-BLOG 3306]# vim mysqld
写入如下内容:

#!/bin/bash
#
MYSQL_BASE_PATH=/usr/local/mysql-5.6.39
MYSQL_PORT=3306
MYSQL_3306_BASEDIR=/mysql_data/3306
MYSQL_SOCK=${MYSQL_3306_BASEDIR}/data/mysql.sock
MYSQL_CONF=${MYSQL_3306_BASEDIR}/my.cnf
MYSQL_DATADIR=${MYSQL_3306_BASEDIR}/data
MYSQL_USER=root
MYSQL_PASS=root

#When No Input
function Usage(){
echo “Please Usage ./mysqld {start|stop|restart|status}”
exit 2
}

#Start MySQL
function start_mysql() {
if [ps -ef | grep mysql | grep ${MYSQL_PORT} | grep -v grep | wc -l -gt 1 ]; then
echo “MySQL is already running…”
else
${MYSQL_BASE_PATH}/bin/mysqld_safe –defaults-file=${MYSQL_CONF} –datadir=${MYSQL_DATADIR} > /dev/null 2>&1 &
sleep 2
if [ps -ef | grep mysql | grep ${MYSQL_PORT} | grep -v grep | wc -l -gt 1 ]; then
echo “MySQL start success!”
else
echo “MySQL start failure.View logs and try again.”
fi
fi
}

#Stop MySQL
function stop_mysql(){
if [ps -ef | grep mysql | grep ${MYSQL_PORT} | grep -v grep | wc -l -gt 1 ]; then
${MYSQL_BASE_PATH}/bin/mysqladmin -u${MYSQL_USER} -p${MYSQL_PASS} -P${MYSQL_PORT} -S ${MYSQL_SOCK} shutdown > /dev/null 2>&1 &
sleep 2
if [ps -ef | grep mysql | grep ${MYSQL_PORT} | grep -v grep | wc -l -gt 1 ]; then
echo “MySQL stop failure…”
else
echo “MySQL stop success!”
fi
else
echo “MySQL is not running…”
fi
}

#Restart MySQL
function restart_mysql(){
stop_mysql
sleep 2
start_mysql
}

#MySQL status
function mysql_status(){
if [ps -ef| grep mysql | grep ${MYSQL_PORT}|grep -v grep | wc -l -gt 1 ]; then
echo “MySQL is running…”
else
echo “MySQL is stopped.”
fi
}
case $1 in
start)
start_mysql
;;
stop)
stop_mysql
;;
restart)
restart_mysql
;;
status)
mysql_status
;;
*)
Usage
;;
esac
授予 mysqld 脚本可执行权限,然后启动:
[root@WB-BLOG 3306]# chmod +x mysqld
[root@WB-BLOG 3306]# ./mysqld start

# 查看运行状态
[root@WB-BLOG 3306]# ./mysqld status
MySQL is running…
(3)将 3306 实例中的 mysqld 脚本拷贝一份到 /mysqld_data/3307 目录下,然后修改端口及实例的目录,最终内容如下:

#!/bin/bash
#
MYSQL_BASE_PATH=/usr/local/mysql-5.6.39
MYSQL_PORT=3307
MYSQL_3307_BASEDIR=/mysql_data/3307
MYSQL_SOCK=${MYSQL_3307_BASEDIR}/data/mysql.sock
MYSQL_CONF=${MYSQL_3307_BASEDIR}/my.cnf
MYSQL_DATADIR=${MYSQL_3307_BASEDIR}/data
MYSQL_USER=root
MYSQL_PASS=root

#When No Input
function Usage(){
echo “Please Usage ./mysqld {start|stop|restart|status}”
exit 2
}

#Start MySQL
function start_mysql() {
if [ps -ef | grep mysql | grep ${MYSQL_PORT} | grep -v grep | wc -l -gt 1 ]; then
echo “MySQL is already running…”
else
${MYSQL_BASE_PATH}/bin/mysqld_safe –defaults-file=${MYSQL_CONF} –datadir=${MYSQL_DATADIR} > /dev/null 2>&1 &
sleep 2
if [ps -ef | grep mysql | grep ${MYSQL_PORT} | grep -v grep | wc -l -gt 1 ]; then
echo “MySQL start success!”
else
echo “MySQL start failure.View logs and try again.”
fi
fi
}

#Stop MySQL
function stop_mysql(){
if [ps -ef | grep mysql | grep ${MYSQL_PORT} | grep -v grep | wc -l -gt 1 ]; then
${MYSQL_BASE_PATH}/bin/mysqladmin -u${MYSQL_USER} -p${MYSQL_PASS} -P${MYSQL_PORT} -S ${MYSQL_SOCK} shutdown > /dev/null 2>&1 &
sleep 2
if [ps -ef | grep mysql | grep ${MYSQL_PORT} | grep -v grep | wc -l -gt 1 ]; then
echo “MySQL stop failure…”
else
echo “MySQL stop success!”
fi
else
echo “MySQL is not running…”
fi
}

#Restart MySQL
function restart_mysql(){
stop_mysql
sleep 2
start_mysql
}

#MySQL status
function mysql_status(){
if [ps -ef| grep mysql | grep ${MYSQL_PORT}|grep -v grep | wc -l -gt 1 ]; then
echo “MySQL is running…”
else
echo “MySQL is stopped.”
fi
}
case $1 in
start)
start_mysql
;;
stop)
stop_mysql
;;
restart)
restart_mysql
;;
status)
mysql_status
;;
*)
Usage
;;
esac
10、单机多实例的登录
(1)常规登录方法
[root@WB-BLOG ~]# mysql -uroot -proot -h127.0.0.1 -P3307 -S /mysql_data/3307/data/mysql.sock
参数解释:
-S: 指定示例对应的 Socket 文件
注意:单机多实例的登录需要指定待登录示例对应的 socket 文件。
(2)为了防止每次登陆 MySQL 时需要带一对参数,编写一个方便登陆的脚本 mysql_login.sh,将登录所需参数写入到脚本中,内容如下:
[root@WB-BLOG mysql_data]# cat mysql_login.sh

#!/bin/bash
#
SERVER_IP=127.0.0.1
MYSQL_BASE_PATH=/usr/local/mysql-5.6.39
MYSQL_01_PORT=3306
MYSQL_02_PORT=3307

#MYSQL USER AND PASS
MYSQL_01_USER=root
MYSQL_01_PASS=root
MYSQL_02_USER=root
MYSQL_02_PASS=root

MYSQL_01_BASEDIR=/mysql_data/3306
MYSQL_02_BASEDIR=/mysql_data/3307

MYSQL_01_SOCK=${MYSQL_01_BASEDIR}/data/mysql.sock
MYSQL_02_SOCK=${MYSQL_02_BASEDIR}/data/mysql.sock

echo “1> mysql-3306”
echo “2> mysql-3307”

read -p “Please Input the Login Server Number:[1,2]:” INPUT
case $INPUT in
1)
${MYSQL_BASE_PATH}/bin/mysql -u${MYSQL_01_USER} -p${MYSQL_01_PASS} -P${MYSQL_01_PORT} -h${SERVER_IP} -S ${MYSQL_01_SOCK} –prompt=’mysql-server-3306> ‘
;;
2)
${MYSQL_BASE_PATH}/bin/mysql -u${MYSQL_02_USER} -p${MYSQL_02_PASS} -P${MYSQL_02_PORT} -h${SERVER_IP} -S ${MYSQL_02_SOCK} –prompt=’m
ysql-server-3307> ‘
;;
*)
echo “Wrong Input.Please run mysql_login.sh again.”
;;
esac
脚本中的登录命令参数说明:
prompt: 指定登录之后的 mysql 命令行提示符,如果同时打开多个 shell 窗口,可以在每一个 mysql 的命令行窗口指定命令提示符,防止数据库的误操作。
脚本的运行效果:
[root@WB-BLOG mysql_data]# ./mysql_login.sh
1> mysql-3306
2> mysql-3307
Please Input the Login Server Number:[1,2]: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.39-log 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-server-3306>

# 登录成功
至此,MySQL 的多实例搭建介绍完毕,后面的主从异步复制介绍打算使用 MySQL 单机多实例来部署,
第 9 章 MySQL 数据库运维之主从复制搭建
上篇文章详细介绍了 MySQL 数据库的单机多实例搭建,本篇文章将在上篇文章的基础上介绍 MySQL 主从复制过程,其中常见的复制架构有:异步复制,半同步复制及同步复制。常用的复制架构有异步复制及半同步复制!
一、常见的复制架构
1、主主复制
(1)结构图:

(2)说明:主主复制即复制的两个实例互为主从,两个库中都可以同时读和写;
(3)优点:
a、对于读写请求都较多的需求,可以在多个实例之间分摊读写请求,减轻单实例的读写压力
b、互为主从,当一个示例出故障时,可以迅速切换到另外一个实例上,提供读写服务
2、一主一从
(1)结构图:

(2)说明:指的是在两个数据库实例中,一个实例扮演着主库的角色,另一个实例扮演着从库的角色。这种方案中,从库通常用来作为备份使用,提供服务的多为主库;
(3)优点:
a、多数情况下,可以有效降低因某台数据库服务器故障而导致数据丢失的概率
b、作为备份服务器,可以在从库上完成在线数据的全备份,而不影响主库的写服务
3、一主多从
(1)结构图:

(2)说明:指的是在多个数据库实例中,只包含了一个主库,其他实例都作为该主库的从库,这种架构是业务规模较大场景中的一种复制架构;
(3)优点:
a、该方已经比较成功,而且使用范围极为广泛,出问题之后可以迅速找到解决方案
a、作为主库的备份,可以迅速扩展多个从库
b、可以使用 mysql-proxy 等中间件提供读写分离服务,通过多个从库来应对大量的读请求,提高网站的吞吐量
c、当主库出故障时,从库可以快速接管主库,成为新的主库,提供写服务
二、主从复制的原理和过程
1、主从异步复制的原理
主库上的二进制 bin-log 中记录主库的所有 DML 操作,同时在主库上运行有一个 IO 线程,用于响应从库上的 bin-log 日志读取请求;在从库上运行有一个 IO 线程和一个 SQL 线程,IO 线程会实时通过网络请求去从库上读取 bin-log 日志,然后写入到自身的 relay-log 日志文件中,同时运行在从库上的 SQL 线程会去解析并读取 relay-log,然后在自身库上执行读取到的 SQL,完成主从数据的同步,示意图如下:

2、主从同步的工作过程
(1)详细过程
a、主库上会开启了二进制 bin-log 日志记录,同时运行有一个 IO 线程;
b、主库上对于需要同步的数据库或者表所发生的所有 DML 操作都会被记录到 bin-log 二进制日志文件中;
c、从库上开启 relay-log 日志,同时运行有一个 IO 线程和一个 SQL 线程;
d、IO 线程负责从主库中读取 bin-log 二进制日志,并写入到本地的 relay-log 日志中,同时记录从库所读取到的主库的日志文件位置信息,以便下次从这个位置点再次读取;
e、SQL 线程负责从本地的 relay-log 日志中读取同步到的二进制日志,并解析为数据库可以识别的 SQL 语句,然后应用到本地数据库,完成同步;
f、执行完 relay-log 中的操作之后,进入睡眠状态,等待主库产生新的更新;
(2)以上详细过程可总结为三步
第一步:主库在每个事务更新数据完成之前,将该操作记录串行地写入到 binlog 文件中;
第二步:从库开启一个 I/O 线程,该线程对主库打开一个普通连接,主要工作是读取二进制日志。如果读取的进度已经跟上了主库,就进入睡眠状态并等待主库产生新的事件。I/O 线程最终的目的是将这些事件写入到中继日志中;
第三步:SQL 线程会读取中继日志,并顺序执行该日志中的 SQL 事件,从而与主数据库中的数据保持一致;
三、MySQL 异步复制搭建过程(单机多实例介绍,沿用上篇文章中搭建的多实例环境)
1、环境准备
操作系统:CentOS6.9
服务器 IP:192.168.0.10
数据库版本:MySQL-5.6.39
数据库实例:实例 1–3306 端口(主),实例 2–3307 端口(从)
2、编辑 3306 实例的配置文件,打开该实例的二进制日志,并修改 server-id,如下
[root@WB-BLOG ~]# cd /mysql_data/3306/
[root@WB-BLOG 3306]# vim my.cnf
[mysqld]
server_id=3
log_bin=/mysql_data/3306/data/mysql-bin
log_bin_index=/mysql_data/3306/data/mysql-bin-index
binlog_format=mixed

参数解释:
(1)server-id:用来标识一个唯一的实例,如果是在同一个局域网内,可以使用 ip 地址的最后一段,要保证唯一
(2)log_bin:二进制日志文件的路径,mysql 用户对该路径必须具有读写权限
(3)log_bin_index:二进制文件的索引路径,mysql 用户对该路径必须具有读写权限
(4)binlog_format:表示二进制日志内容的记录方式,有三种方式:
a、row: 基于行记录的方式,MySQL 会将真实发生变化的行记录进日志,所以如果有 update 更新全表的操作,二进制日志文件会变得非常大。通常用于 SQL 语句复杂但是影响的行比较少的场景
b、statement: 基于语句的方式,MySQL 会将导致数据发生变化的 SQL 语句记录到日志文件中,适用于一条语句影响很多行的场景,但是注意当在主库上使用到了 UUID,SYSDATE,FOUND_ROWS 函数时,使用 statement 方式的复制会出现主从不一致的情况;
c、mixed: 混合记录模式,MySQL 会自动进行判断具体是使用 row 格式还是 statement 格式,通常情况下都使用 mixed,由 MySQL 来进行判断
3、重启主库
[root@WB-BLOG ~]# cd /mysql_data/3306/
[root@WB-BLOG 3306]# ./mysqld restart
4、备份主库的数据
[root@WB-BLOG 3306]# cd /usr/local/mysql-5.6.39/bin/
[root@WB-BLOG tmp]# ./mysqldump -uroot -proot -h127.0.0.1 -P3306 -S /mysql_data/3306/data/mysql.sock -A –master-data=2 -F –single-transaction | gzip > /tmp/mysql_all.sql.gz
参数说明:
-S:指定 socket 文件,单机多实例必须要指定
-A:–all-databases,表示备份所有的数据库
–master-data:表示 change master 命令是否包括在备份之后的 sql 文件中,常用的值有 1 和 2
1:表示 change master 指令在 sql 文件中处于打开状态,可用于快速创建主从同步,不用再次手动修改日志文件名称和位置点
2:表示 change master 指令在 sql 文件中会被注释,从库上使用 change master 时需要手动指定日志文件的文件名和位置点
-F:表示备份日志的时候刷新二进制日志,重新创建一个新的二进制日志文件
–single-transaction:用于 InnoDB 存储引擎格式的表备份,导出开始时设置事务隔离状态并使用一致性快照开始事务,而后马上执行 unlock tables,然后执行导出
gzip:表示将备份的 sql 文件压缩

# 其他常见参数在后面的 MySQL 数据备份于恢复会详细介绍
5、登陆主库,然后创建复制账户
[root@WB-BLOG 3306]# cd ..
[root@WB-BLOG mysql_data]# ./mysql_login.sh

mysql-server-3306> USE mysql

# 授权从库的
mysql-server-3306> GRANT REPLICATION SLAVE,REPLICATION CLIENT ON . to ‘repl‘@’127.0.0.1’ IDENTIFIED BY ‘repl’;
mysql-server-3306> FLUSH PRIVILEGES;
6、查看主库的二进制日志文件及位置点
mysql-server-3306> show master status \G
*** 1. row ***
File: mysql-bin.000014
Position: 367
Binlog_Do_DB:
Binlog_Ignore_DB:
Executed_Gtid_Set:
1 row in set (0.00 sec)
7、将主库导出的数据导入从库中
[root@WB-BLOG mysql_data]# cd /usr/local/mysql-5.6.39/bin/
[root@WB-BLOG bin]# gzip -d /tmp/mysql_all.sql.gz | ./mysql -uroot -proot -S /mysql_data/3307/data/mysql.sock
8、修改从库的配置文件,开启 relay-log 日志,并设置 server-id,如下
[mysqld]
server-id=4
relay_log=/mysql_data/3307/data/relay-log
relay_log_index = /mysql_data/3307/data/relay-log-index

9、修改从库上的 master 指向,使其指向主库,并且从主库上最新的二进制日志和位置点开始同步,然后启动主从同步
[root@WB-BLOG mysql_data]# ./mysql_login.sh
mysql-server-3307> CHANGE MASTER TO master_host = ‘127.0.0.1’,master_port = 3306,master_user=’repl’,master_password=’repl’,master_log_file=’mysql-bin.000014’,master_log_pos = 367;
mysql-server-3307> START SLAVE;
mysql-server-3307> SHOW SLAVE STATUS \G
*** 1. row ***
Slave_IO_State: Waiting for master to send event
Master_Host: 127.0.0.1
Master_User: repl
Master_Port: 3306
Connect_Retry: 60
Master_Log_File: mysql-bin.000015
Read_Master_Log_Pos: 425
Relay_Log_File: relay-log.000004
Relay_Log_Pos: 588
Relay_Master_Log_File: mysql-bin.000015
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Replicate_Do_DB:
注意:上述结果中 Slave_IO_Running 和 Slave_SQL_Running 都为 Yes 表示主从同步成功,如果为 Connecting…,可以等待一会再次查看,如果为 No,表示同步失败;
参数说明:
master_host: 主库的主机名或者 IP 地址
master_port: 主库的端口号,必须为整数,不能加引号,否则会提示错误
master_user: 在主库上添加的复制用户名称
master_password: 在主库上添加的复制用户密码
master_log_file: 主库当前的二进制日志文件名称
master_log_pos: 主库当前的二进制文件位置点,整数,不可加引号,否则会提示错误
开启主从的另外一种方法是分别开启 SQL 线程和 IO 线程,如下:
mysql> START SLAVE IO_THREAD;
mysql> START SLAVE SQL_THREAD;
10、验证,登陆主库,然后创建数据库,查看从库是否可以正常同步
mysql-server-3306> CREATE DATABASE test_db;
mysql-server-3306> QUIT
mysql-server-3307> SHOW DATABASES;
+——————–+
| Database |
+——————–+
| information_schema |
| mysql |
| performance_schema |
| test |
| test_db |
+——————–+
5 rows in set (0.00 sec)

# 从上面的结果可以看到,test_db 已经同步到 3307 实例上了
11、至此,MySQL 的主从复制搭建完毕。
12、主从同步中常见的问题
(1)从库的 IO 线程无法连接,通过”show slave status G”可以查看到具体的错误信息
原因 1:在主库上创建的用户授权错误,导致从库无法远程连接主库
解决办法 1:在主库上通过”show grants for ‘user‘@’ip’;”查看授权是否正确,如果错误,重新授权即可
原因 2:如果是独立主机上的两个主从数据库实例,授权正确的情况下,可能是由于主库的防火墙拦截导致从库无法连接主库
解决办法 2:关闭主库的防火墙,或者在主库所在服务器添加防火墙规则,允许从库的 tcp 连接
(2)从库启动的时候提示 server-id 冲突,导致无法同步主库上的数据
原因:主从库配置文件中的 server-id 相同了
解决办法:将主库可从库配置文件中的 server-id 改为不同,重新开启从库上的同步即可
(3)在从库上执行了创建库或者表的操作,然后在主库上又执行了一遍,导致同步错误,如下:
Last_SQL_Error: Error ‘Can’t create database ‘test1’; database exists’ on query. Default database: ‘test1’. Query: ‘create database test1’
原因:从库上创建了库,主库上再次创建,从库会将主库上的创建过程再次应用到从库,导致从库上创建同名的库,发生错误
解决办法:停止从库,然后设置 sql_slave_skip_count,使其跳过同步主库创建库的操作,从下一个操作开始同步,如下:

# 停止从库
mysql-server-3307> STOP SLAVE;
Query OK, 0 rows affected (0.00 sec)

# 向前跳跃一步,从下一个点开始同步
mysql-server-3307> SET GLOBAL sql_slave_skip_counter =1;
Query OK, 0 rows affected (0.00 sec)

# 重新开启从库上的同步
mysql-server-3307> START SLAVE ;
Query OK, 0 rows affected (0.03 sec)

# 再次查看,发现已经正常
针对直接写从库的操作,可以再从库上创建一个普通用户,授予其部分操作权限,然后设置从库的只读,通过在从库的配置文件中增加”read-only”参数来设置。但是注意,这个参数对而且只对非 super 用户生效,对 root 用户没有任何效果。
13、再生产场景下如何保证主库上的用户可以有写权限,从库上的用户只有读权限
方法 1:在设置从库同步的时候,排除对 mysql 系统库的同步,通过在配置文件中指定 binlog_ignore_db=mysql 来排除不需要同步的库,或者在配置文件中指定 binlog_do_db=db_name 只来同步需要同步的库,然后分别在主库上创建可以写的用户,在从库上创建只能读的用户;
[mysqld]
binlog_ignore_db=mysql
binlog_do_db=user_db
方法 2:在未排除任何库的情况下,先在主库上创建可以读写的用户,然后在从库中从新回收用户的写权限;
方法 3:在主库和从库上创建不同的用户,然后分别授予不同的权限,使得主库只能写,从库只能读;
四、MySQL 半同步搭建过程(介绍过程仍然使用单机多实例的环境)
1、定义
是介于异步复制和全同步复制之间的一种复制方式,主库在执行完客户端提交的事务后不是立刻返回给客户端,而是等待至少一个从库接收到并写到 relay log 中才返回给客户端。
2、优缺点
(1)优点:有效的提高了数据的安全性,需要等到数据写到从库之后才返回给客户端;
(2)缺点:因为需要等待至少一个从库接收到并写入 relaylog 中,索引会造成一定的网络延迟,需要在网络延迟较低的环境中使用
3、搭建过程
(1)前提条件:
a、MySQL 数据库版本为 5.5 及以上
b、属性变量 have_dynamic_loading 的值为 YES
c、异步复制已经搭建完成
(2)查看主库和从库上的 have_dynamic_loading 变量
[root@WB-BLOG mysql_data]# ./mysql_login.sh
mysql-server-3306> SHOW VARIABLES LIKE ‘have_dynamic_loading’;
+———————-+——-+
| Variable_name | Value |
+———————-+——-+
| have_dynamic_loading | YES |
+———————-+——-+
1 row in set (0.00 sec)
(3)登陆主库,在主库上安装半同步插件
mysql-server-3306> INSTALL PLUGIN rpl_semi_sync_master SONAME ‘semisync_master.so’;
Query OK, 0 rows affected (0.01 sec)
mysql-server-3306> SHOW PLUGINS \G
*** 43. row ***
Name: rpl_semi_sync_master
Status: ACTIVE
Type: REPLICATION
Library: semisync_master.so
License: GPL
43 rows in set (0.00 sec)

# 查看输出结果中包括上面的一行,表示半同步插件安装成功
注:如果想卸载半同步插件,可以使用如下命令:
mysql-server-3306> UNINSTALL PLUGIN rpl_semi_sync_master;
(4)登陆从库,安装从库上的半同步插件
mysql-server-3307> INSTALL PLUGIN rpl_semi_sync_slave SONAME ‘semisync_slave.so’;
Query OK, 0 rows affected (0.01 sec)
mysql-server-3307> SHOW PLUGINS;
*** 43. row ***
Name: rpl_semi_sync_slave
Status: ACTIVE
Type: REPLICATION
Library: semisync_slave.so
License: GPL
43 rows in set (0.01 sec)
注:从库上的半同步插件,也可以使用如下命令完成卸载:
mysql-server-3307> UNINSTALL PLUGIN rpl_semi_sync_slave;
(5)查看插件是否加载成功
主库:
mysql-server-3306> SELECT plugin_name,plugin_status FROM information_schema.plugins WHERE plugin_name LIKE ‘%semi%’;
+———————-+—————+
| plugin_name | plugin_status |
+———————-+—————+
| rpl_semi_sync_master | ACTIVE |
+———————-+—————+
1 row in set (0.00 sec)
从库:
mysql-server-3307> SELECT plugin_name,plugin_status FROM information_schema.plugins WHERE plugin_name LIKE ‘%semi%’;
+———————+—————+
| plugin_name | plugin_status |
+———————+—————+
| rpl_semi_sync_slave | ACTIVE |
+———————+—————+
1 row in set (0.00 sec)
(6)配置并开启主库的半同步复制,然后重启主库
[root@WB-BLOG 3306]# vim my.cnf

# 在 mysqld 段下面添加如下内容:
[mysqld]
plugin-load = rpl_semi_sync_master=semisync_master.so
rpl_semi_sync_master_enabled=1
[root@WB-BLOG 3306]# ./mysqld restart
(7)配置并开启从库的半同步复制,然后重启从库
[root@WB-BLOG 3307]# vim my.cnf

# 添加如下内容:
[mysqld]
plugin-load = rpl_semi_sync_slave=semisync_slave.so
rpl_semi_sync_slave_enabled=1
[root@WB-BLOG 3307]# ./mysqld restart
(8)重启从库上的 IO 线程
mysql-server-3307> STOP SLAVE IO_THREAD;
Query OK, 0 rows affected (0.00 sec)
ysql-server-3307> START SLAVE IO_THREAD;
Query OK, 0 rows affected (0.00 sec)
(9)查看主库和从库上的半同步复制是否在运行
登录主库查看:
mysql-server-3306> SHOW STATUS LIKE ‘rpl_semi_sync_master_status’;
+—————————–+——-+
| Variable_name | Value |
+—————————–+——-+
| Rpl_semi_sync_master_status | ON |
+—————————–+——-+
登录从库查看:
mysql-server-3307> SHOW STATUS LIKE ‘rpl_semi_sync_slave_status’;
+—————————-+——-+
| Variable_name | Value |
+—————————-+——-+
| Rpl_semi_sync_slave_status | ON |
+—————————-+——-+
1 row in set (0.00 sec)
上述结果表示主库和从库上的半同步复制运行正常。
(10)验证半同步复制是否正常
验证方法:正常在主库上创建一张表,会立刻返回,耗时 0.1s。关闭从库的 io 线程,然后在主库上执行建表操作,会发现,主库上回阻塞 10 秒之后才会返回,而这个时间正好和主库上的 rpl_semi_sync_master_timeout 相同,表示半同步起作用了,主库的 DDL 操作需要等到从库应用完 relaylog 之后才返回;

# 主库执行:
mysql-server-3307> STOP SLAVE IO_THREAD;

# 从库执行:
mysql-server-3306> CREATE TABLE test(id int);
Query OK, 0 rows affected (10.03 sec)

# 查看主库上的 rpl_semi_sync_maser_timeout
mysql-server-3306> SHOW VARIABLES LIKE ‘rpl_semi_sync_master_timeout’;
+——————————+——-+
| Variable_name | Value |
+——————————+——-+
| rpl_semi_sync_master_timeout | 10000 |
+——————————+——-+
至此,MySQL 的半同步复制搭建完成。
4、半同步搭建中常见问题
(1)主从不能正常同步:和主从同步无法正常复制的排查方法相同
(2)不能正常安装半同步插件
原因 1:可能是版本问题
解决办法 1:查看 MySQL 实例的版本,如果版本问题,更换新版本重新安装即可
mysql> SELECT version();
原因 2:MySQL 的安装目录中未包含用于半同步复制的共享库
解决办法 2:找到该版本对应的半同步共享库,然后重新安装
五、全同步复制
同步复制在所有复制方案中最安全,但是性能最差,而且需要使用 DRBD(分布式复制块设备)来完成数据的同步,DRBD 是一种类似于”rsync+inotify”的架构,通常使用较少,几乎不用,此处不做详细介绍。
到此,MySQL 的主从复制介绍完毕,主从复制是一块很大的内容,包括延迟排查,数据一致问题、快速主从搭建及主从复制的高可用,后面会继续写
第 10 章 MySQL 数据库运维之主从复制延迟问题排查
上篇文章介绍了单机环境下的 MySQL 主从异步复制和主从半同步复制的搭建过程。搭建过程很简单,但是在实际使用过程中,更多的是解决问题,本篇文章将介绍一下 MySQL 主从复制中常见的问题以及如何定位问题和如何解决问题。
一、从库复制延迟问题
1、可能的原因如下
(1)主从服务器处于不同的网络之中,由于网络延迟导致;
(2)主从服务器的硬件配置不同,从服务器的硬件配置(包括内存,CPU,网卡等)远低于主服务器;
(3)主库上有大量的写入操作,导致从库无法实时重放主库上的 binlog;
(4)主库上存在着大事务操作或者慢 SQL,导致从库在应用主库 binlog 的过程过慢,形成延迟;
(5)数据库实例的参数配置问题导致,如:从库开启了 binlog,或者配置了每次事务都去做刷盘操作;
2、主从同步延迟问题判断
(1)根据从库上的状态参数判断
mysql-server-3307> SHOW SLAVE STATUS \G
在输出结果中找到 Seconds_Behind_Master 参数,这个参数表示的是从库上的 IO 线程和 SQL 线程相差的时间,然后根据该参数值判断,这个值只是初步判断,不能由这个值来下结论,有如下几种情况:
a、0:表示无延迟,理想状态;
b、NULL:表示从库上的 IO 线程和 SQL 线程中,有某一个线程出现问题,可以再次查看 Slave_IO_Running 和 Slave_SQL_Running 的值是否都为 Yes;
c、大于 0:表示主从已经出现延迟,这个值越大,表示从库和主库之间的延迟越严重;
d、小于 0:这个值在官方文档中没有说明,通常不会出现。如果出现,那恭喜你中奖了,撞见 MySQL 的 bug 了;
(2)根据主从库上面当前应用的二进制日志文件名称或者重放日志的位置来判断
① 同时打开两个 MySQL 的命令行窗口,分别打开主库和从库,在第一个窗口上执行查看主库当前状态的命令
mysql-server-3306> SHOW MASTER STATUS \G
*** 1. row ***
File: mysql-bin.000017
Position: 120
Binlog_Do_DB:
Binlog_Ignore_DB:
Executed_Gtid_Set:
1 row in set (0.00 sec)
② 在第二个从库的命令行窗口执行如下命令
mysql-server-3307> SHOW SLAVE STATUS \G
*** 1. row ***
Slave_IO_State: Waiting for master to send event

Connect_Retry: 60
Master_Log_File: mysql-bin.000017
Read_Master_Log_Pos: 120
Relay_Log_File: relay-log.000016
Relay_Log_Pos: 283
Relay_Master_Log_File: mysql-bin.000017
Slave_IO_Running: Yes
Slave_SQL_Running: Yes

Last_Errno: 0
Last_Error:
Skip_Counter: 0
Exec_Master_Log_Pos: 120
Relay_Log_Space: 613
Until_Condition: None
Until_Log_File:
Until_Log_Pos: 0

Seconds_Behind_Master: 0

Replicate_Ignore_Server_Ids:
Master_Server_Id: 3
Master_UUID: 2dbbf79b-5d9f-11e8-8004-000c29e28409
Master_Info_File: /mysql_data/3307/data/master.info
SQL_Delay: 0
SQL_Remaining_Delay: NULL
③ 比较从库上的 Master_Log_File 和 Relay_Master_Log_File 文件之间是否有差异
a、如果有差异,则说明主从延迟很严重;
b、如果没有差异,则比较 Read_Master_Log_Pos 和 Exec_Master_Log_Pos 的差异,这俩参数分别表示从库当前读取到的主库的二进制日志文件位置点和已经执行到的位置点;
c、如果上述输出都没有差异,可以通过主库上”show master status”和从库上”show slave status”的结果作比较。主要比较主库的”File”和从库的”Master_Log_File”,主库上的”Position”和从库上的”Read_Master_Log_Pos”;
3、主从延迟解决办法
(1)判断是否由于网络导致
方法:测试主从库之间的网络延迟,比如测试 ping 延迟。同时可以检查主从同步的时候是否使用了主库的域名来同步,而域名解析速度可能会特别慢。或者使用其他测试工具;
(2)判断是否由于硬件环境导致
方法:确认主从库的硬件配置是否相差较大,如果配置参数相差较大,可以排查从库上的 CPU,内存,IO 使用率来判断是否因为硬件配置导致;
(3)判断是否在主库上有大量的 DML 操作
方法:可以再主库上通过”show full processlist”命令查看当前正在执行的 sql,查看是否有大量正在执行的 SQL,或者观察主库的 CPU 和内存使用率,判断是否有高并发操作;
(4)判断是否有慢 SQl,可以再主库上临时打开慢 SQL 记录,临时打开方法如下

# 开启慢 SQL 功能并查看是否生效
mysql-server-3306> SET @@GLOBAL.slow_query_log = ON;
mysql-server-3306> SHOW VARIABLES LIKE ‘slow_query_log’;

# 设置慢 SQL 的时间并查看是否生效,单位为 s,表示大于多少秒的 SQL 会被记录
mysql-server-3306> SET @@GLOBAL.long_query_time = 5;
mysql-server-3306> SHOW VARIABLES LIKE ‘long_query_time’;

# 设置慢 SQL 记录日志路径并查看是否生效。注意,这个目录必须对 MySQL 用户有读写权限
mysql-server-3306> SET @@GLOBAL.slow_query_log_file = ‘/mysql_data/mysql-slow.log’;
mysql-server-3306> SHOW VARIABLES LIKE ‘slow_query_log_file’;
(5)检查从服务器参数配置是否合理
① 查看从库是否开启了 binlog 日志,从库上执行如下命令查看
mysql-server-3307> SHOW VARIABLES LIKE ‘log_bin’;
如果开启了 binlog 日志,而且从库未充当其他库的主库时,可以将从库上的 binlog 关闭,否则会增加从库负担,每次重放完成主库的 binlog 还要记录到自身的 binlog
② 查看从库上的 sync_binlog 参数的值,这个参数表示的是事务提交多少次之后,由 MySQL 来将 binlog_cache 中的数据刷新到磁盘,有以下几种值:
0:表示事务提交之后,MySQL 不做刷新 binlog_cache 到磁盘的操作,而是由操作系统来定时自动完成刷盘操作,这种操作对性能损耗最少,但是也最不安全;
n:表示提交 n 次事务之后,由 MySQL 将 binlog_cache 中的数据刷新到磁盘,如果开启,会对性能有一定程度的损耗。所以,从库上如果延迟很严重,可以考虑将该参数的值设为 0;
mysql-server-3307> SET @@GLOBAL.sync_binlog = 0;
mysql-server-3307> SHOW VARIABLES LIKE ‘sync_binlog’;
+—————+——-+
| Variable_name | Value |
+—————+——-+
| sync_binlog | 0 |
+—————+——-+
1 row in set (0.00 sec)
③ 如果从库中要同步的数据库使用的是 InnoDB 存储引擎,可以查看 innodb_flush_log_at_trx_commit 参数。这个参数表示事务执行完成之后,多久的频率刷新一次日志到磁盘上,可用的值有如下几种:
0:表示 MySQL 会将日志缓冲区中的数据每秒一次地写入日志文件中,并且日志文件的刷盘操作同时进行。该模式下在事务提交的时候,不会主动触发写入磁盘的操作,效率最搞,但是安全性也比较低,可能会丢失数据;
1:每一次事务提交都需要把日志写入磁盘,这个过程是特别耗时的操作;
2:每一次事务提交之后,不会自动触发日志刷盘的操作,而是由操作系统来决定什么时候来做刷新日志的操作,在操作系统挂了的情况下才会丢失数据;
如果在主从延迟非常严重的情况下,可以将从库的该参数设置为 0,以提高从库上重放主库二进制日志的效率。
mysql-server-3307> SET @@GLOBAL.innodb_flush_log_at_trx_commit = 0;
mysql-server-3307> SHOW VARIABLES LIKE ‘innodb_flush_log_at_trx_commit’;
+——————————–+——-+
| Variable_name | Value |
+——————————–+——-+
| innodb_flush_log_at_trx_commit | 0 |
+——————————–+——-+
1 row in set (0.00 sec)
注意:上述设计到修改 MySQL 数据库实例的操作中,修改之后会立刻生效,但是重启实例之后,会失效,如果要永久修改,则需要编辑 mysql 配置文件,然后重启。
至此,主从复制延迟的常见原因就介绍完毕,还有更多其他原因需要实际问题实际解决,此处并未提到
第 11 章 MySQL 数据库运维之数据备份 01
上篇文章介绍了 MySQL 主从复制中常见问题排查。在主从复制架构搭建完成之后,需要定期对数据进行备份,本篇文章开始就来介绍 MySQL 数据库的数据备份与恢复,由于备份及恢复内容较多,分多个小节介绍,本小节先来介绍 MySQL 数据库备份的相关概念及 mysqldump 逻辑备份工具。
1、备份类型
(1)按照备份后的文件来划分
a、物理备份:直接备份数据库文件,常用的有 LVM 逻辑卷备份,或者直接拷贝压缩数据库数据目录
b、逻辑备份:使用备份工具将数据库中的数据导出为数据库 sql 脚本或者数据文件
(2)按照是否停止数据库服务划分
a、冷备:备份期间需要停止数据库服务,会造成不可读不可写
b、温备:备份期间不需要停止数据库服务,但是需要锁表,故只能读,不可写
c、热备:备份期间,数据库服务完全不受影响,可读可写
(3)按照备份的周期和备份内容划分
a、完全备份:一次性备份数据库实例中的所有数据
b、增量备份:如果刚进行过一次完全备份,则增量备份就指的是全备之后到当前时间点之间增加的数据;
如果上次为增量备份,则增量备份指的内容是上次增量之后到当前时间点之间增加的数据
c、差异备份:仅备份上次全量备份之后发生变化的数据,由于比增量备份耗费的磁盘空间更多,故用的比较少
2、备份策略
(1)全量备份 + 增量备份
(2)全量备份 + 差异备份
3、备份工具介绍
(1)mysqldump:基于 MySQL 客户端的一个逻辑备份工具,可实现温备,可以使用 -u,-p,-h 等选项备份远程数据库上的数据
(2)mysqlhotcopy:基于 MySQL 客户端的一个物理工具,只可冷备,备份过程中需指定 -u,-p,-h,-S 等基本参数,这些基本参数的含义和 mysql 命令对应的参数含义相同
(3)lvm2:基于 lvm 的物理备份工具,由于是通过对逻辑卷做快照来实现的,所以备份速度极快,可在瞬间完成,可粗略认为是热备
(4)xtrabackup:Percona 旗下的一块开源 MySQL 备份工具,可以实现在线热备和温备
除此之外还有一些其他备份工具,如 mysqldumper,PhpMyAdmin 等,感兴趣可自己查资料学习。
4、mysqldump 逻辑备份基本操作
(1)用法
mysqldump [OPTIONS] database [tables]
OR mysqldump [OPTIONS] –databases [OPTIONS] DB1 [DB2 DB3…]
OR mysqldump [OPTIONS] –all-databases [OPTIONS]
(2)常用参数选项 OPTIONS
-A,–all-databases:该选项表示备份实例中的所有数据库
-B,–databases:指定要备份的数据库名称,后面可以同时跟多个数据库
-E,–events:表示备份过程中包括数据库中的事件
-F,–flush-logs:表示备份完成之后刷新日志,滚动日志点,如果没有开启二进制日志,使用该选项会提示错误
–flush-privileges:表示备份最新的权限表数据
–hex-blob:表示备份过程中包括数据库中的二进制数据(BINARY,VARBINARY,BLOG)
-x,–lock-all-tables:该选项的作用是在备份过程中锁定所有的表,通常用于备份 MyISAM 存储引擎类型的表数据
–single-transaction:该选项表示备份过程中保证数据的一致性,使用事务隔离状态和一致性快照保证,目前只支持 InnoDB 类型的表
注:–lock-all-tables 选项和–single-transaction 选项同时只能用一个
–master-data=N:该选项用来设置在导出的数据中是否包括对二进制日志文件和日志点的记录,常用的值有:1 和 2
1:表示在导出的 sql 文件中包括了”change master to master_log_file=’’,master_log_pos=N”内容,用来做快速主从复制
2:表示在导出的 sql 文件中不包括”change master to master_log_file=’’,master_log_pos=N”内容
-t,–no-create-info:表示只备份数据,不备份表的创建信息
-d,–no-data:表示只备份表结构,不备份任何数据
-R,–routines:表示备份中同时备份存储过程和函数
–tables:如果不需要备份整个库,只需要备份部分表,可以使用该选项指定
–triggers:表示备份中同时备份触发器
-u:指定完成备份操作的用户名
-p:指定完成备份操作的用户密码
-h:指定完成备份操作的域名或者 ip 地址
-P:指定完成备份操作的端口号。注意,是大写的 P
(3)示例
示例 1:完成 127.0.0.1 服务器上 3306 数据库实例的全量备份
[root@WB-BLOG ~]# mkdir -pv /backup/
[root@WB-BLOG ~]# mysqldump -uroot -proot -h127.0.0.1 -P3306 -S /tmp/mysql.sock –all-databases –routines –triggers –single-transaction –events –master-data=2 | gzip > /backup/full_db_date +%F.sql.gz

# 检查文件是否正常,这步很很很重要。有时候由于一些原因,备份文件生成了,但是打不开,所以务必检查
[root@WB-BLOG ~]# gzip -d /backup/full_db_2018-06-18.sql.gz
[root@WB-BLOG ~]# less /backup/full_db_2018-06-18.sql
示例 2:使用 root 用户备份 127.0.0.1 服务器上 3306 实例中的 test 库,并完成压缩放在 /backup 目录下
[root@WB-BLOG ~]# mysqldump -uroot -proot -h127.0.0.1 -P3306 -S /tmp/mysql.sock –databases test –routines –triggers –single-transaction –events –master-data=2 | gzip > /backup/test-date +%F.sql.gz
[root@WB-BLOG ~]# ls /backup/test-2018-06-18.sql.gz

# 查看是否备份成功
[root@WB-BLOG ~]# gzip -d /backup/test-2018-06-18.sql.gz
[root@WB-BLOG ~]# less /backup/test-2018-06-18.sql
示例 3:备份 127.0.0.1 服务器上 3306 实例中的 test 库中的 user 表
[root@WB-BLOG ~]# mysqldump -uroot -proot -h127.0.0.1 -P3306 -S /tmp/mysql.sock –databases test –tables user –routines –triggers –master-data=2 –events| gzip > /backup/Db-test_Tb-user_date +%F.sql.gz
[root@WB-BLOG ~]# ls /backup/Db-test_Tb-user_2018-06-18.sql.gz

# 查看是否备份成功
[root@WB-BLOG ~]# gzip -d /backup/Db-test_Tb-user_2018-06-18.sql.gz
[root@WB-BLOG ~]# less /backup/Db-test_Tb-user_2018-06-18.sql
示例 4:备份 127.0.0.1 服务器上 3306 实例中的 mysql 库的表结构,不备份数据
[root@WB-BLOG ~]# mysqldump -uroot -proot -h127.0.0.1 -S /tmp/mysql.sock -P3306 –databases mysql –no-data –triggers –routines –master-data=2 –events| gzip > /backup/mysql_table_frame_date +%F.sql.gz

# 查看是否备份成功
[root@WB-BLOG ~]# gzip -d /backup/mysql_table_frame_2018-06-18.sql.gz
[root@WB-BLOG ~]# less /backup/mysql_table_frame_2018-06-18.sql
示例 5:备份 127.0.0.1 服务器上 3306 实例中的 test 库中所有表的数据,不备份表结构
[root@WB-BLOG ~]# mysqldump -uroot -proot -h127.0.0.1 -S /tmp/mysql.sock -P3306 –databases test –no-create-info –triggers –routines –master-data=2 –events| gzip > /backup/test_table_data_date +%F.sql.gz

# 查看是否备份成功
[root@WB-BLOG ~]# gzip -d /backup/test_table_data_2018-06-18.sql.gz
[root@WB-BLOG ~]# less /backup/test_table_data_2018-06-18.sql
实例 6:在 windows 上远程备份 192.168.0.10 服务器上的 3306 数据库实例中的 mysql 数据库
前提:需要在 0.10 服务器上授予 windows 主机的远程连接权限,授权操作可查看之前文章,此处略
D:\SoftWare\mysql-5.7.21\bin>mysqldump -uroot -proot -h127.0.0.1 -P3306 –databases mysql > D:/remote_mysql.sql

# 查看 D 盘根目录,发现已经备份完成,查看备份文件也正常
至此,第一小节 MySQL 备份相关概念及 mysqldump 逻辑备份工具介绍完毕,下一篇文章将继续介绍 MySQL 的物理备份工具 Xtrabackup
第 12 章 MySQL 数据库运维之数据备份 02
上篇文章介绍了一下 MySQL 数据库逻辑备份第一部分 mysqldump 的使用,本篇文章将继续介绍 MySQL 数据库逻辑备份第二部分 Xtrabackup 工具的操作过程!
1、Xtrabackup 介绍
Xtrabackup 是有 Percona 公司发行的一款数据库物理备份工具,可以对 InnoDB 存储引擎的表实现在线热备,同时可以对数据库进行全备和增量备份以及增量恢复。
2、下载及安装
(1)下载地址:https://www.percona.com/downl... ,根据自身的操作系统选择版本,版本如果不对应,会安装失败,本人使用的是 CentOS6.9_X64 的操作系统,下载的版本为:percona-xtrabackup-24-2.4.8-1.el6.x86_64.rpm
(2)安装依赖库 perl-DBD-MySQL,如果已经安装,直接进入下一步

# 查看是否安装
[root@WB-BLOG ~]# rpm -qa | grep perl-DBD-MySQL

# 如果没有安装,使用下面的命令安装 perl-DBD-MySQL
[root@WB-BLOG ~]# yum install -y per-DBD-MySQL
(3)安装 libev 依赖库
a、下载地址:在 http://rpmfind.net/ 直接搜索 libev,然后选择和自己操作系统对应的 rpm 安装包
b、注意:此处也需要注意版本,否则会安装失败,本人用的是 CentOS6.9_X64 的操作系统,下载的 rpm 安装包版本为:libev-4.04-2.el6.x86_64.rpm
c、安装:
[root@WB-BLOG ~]# rpm -ivh libev-4.04-2.el6.x86_64.rpm
(4)安装 perl-Digest-MD5 依赖库,否则在备份过程中会出现错误
[root@WB-BLOG ~]# yum -y install perl-Digest-MD5
(5)安装 Xtrabackup
[root@WB-BLOG ~]# rpm -ivh percona-xtrabackup-24-2.4.8-1.el6.x86_64.rpm
3、使用方法
语法:innobackupex [OPTIONS] /path
常用参数 OPTIONS 说明:
—————————————基本备份参数———————————
–user:指定执行备份操作的用户名
–password:指定执行备份操作的用户密码
–host:指定数据库所在服务器的域名或者 IP 地址
–port:指定实例端口
–socket:指定 socket 文件,以便备份进程登陆 mysql
–databases:指定要备份的数据库,如果需要备份多个数据库,多个数据库之间使用空格隔开,如:–databases=”test1 test2”
–include:指定要备份的数据库,可以指定多个库”db1.|db2.
还可以指定要备份的表,可以指定多个表”db1.table01|db1.table02|db2.test”
–defaults-file:指定数据库实例的配置文件 my.cnf。注:如果用该参数,必须放在第一个参数位置,否则会提示错误
–apply-log:通过回滚未提交的事务及同步已经提交的事务至数据文件来保证数据文件处于一致性状态
–redo-only:用来保证全备和增量备份的数据文件在恢复前必须先将在重做日志文件中的已提交的事务重做,该参数将会合并全备和增量备份的数据文件,但不包括最后一次增量备份的数据文件
–copy-back:指定操作为数据的恢复,如果不是全备,不可用这个选项,这个选项用于全备的恢复
–slave-info:在主从库备份中使用,用来备份从库的”show slave status”信息
–stream:指定备份文件的输入格式,如:tar
–tmpdir:如果使用 stream=tar 的时候备份,xtrabackup_logfile 可能会放在临时目录 /tmp 下,如果此时数据库中数据写入量特别大的时候,这个文件会特别大,可能会将 /tmp 目录占满,使用这个参数可以指定 tmp 目录
–no-timestamp:该选项后面不用加值,用来指定备份的时候不适用默认的日期目录,而是使用指定好的目录

—————————————增量备份参数———————————
–incremental:指定操作为增量备份操作
–incremental-basedir:指定本次的增量备份是相对于之前的哪一次备份,指定之前备份数据的目录
–incremental-dir:指定增量备份恢复时,增量备份的目录
其他更多参数,可以使用”innobackupex –help”命令查看。
4、备份及恢复示例
示例 1:全量备份 127.0.0.1 上 3306 实例中的所有数据,备份到 /backup 目录下,然后模拟数据丢失恢复
备份:
[root@WB-BLOG ~]# innobackupex –defaults-file=/etc/my.cnf –user=root –password=root –port=3306 –socket=/tmp/mysql.sock /backup/

# 上述命令执行完成之后,打印 completed ok. 然后查看是否备份成功,很重要
[root@WB-BLOG ~]# ls /backup/
恢复:

# 模拟故障,删除数据目录
[root@WB-BLOG ~]# rm -rf /mnt/mydata/data/*

# 杀掉 mysql 的服务进程
[root@WB-BLOG ~]# killall mysqld

# 应用 log 日志,目的是为了回滚未提交的事务及同步已经提交的事务至数据目录,保证数据的一致性
[root@WB-BLOG ~]# innobackupex –apply-log /backup/2018-05-29_10-27-53/

# 恢复数据,指定配置文件,xtrabackup 会将数据文件拷贝到原来的数据目录中
[root@WB-BLOG ~]# innobackupex –defaults-file=/etc/my.cnf –copy-back /backup/2018-05-29_10-27-53/

# 拷贝完成之后,权限会有问题,需要重新授权
[root@WB-BLOG ~]# chown -R mysql:mysql /mnt/mydata/data/

# 重新启动数据库来验证是否恢复正常
[root@WB-BLOG ~]# service mysqld start
示例 2:全量备份 127.0.0.1 服务器上 3306 实例的所有数据,并备份到目录 /backup/20180619 目录下,然后恢复
备份:
[root@WB-BLOG ~]# mkdir -pv /backup/20180619
[root@WB-BLOG ~]# innobackupex –user=root –password=root –host=127.0.0.1 –port=3306 –socket=/tmp/mysql.sock –no-timestamp /backup/20180619/
[root@WB-BLOG ~]# ls /backup/20180619/
恢复,处理过程和示例 1 相同,不再解释:
[root@WB-BLOG ~]# rm -rf /mnt/mydata/data/*
[root@WB-BLOG ~]# killall mysqld
[root@WB-BLOG ~]# innobackupex –apply-log /backup/20180619/
[root@WB-BLOG ~]# innobackupex –defaults-file=/etc/my.cnf –copy-back /backup/20180619/
[root@WB-BLOG ~]# chown -R mysql:mysql /mnt/mydata/data/
[root@WB-BLOG ~]# service mysqld start
示例 3:备份 127.0.0.1 服务器上 3306 实例中的 test1 和 test2 数据库,并备份到 /backup/part_db/ 目录下,然后恢复
备份:
[root@WB-BLOG ~]# mkdir -pv /backup/part_db
[root@WB-BLOG ~]# innobackupex –defaults-file=/etc/my.cnf –user=root –password=root –host=127.0.0.1 –port=3306 –socket=/tmp/mysql.sock –databases=”test1 test2” –no-timestamp /backup/part_db/
[root@WB-BLOG ~]# ls /backup/part_db/
恢复:

# 模拟数据库丢失
[root@WB-BLOG ~]# mysql -uroot -proot -h127.0.0.1 -P3306 -e “drop database test1”
[root@WB-BLOG ~]# mysql -uroot -proot -h127.0.0.1 -P3306 -e “drop database test2”

# 应用 log 日志,使未提交的事务回滚,已经提交的数据同步到数据目录中
[root@WB-BLOG ~]# innobackupex –apply-log /backup/part_db/

# 由于是部分数据库的恢复,所以不能使用–copy-back 选项,所以需要使用拷贝的方法
[root@WB-BLOG ~]# cp -r /backup/part_db/test{1,2}/ /mnt/mydata/data/

# 授权
[root@WB-BLOG ~]# chown -R mysql:mysql /mnt/mydata/data/test{1,2}

# 重启数据库
[root@WB-BLOG ~]# servie mysqld restart
示例 4:增量备份 127.0.0.1 服务器上 3306 实例中的所有库,第一次增量备份到目录 /backup/increment01,第二次增量备份到目录 /backup/increment02,全量备份到 /backup/allback 然后恢复
备份:

# 创建全量备份和增量备份目录
[root@WB-BLOG ~]# mkdir -pv /backup/allback
[root@WB-BLOG ~]# mkdir -pv /backup/increment{01,02}

# 完成一次全量备份
[root@WB-BLOG ~]# innobackupex –defaults-file=/etc/my.cnf –user=root –password=root –host=127.0.0.1 –port=3306 –socket=/tmp/mysql.sock –no-timestamp /backup/allback/

# 完成第一次增量备份,此时可能会有新数据写入
[root@WB-BLOG ~]# innobackupex –defaults-file=/etc/my.cnf –user=root –password=root –host=127.0.0.1 –port=3306 –socket=/tmp/mysql.sock –no-timestamp –incremental-basedir=/backup/allback/ –incremental /backup/increment01/

# 完成第二次全量备份,此时可能会有新数据写入
[root@WB-BLOG ~]# innobackupex –defaults-file=/etc/my.cnf –user=root –password=root –host=127.0.0.1 –port=3306 –socket=/tmp/mysql.sock –no-timestamp –incremental-basedir=/backup/increment01/ –incremental /backup/increment02/

# 查看两次备份的数据目录是否正常
[root@WB-BLOG ~]# ls /backup/increment0{1,2}
恢复:

# 模拟数据丢失
[root@WB-BLOG ~]# rm -rf /mnt/mydata/data/*
[root@WB-BLOG ~]# killall mysqld

# 完成全备的 log 日志应用
[root@WB-BLOG ~]# innobackupex –defaults-file=/etc/my.cnf –incremental –apply-log –redo-only /backup/allback/

# 完成第一次增量中 log 日志的应用
[root@WB-BLOG ~]# innobackupex –defaults-file=/etc/my.cnf –incremental –apply-log –redo-only /backup/allback/ –incremental-dir=/backup/increment01/

# 完成第二次增量中 log 日志的应用
[root@WB-BLOG ~]# innobackupex –defaults-file=/etc/my.cnf –incremental –apply-log –redo-only /backup/allback/ –incremental-dir=/backup/increment02/

# 两次增量都合并到全量中之后,再应用全量中的 log 日志
[root@WB-BLOG ~]# innobackupex –defaults-file=/etc/my.cnf –apply-log /backup/allback/

# 最后全量恢复全备文件夹中的数据
[root@WB-BLOG ~]# innobackupex –defaults-file=/etc/my.cnf –copy-back /backup/allback/

# 重新授权
[root@WB-BLOG ~]# chown -R mysql:mysql /mnt/mydata/data/

# 然后启动数据库服务,然后验证数据是否正确
[root@WB-BLOG ~]# service mysqld start
示例 5:压缩备份 127.0.0.1 服务器上的 3306 实例中的 test1 库到 /backup/test1_data,然后恢复
备份:

# 创建备份目录
[root@WB-BLOG ~]# mkdir -pv /backup/test1_data

# 压缩备份
[root@WB-BLOG ~]# innobackupex –defaults-file=/etc/my.cnf –user=root –password=root –host=127.0.0.1 –port=3306 –socket=/tmp/mysql.sock –compress –no-timestamp –databases=”test1” /backup/test1_data/
恢复:

# 模拟删掉数据库 test1
[root@WB-BLOG ~]# mysql -uroot -proot -h127.0.0.1 -P3306 -e “drop database test1”

# 恢复,首先解压缩
[root@WB-BLOG ~]# innobackupex –decompress /backup/test1_data/

# 注意:如果提示如下错误,则需要安装 qpress 工具,下载地址:ftp://ftp.pbone.net/mirror/ftp5.gwdg.de/pub/opensuse/repositories/home:/AndreasStieger:/branches:/Archiving/CentOS_CentOS-6/x86_64/qpress-1.1-8.3.x86_64.rpm,使用 rpm 命令安装
innobackupex version 2.4.8 based on MySQL server 5.7.13 Linux (x86_64) (revision id: 97330f7)
180529 12:29:01 [01] decompressing ./ibdata1.qp
sh: qpress: command not found
cat: write error: Broken pipe
Error: thread 0 failed.
[root@WB-BLOG ~]# innobackupex –apply-log /backup/test1_data

# 拷贝备份数据
[root@WB-BLOG ~]# cp -r /backup/test1_data/test1/ /mnt/mydata/data/
[root@WB-BLOG ~]# chown -R mysql:mysql /mnt/mydata/data/test1/

# 重启数据库然后验证数据是否正确
[root@WB-BLOG ~]# service mysqld restart

至此,Xtrabackup 物理备份工具介绍完毕,常见的用法可以参考上述的示例。另外,该工具还有其他高级用法,使用较少,此处暂未介绍。
第 13 章 MySQL 数据库运维之数据备份 03
上篇文章介绍了一下 MySQL 的数据备份与恢复第二部分内容,即使用 Xtrabackup 完成 MySQL 数据库的物理备份与恢复,但是 Xtrabackup 主要是针对于 InnoDB 表引擎格式的备份,其他存储引擎并不适用。本篇文章将介绍使用 LVM 完成 MySQL 的数据备份和恢复!
一、LVM 简单介绍
1、定义
LVM,全称为:Logical Volume Manager。意思是逻辑卷管理,是 Linux 环境下对磁盘分区进行管理的一种机制。早期的磁盘分区在分完区之后无法改变分区的大小,但是通常使用磁盘之前又不能对需要的磁盘容量进行准确评估,可能会造成磁盘不够用或者磁盘浪费等问题,而 LVM 可以动态创建和修改磁盘大小,可以有效地解决这个问题。
2、重要概念
(1)PV(Physical Volume):表示物理卷,在逻辑卷管理系统最底层,可为整个物理硬盘或实际物理硬盘上的分区;
(2)VG(Volume Group):表示卷组,建立在物理卷上,一个卷组中至少要包括一个物理卷,卷组建立后可动态的添加卷到卷组中,一个逻辑卷管理系统工程中可有多个卷组;
(3)LV(Logical Volume):逻辑卷建立在卷组基础上,卷组中未分配空间可用于建立新的逻辑卷,逻辑卷建立后可以动态扩展和缩小空间;
(4)PE(Physical Extent):物理区域是物理卷中可用于分配的最小存储单元,物理区域大小在建立卷组时指定,一旦确定不能更改,同一卷组所有物理卷的物理区域大小需一致,新的 PV 加入到 VG 后,PE 的大小自动更改为 VG 中定义的 PE 大小;
(5)LE(Logical Extent):逻辑区域是逻辑卷中可用于分配的最小存储单元,逻辑区域的大小取决于逻辑卷所在卷组中的物理区域的大小;
更多内容可以参考工具书《鸟哥的 Linux 私房菜》,里面有详细介绍。
3、各部分组成结构

二、创建逻辑卷并备份
1、备份前提
(1)待备份的 MySQL 数据必须在在逻辑卷上
(2)如果是 InnoDB 存储引擎表,为了保证备份之后的数据一致性,需要事务日志和数据都在同一个卷上,因为同一时刻只能对一个逻辑卷做快照
2、备份过程
(1)首先需要准备一块磁盘或者一个分区,此处演示使用 VMware 新加磁盘的办法,首先关闭 Vmware,具体操作过程如下

到此,磁盘添加完毕,启动 Vmware 虚拟机。
(2)使用如下命令查看新添加的磁盘
[root@WB-BLOG ~]# fdisk -l

(3)使用刚才添加的磁盘来创建物理卷
[root@WB-BLOG ~]# pvcreate /dev/sdb
Physical volume “/dev/sdb” successfully created

# 查看创建的物理卷
[root@WB-BLOG ~]# pvdisplay
“/dev/sdb” is a new physical volume of “10.00 GiB”
— NEW Physical volume —
PV Name /dev/sdb

(4)根据物理卷来创建卷组
[root@WB-BLOG ~]# vgcreate mygroup /dev/sdb
Volume group “mygroup” successfully created
参数解释:
mygroup:表示卷组的名称
/dev/sdb:表示使用哪个物理卷来创建卷组

# 查看创建的卷组
[root@WB-BLOG ~]# vgdisplay
— Volume group —
VG Name mygroup

(5)根据上一步创建的卷组来创建逻辑卷
[root@WB-BLOG ~]# lvcreate -n mysqldata –size 3G mygroup
Logical volume “mysqldata” created.
参数解释:
-n:表示要创建的逻辑卷的名称
–size:表示要创建的逻辑卷的大小
mygroup:表示使用哪个卷组来创建逻辑卷

# 查看创建的逻辑卷
[root@WB-BLOG ~]# lvdisplay
— Logical volume —
LV Path /dev/mygroup/mysqldata
LV Name mysqldata
VG Name mygroup
LV UUID qHJyK4-eMUF-gQku-ojkC-j7Tr-hayA-UFsq5J
(6)格式化刚才创建的逻辑卷,创建完逻辑卷之后,逻辑卷的默认磁盘路径为 /dev/ 卷组名称 / 逻辑卷名称,通过 lvdisplay 命令输出结果中的”LV Path”也可以看到
[root@WB-BLOG ~]# mkfs.ext4 /dev/mygroup/mysqldata
(7)挂载逻辑卷到一个目录上

# 使用 blkid 查看设备的 UUID 编号
[root@WB-BLOG ~]# blkid /dev/mygroup/mysqldata
/dev/mygroup/mysqldata: UUID=”46483472-1c46-4af5-8844-d39fd653d57d” TYPE=”ext4”

# 上述输出的 UUID 为设备的 UUID 号,可以使用该 ID 编号将这个逻辑卷挂载到某个目录上
[root@WB-BLOG ~]# mkdir -pv /mnt/
[root@WB-BLOG ~]# vim /etc/fstab

# 在末尾加入如下一行,表示将 UUID 为 4648…的逻辑卷设备挂载到 /mnt 目录,然后保存退出
UUID=”46483472-1c46-4af5-8844-d39fd653d57d” /mnt ext4 defaults 0 0

# 让操作系统读取 fstab 文件内容,并从新执行挂载
[root@WB-BLOG ~]# mount -a

# 查看逻辑卷是否挂载成功,看到如下一行表示挂载成功
[root@WB-BLOG ~]# df -h
/dev/mapper/mygroup-mysqldata 2.9G 4.5M 2.8G 1% /mnt
(8)在 /mnt 目录下创建 MySQL 的数据目录 data
[root@WB-BLOG ~]# mkdir -pv /mnt/mydata/data/
(9)修改 MySQL 配置文件和启动脚本中的 datadir 目录和 binlog 日志路径到 data 目录下
[root@WB-BLOG ~]# vim /etc/my.cnf

# 修改如下内容
datadir = /mnt/mydata/data
log_bin = /mnt/mydata/data/mysql-bin
binlog_format = mixed
log_bin_index = /mnt/mydata/data/mysql-bin-index

[root@WB-BLOG ~]# vim /etc/init.d/mysqld

# 修改如下内容
datadir=/mnt/mydata/data

(10)授权,并重新初始化 MySQL 数据库到 /mnt/mydata/data 目录下
[root@WB-BLOG ~]# chown -R mysql:mysql /mnt/mydata/data/
[root@WB-BLOG ~]# cd /usr/local/mysql-5.6.39/scripts/
[root@WB-BLOG scripts]# ./mysql_install_db –user=mysql –group=mysql –datadir=/mnt/mydata/data/ –basedir=/usr/local/mysql-5.6.39
(11)启动数据库,查看是否正常
[root@WB-BLOG scripts]# service mysqld start
至此,MySQL 的数据目录已经在 LVM 逻辑卷上。
(12)假如实例已经使用了一段时间,新的数据已经在 LVM 逻辑卷中了,现在需要备份。则可以通过创建逻辑卷快照完成 MySQL 的数据备份
[root@WB-BLOG scripts]# lvcreate –snapshot /dev/mygroup/mysqldata -n data-snap –size 1G –permission r
Logical volume “data-snap” created.

# 以上命令表示使用 /dev/mygroup/mysqldata 逻辑卷创建了一个名称为 data-snap 大小为 1G 的只读快照
参数解释:
–snapshot:表示创建逻辑卷快照
-n:指定快照名称
–size:指定快照的大小
–permission:指定快照对使用者的操作权限,上述权限为只读

# 查看是否备份成功,需要将这个快照卷挂载到某个目录上查看
[root@WB-BLOG scripts]# mount /dev/mygroup/mysqldata /tmp/
[root@WB-BLOG scripts]# ls /tmp/mydata/data/
(13)备份完成之后,可以将备份的数据打包压缩,然后移除逻辑卷快照

# 打包数据
[root@WB-BLOG scripts]# tar zcvf mysql_data_date +%F.tar.gz /tmp/mydata/data/

# 移除逻辑卷快照,可以使用 lvdisplay 命令查看到刚才创建的逻辑卷快照的名称
[root@WB-BLOG scripts]# lvremove /dev/mygroup/data-snap
Do you really want to remove active logical volume data-snap? [y/n]: y
Logical volume “data-snap” successfully removed
(14)数据恢复,使用刚才打包的备份文件完成数据的恢复

# 模拟数据丢失(删库!注意千万不要正式环境尝试…)
[root@WB-BLOG scripts]# rm -rf /mnt/mydata/data/*

# 停止 mysqld 进程
[root@WB-BLOG scripts]# killall mysqld
[root@WB-BLOG scripts]# tar xf mysql_data_2018-05-30.tar.gz

# 移动数据并授权
[root@WB-BLOG scripts]# mv tmp/mydata/data/* /mnt/mydata/data/
[root@WB-BLOG scripts]# chown -R mysql:mysql /mnt/mydata/data/

# 启动数据库
[root@WB-BLOG scripts]# service mysqld start
(15)登录之后检查数据是否还原
至此,基于 LVM 的 MySQL 数据备份与恢复介绍完毕,下片文章开始将结合 MySQL 的二进制日志介绍数据的全量和增量恢复
来源链接:https://segmentfault.com/u/xiarihanbing/articles
第 14 章 关于 MySQL 的知识点与面试常见问题
14.1 书籍推荐
《高性能 MySQL : 第 3 版》
14.2 文字教程推荐
MySQL 教程(菜鸟教程)
MySQL 教程(易百教程)
14.3 视频教程推荐
基础入门: 与 MySQL 的零距离接触 - 慕课网
Mysql 开发技巧: MySQL 开发技巧(一)  MySQL 开发技巧(二)  MySQL 开发技巧(三)
Mysql5.7 新特性及相关优化技巧: MySQL5.7 版本新特性  性能优化之 MySQL 优化
MySQL 集群(PXC)入门  MyCAT 入门及应用
14.4 常见问题总结
• ①存储引擎
MySQL 常见的两种存储引擎:MyISAM 与 InnoDB 的爱恨情仇
• ②字符集及校对规则
字符集指的是一种从二进制编码到某类字符符号的映射。校对规则则是指某种字符集下的排序规则。Mysql 中每一种字符集都会对应一系列的校对规则。
Mysql 采用的是类似继承的方式指定字符集的默认值,每个数据库以及每张数据表都有自己的默认值,他们逐层继承。比如:某个库中所有表的默认字符集将是该数据库所指定的字符集(这些表在没有指定字符集的情况下,才会采用默认字符集) PS:整理自《Java 工程师修炼之道》
详细内容可以参考: MySQL 字符集及校对规则的理解
• ③索引相关的内容(数据库使用中非常关键的技术,合理正确的使用索引可以大大提高数据库的查询性能)
  Mysql 索引使用的数据结构主要有 BTree 索引 和 哈希索引 。对于哈希索引来说,底层的数据结构就是哈希表,因此在绝大多数需求为单条记录查询的时候,可以选择哈希索引,查询性能最快;其余大部分场景,建议选择 BTree 索引。
  Mysql 的 BTree 索引使用的是 B 数中的 B+Tree,但对于主要的两种存储引擎的实现方式是不同的。
  MyISAM: B+Tree 叶节点的 data 域存放的是数据记录的地址。在索引检索的时候,首先按照 B+Tree 搜索算法搜索索引,如果指定的 Key 存在,则取出其 data 域的值,然后以 data 域的值为地址读取相应的数据记录。这被称为“非聚簇索引”。
  InnoDB: 其数据文件本身就是索引文件。相比 MyISAM,索引文件和数据文件是分离的,其表数据文件本身就是按 B+Tree 组织的一个索引结构,树的叶节点 data 域保存了完整的数据记录。这个索引的 key 是数据表的主键,因此 InnoDB 表数据文件本身就是主索引。这被称为“聚簇索引(或聚集索引)”。而其余的索引都作为辅助索引,辅助索引的 data 域存储相应记录主键的值而不是地址,这也是和 MyISAM 不同的地方。在根据主索引搜索时,直接找到 key 所在的节点即可取出数据;在根据辅助索引查找时,则需要先取出主键的值,在走一遍主索引。 因此,在设计表的时候,不建议使用过长的字段作为主键,也不建议使用非单调的字段作为主键,这样会造成主索引频繁分裂。 PS:整理自《Java 工程师修炼之道》
详细内容可以参考:
干货:mysql 索引的数据结构
MySQL 优化系列(三)–索引的使用、原理和设计优化
• ④查询缓存的使用
my.cnf 加入以下配置,重启 Mysql 开启查询缓存
query_cache_type=1
query_cache_size=600000
Mysql 执行以下命令也可以开启查询缓存
set global query_cache_type=1;
set global query_cache_size=600000;
如上,开启查询缓存后在同样的查询条件以及数据情况下,会直接在缓存中返回结果。这里的查询条件包括查询本身、当前要查询的数据库、客户端协议版本号等一些可能影响结果的信息。因此任何两个查询在任何字符上的不同都会导致缓存不命中。此外,如果查询中包含任何用户自定义函数、存储函数、用户变量、临时表、Mysql 库中的系统表,其查询结果也不会被缓存。
缓存建立之后,Mysql 的查询缓存系统会跟踪查询中涉及的每张表,如果这些表(数据或结构)发生变化,那么和这张表相关的所有缓存数据都将失效。
缓存虽然能够提升数据库的查询性能,但是缓存同时也带来了额外的开销,每次查询后都要做一次缓存操作,失效后还要销毁。 因此,开启缓存查询要谨慎,尤其对于写密集的应用来说更是如此。如果开启,要注意合理控制缓存空间大小,一般来说其大小设置为几十 MB 比较合适。此外,还可以通过 sql_cache 和 sql_no_cache 来控制某个查询语句是否需要缓存:
select sql_no_cache count(*) from usr;
• ⑤事务机制
关系性数据库需要遵循 ACID 规则,具体内容如下:

  1. 原子性: 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;
  2. 一致性: 执行事务前后,数据保持一致;
  3. 隔离性: 并发访问数据库时,一个用户的事物不被其他事物所干扰,各并发事务之间数据库是独立的;
  4. 持久性: 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库 发生故障也不应该对其有任何影响。
    为了达到上述事务特性,数据库定义了几种不同的事务隔离级别:
    • READ_UNCOMMITTED(未授权读取): 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
    • READ_COMMITTED(授权读取): 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
    • REPEATABLE_READ(可重复读): 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
    • SERIALIZABLE(串行): 最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
    这里需要注意的是:Mysql 默认采用的 REPEATABLE_READ 隔离级别 Oracle 默认采用的 READ_COMMITTED 隔离级别.
    事务隔离机制的实现基于锁机制和并发调度。其中并发调度使用的是 MVVC(多版本并发控制),通过保存修改的旧版本信息来支持并发一致性读和回滚等特性。
    详细内容可以参考: 可能是最漂亮的 Spring 事务管理详解
    • ⑥锁机制与 InnoDB 锁算法
    MyISAM 和 InnoDB 存储引擎使用的锁:
    o MyISAM 采用表级锁(table-level locking)。
    o InnoDB 支持行级锁(row-level locking) 和表级锁, 默认为行级锁
    表级锁和行级锁对比:
    o
     表级锁: Mysql 中锁定 粒度最大 的一种锁,对当前操作的整张表加锁,实现简单,资源消耗也比较少,加锁快,不会出现死锁。其锁定粒度最大,触发锁冲突的概率最高,并发度最低,MyISAM 和 InnoDB 引擎都支持表级锁。
     行级锁: Mysql 中锁定 粒度最小 的一种锁,只针对当前操作的行进行加锁。 行级锁能大大减少数据库操作的冲突。其加锁粒度最小,并发度高,但加锁的开销也最大,加锁慢,会出现死锁。
    详细内容可以参考:
    Mysql 锁机制简单了解一下
    InnoDB 存储引擎的锁的算法有三种:
    • Record lock:单个行记录上的锁
    • Gap lock:间隙锁,锁定一个范围,不包括记录本身
    • Next-key lock:record+gap 锁定一个范围,包含记录本身
    相关知识点:
  5. innodb 对于行的查询使用 next-key lock
  6. Next-locking keying 为了解决 Phantom Problem 幻读问题
  7. 当查询的索引含有唯一属性时,将 next-key lock 降级为 record key
  8. Gap 锁设计的目的是为了阻止多个事务将记录插入到同一范围内,而这会导致幻读问题的产生
  9. 有两种方式显式关闭 gap 锁:(除了外键约束和唯一性检查外,其余情况仅使用 record lock) A. 将事务隔离级别设置为 RC B. 将参数 innodb_locks_unsafe_for_binlog 设置为 1
    • ⑦大表优化
    当 MySQL 单表记录数过大时,数据库的 CRUD 性能会明显下降,一些常见的优化措施如下:
  10. 限定数据的范围: 务必禁止不带任何限制数据范围条件的查询语句。比如:我们当用户在查询订单历史的时候,我们可以控制在一个月的范围内。;
  11. 读 / 写分离: 经典的数据库拆分方案,主库负责写,从库负责读;
  12. 缓存: 使用 MySQL 的缓存,另外对重量级、更新少的数据可以考虑使用应用级别的缓存;
  13. 垂直分区:
    根据数据库里面数据表的相关性进行拆分。 例如,用户表中既有用户的登录信息又有用户的基本信息,可以将用户表拆分成两个单独的表,甚至放到单独的库做分库。
    简单来说垂直拆分是指数据表列的拆分,把一张列比较多的表拆分为多张表。 如下图所示,这样来说大家应该就更容易理解了。

垂直拆分的优点: 可以使得行数据变小,在查询时减少读取的 Block 数,减少 I/O 次数。此外,垂直分区可以简化表的结构,易于维护。
垂直拆分的缺点: 主键会出现冗余,需要管理冗余列,并会引起 Join 操作,可以通过在应用层进行 Join 来解决。此外,垂直分区会让事务变得更加复杂;

  1. 水平分区:
    保持数据表结构不变,通过某种策略存储数据分片。这样每一片数据分散到不同的表或者库中,达到了分布式的目的。 水平拆分可以支撑非常大的数据量。
    水平拆分是指数据表行的拆分,表的行数超过 200 万行时,就会变慢,这时可以把一张的表的数据拆成多张表来存放。举个例子:我们可以将用户信息表拆分成多个用户信息表,这样就可以避免单一表数据量过大对性能造成影响。

水平拆分可以支持非常大的数据量。需要注意的一点是: 分表仅仅是解决了单一表数据过大的问题,但由于表的数据还是在同一台机器上,其实对于提升 MySQL 并发能力没有什么意义,所以 水品拆分最好分库 。
水平拆分能够 支持非常大的数据量存储,应用端改造也少,但 分片事务难以解决 ,跨界点 Join 性能较差,逻辑复杂。《Java 工程师修炼之道》的作者推荐 尽量不要对数据进行分片,因为拆分会带来逻辑、部署、运维的各种复杂度 ,一般的数据表在优化得当的情况下支撑千万以下的数据量是没有太大问题的。如果实在要分片,尽量选择客户端分片架构,这样可以减少一次和中间件的网络 I/O。
下面补充一下数据库分片的两种常见方案:
 客户端代理: 分片逻辑在应用端,封装在 jar 包中,通过修改或者封装 JDBC 层来实现。 当当网的 Sharding-JDBC 、阿里的 TDDL 是两种比较常用的实现。
 中间件代理: 在应用和数据中间加了一个代理层。分片逻辑统一维护在中间件服务中。 我们现在谈的 Mycat 、360 的 Atlas、网易的 DDB 等等都是这种架构的实现。
详细内容可以参考:
MySQL 大表优化方案

赞赏是最好的支持与鼓励!
-------------本文结束感谢您的阅读-------------