1. MySQL 基础篇
1.1 MySQL 概述
1.1.1 数据库相关概念
数据库(Database, 简称 DB): 存储数据的仓库,数据是有组织的进行存储。
数据库管理系统(Database Management System, 简称 DBMS): 操作和管理数据库的大型软件。
SQL(Structured Query Language, 简称 SQL): 操作关系型数据库的编程语言,定义了一套操作关系型数据库的统一标准。
主流的关系型数据库管理系统
- Oracle
- MySQL
- Microsoft SQL Server
- PostgreSQL
- IBM Db2
- Microsoft Access
- SQLite
- MariaDB
- Microsoft Azure SQL Database
- Hive
1.1.2 MySQL 数据库
MySQL 官方提供了两种不同的版本:
- 社区版(MySQL Community Server): 免费,MySQL 不提供任何技术支持。
- 商业版(MySQL Enterprise Edition): 收费,可以试用 30 天,官方提供技术支持。
MySQL Community Server 下载地址
MySQL数据库的数据模型
关系型数库(Relational Database Management System,简称RDBMS):建立在关系模型基础上,由多张相互连接的二维表组成的数据库。
关系型数据库的特点
- 使用表存储数据,格式统一,便于维护。
- 使用 SQL 语言操作,标准统一,使用方便。
1.2 SQL
1.2.1 SQL通用语法
- SQL语句可以单行或多行书写,以分号结尾。
- SQL语句可以使用空格/缩进来增强语句的可读性。
- MySQL 数据库的SQL语句不区分大小写,关键字建议使用大写。
- 注释,
- 单行注释:–注释内容或 # 注释内容(MySQL特有);
- 多行注释,/* 注释内容 */
1.2.2 SQL分类
- DDL(Data Definition Language): 数据定义语言,用来定义数据库对象(数据库、表、字段)
- DML(Data Manipulation Language):数据操作语言,用来对数据库表中的数据进行增删改
- DQL(Data Query Language): 数据查询语言,用来查询数据库中表的记录。
- DCL(Data Control Language): 数据控制语言,用来创建数据库用户,控制数据库的访问权限
1.3 函数
函数 是指一段可以直接被另一段程序调用的程序或代码。
函数分类
- 字符串函数
- 数值函数
- 日期函数
- 流程函数
字符串函数
- concat(s1,s2,…,sn): 字符串拼接
- lower(str): 将字符串转为小写
- upper(str): 将字符串转为大写
- lpad(str,n,pad): 左填充
- rpad(str,n,pad): 右填充
- trim(str): 去掉字符串头部和尾部的空格。
- substring(str,start, len): 返回从字符串str 从 start 位置起的 len 个长度的字符串。
数值函数
- ceil(x): 向上取整
- floor(x): 向下取整
- mod(x,y): 返回x和y的模
- rand(): 返回 0-1 内的随机数
- round(x,y): 求参数 x 的四舍五入值,保留y位小数。
日期函数
- curdate(): 返回当前日期
- curtime(): 返回当前时间
- now(): 返回当前日期和时间
- year(date): 获取指定 date 的年份
- month(date): 获取指定 date 的月份
- day(date): 获取指定 date 的日期
- date_add(date, interval expr type): 返回一个日期/时间值加上一个时间间隔 expr 后的时间值
- datediff(date1, date2): 返回起始时间 date1 和 结束时间 date2 之间的天数。
流程函数
- if(value, t, f): 如果 value 为true, 则返回 t, 否则返回 f。
- ifnull(value1, value2): 如果 value 不为空, 则返回 value1,否则返回 value2.
- case when [val1] then [res1] … else [default] end: 如果 val1 为 true, 返回 res1,… 否则返回 default 默认值。
- case [expr] when [val1] then [res1] … else [default] end: 如果 expr 的值等于 val1, 返回 res1,否则返回 default 默认值。
1.4 约束
1.4.1 约束概述
约束是作用于表中字段上的规则,用于限制存储在表中的数据。
目的:保证数据库中的数据正确、有效性和完整性。
常见约束:
- 非空约束(not null):限制该字段的数据不能为 null
- 唯一约束(unique):保证该字段的所有数据都是唯一的、不重复的
- 主键约束(primary key): 主键是一行数据的唯一标识,要求非空且唯一。
- 默认约束(default):保存数据时,如果未指定该字段的值,则采用默认值。
- 检查约束(check, 8.0.16版本之后支持):保证字段满足某一个条件
- 外键约束(foreign key): 用来让两张表的数据建立连接,保证数据的一致性和完整性。
约束是作用于表中字段上的,可以在创建表/修改表的时候添加约束。
建表时,添加约束示例:
create table user (
id int primary key auto_increment comment "主键",
name varchar(10) not null unique comment "姓名",
age int check(age > 0 && age < 120) comment "年龄",
status char(1) default '1' comment "状态",
gender char(1) comment "性别"
) comment "用户表";
1.4.2 外键约束
外键:用来让两张表的数据之间建立连接,从而保证数据的一致性和完整性。
添加外键:
-- 新建表时
create table 表名 (
字段名 字段类型,
...
[constraint] [外键名称] foreign key (外键的字段名) references 主表(主表列名)
);
-- 修改表时
alter table 表名 add constraint 外键名称 foreign key (外键字段名) references 主表(主表列名);
外键约束删除/更新行为:
- no action: 当在父表中删除/更新对应记录时,首先检查该记录是否有对应外键,如果有则不允许删除/更新。
- restrict: 当在父表中删除/更新对应记录时,首先检查该记录是否有对应外键,如果有则不允许删除/更新。
- cascade: 当在父表中删除/更新对应记录时,首先检查该记录是否有对应外键,如果有,则也删除/更新外键在子表中的记录。
- set null: 当在父表中删除对应记录时,首先检查该记录是否有对应外键,如果有则设置子表中该外键的值为 null,(这就要求该外键取null)
- set default: 父表有变更时,子表将外键列设置成一个默认值(innodb 不支持)
指定外键约束删除/更新行为:
alter table 表名 add constraint 外键名称 foreign key (外键的字段名) references 主表(主表列名) on update cascade on delete cascade;
1.5 多表查询
1.5.1 多表关系
项目开发中,在进行数据库表设计时,会根据业务需求及业务模块之间的关系,分析并设计表结构,由于业务之间相互关联,所以各个表之间也存在各种联系,基本上分为三种:
- 一对多 (多对一): 实现是在多的一方建立外键,指向一的一方的主键。
- 多对多:实现是建立第三张中间表,中间表至少包含两个外键,分别关联两方主键。
- 一对一:实现是在任意一方加入外键,关联另一方的主键,并且设置外键为唯一的(unique)
1.5.2 多表查询的概述
多表查询是能从多张表中查询数据
笛卡尔积:笛卡尔乘积是指在数学中,两个集合A集合和B集合的所有组合情况。(在多表查询中需要消除无效的笛卡尔积)。
多表查询的分类:
- 连接查询
- 内连接:相当于查询A、B的交集部分
- 外连接
- 左外连接:查询左表所有数据,以及两张表交集部分的数据。
- 右外连接:查询右表所有数据,以及两张表交集部分的数据。
- 自连接:当前表与自身的连接查询,自连接必须使用表别名。
- 子查询
内连接:
-- 隐式内连接
select 字段列表 from 表1,表2 where 条件;
-- 显式内连接
select 字段列表 from 表1 [inner] join 表2 on 连接条件;
外连接:
-- 左外连接
select 字段列表 from 表1 left [outer] join 表2 on 条件;
-- 右外连接
select 字段列表 from 表1 right [outer] join 表2 on 条件;
自连接:
select 字段列表 from 表A 别名A join 表A 别名B on 条件;
联合查询 union、union all:
对于 union 查询,就是把多次查询结果合并起来,形成一个新的查询结果集。
select 字段列表 from 表A ...
union [all]
select 字段列表 from 表B ...;
union all 是直接将查询结果合并,union 是将查询结果合并后去重。
对于联合查询的多张表的列数必须保持一致,字段类型也需要保持一致。
1.5.3 子查询
SQL 语句中嵌套 SELECT 语句,称为嵌套查询,又称子查询。
select * from t1 where col1 = (select column1 from t2);
子查询的外部语句可以是 INSERT/UPDATE/DELETE/SELECT 的任何一个。
根据子查询结果不同,分为:
- 标量子查询:子查询结果为单个值
- 列子查询:子查询结果为一列
- 行子查询:子查询结果为一行
- 表子查询:子查询结果为多行多列
根据子查询出现的位置,分为:
- where 之后
- from 之后
- select 之后
标量子查询: 子查询返回的结果是单个值(数字、字符串、日期等),最简单的形式。
常用的操作符:=、<>、>、>=、<、<=
列子查询:子查询返回的结果是一列(可以是多行)。
常用操作符:
- in:在指定的集合范围内,多选一。
- not in:不在指定的集合范围之内。
- any:子查询返回列表中,有任意一个满足即可。
- some:与any等同,使用some的地方都可以使用 any。
- all:子查询返回列表的所有值都必须满足。
行子查询:子查询返回的结果是一行(可以是多列)。
常用操作符:=、<>、in、not in
表子查询:子查询返回的结果是多行多列。
常用操作符:in
1.6 事务
1.6.1 事务简介
事务是一组操作的集合,它是一个不可分割的工作单位,事务会把所有的操作作为一个整体一起向系统提交或撤销操作请求,即这些操作要么同时成功,要么同时失败。
默认MySQL的事务是自动提交的,也就是说,当执行一条DML语句,MySQL会立即隐式的提交事务。
1.6.2 事务操作
查看/设置事务的提交方式:
select @@autocommit;
-- 0 是手动提交事务;1 是自动提交事务
set @@autocommit=0;
开启事务:
-- 方式一
start transaction;
-- 方式二
begin;
提交事务:
commit;
回滚事务:
rollback;
1.6.3 事务四大特性
- 原子性(Automicity): 事务是不可分割的最小操作单元,要么全部成功,要么全部失败。
- 一致性(Consistency):事务完成时,必须使所有的数据都保持一致状态。
- 隔离性(Isolation):数据库系统提供的隔离机制,保证事务在不受外部并发操作影响的独立环境下运行。
- 持久性(Durability):事务一旦提交或回滚,它对数据库中的数据的改变就是永久的。
1.6.4 并发事务问题
- 脏读:一个事务读到另一个事务还没有提交的数据。
- 不可重复读:一个事务先后读取同一条记录,但两次读取的数据不同。
- 幻读:一个事务按照条件查询数据时,没有对应的数据行,但是在插入数据时,又发现这行数据已经存在,好像出现了"幻影"。
1.6.5 事务隔离级别
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
read uncommitted | ✔ | ✔ | ✔ |
read committed | ✖ | ✔ | ✔ |
repeattable read(默认) | ✖ | ✖ | ✔ |
serializable | ✖ | ✖ | ✖ |
查看事务隔离级别:
select @@transaction_isolation;
设置事务隔离级别:
set [session | global] transaction isolation level { read uncommitted | read committed | repeatable read | serializable}
2. MySQL 进阶篇
2.1 存储引擎
2.1.1 MySQL 体系结构
- 连接层:最上层是一些客户端和链接服务,主要完成一些类似于连接处理、认证授权、及相关的安全方案。服务器也会为安全接入的每个客户端验证它所具有的操作权限。
- 服务层:第二层架构主要完成大多数的核心服务功能,如SQL接口,并完成缓存查询,SQL的分析和优化,部分内置函数的执行。所有跨存储引擎的功能也在这一层实现,如过程、函数等。
- 引擎层:存储引擎真正的负责了MySQL中数据的存储和提取,服务器通过API和存储引擎进行通信。不同的存储引擎具有不同的功能,这样我们可以根据自己的需要,来选取合适的存储引擎。
- 存储层:主要是将数据存储在文件系统之上,并完成与存储引擎的交互。
2.1.2 存储引擎简介
存储引擎就是存储数据、建立索引、更新/查询数据等技术的实现方式。存储引擎是基于表的,而不是基于数据库的,所以存储引擎也可被称为表类型。
在建表时指定存储引擎:
create table 表名(
字段 字段类型 [comment 字段注释]
) engine = InnoDB [comment 表注释];
查看当前数据库支持的存储引擎:
show engines;
2.1.3 存储引擎特点
2.1.3.1 InnoDB 存储引擎
InnoDB 是一种兼顾了高可靠性和高性能的通用存储引擎,在 MySQL 5.5 之后,InnoDB 是默认的 MySQL 存储引擎。
InnoDB 特点:
- DML 操作遵循 ACID 模型,支持事务。
- 行级锁,提高并发访问性能;
- 支持外键 FOREIGN KEY 约束,保证数据的完整性和约束。
InnoDB 文件:
xxx.ibd: xxx代表表名,innoDB引擎的每张表都会对应这样一个表空间文件,存储该表的表结构 (frm、sdi)、数据和索引。
参数:innodb_file_per_table:决定表空间中表的数量。
-- 如果值为 ON,则一张表对应一个表空间。
show variables like 'innodb_file_per_table';
# 查看 ibd 文件中的表结构
ibd2sdi xxx.ibd
InnoDB 逻辑存储结构:
2.1.3.2 MyISAM 存储引擎
MyISAM 是MySQL早期的默认存储引擎。
MyISAM 特点:
- 不支持事务,不支持外键
- 支持表锁,不支持行锁
- 访问速度快
MyISAM 文件:
- xxx.sdi:存储表结构消息,数据格式为json
- xxx.MYD:存储数据
- xxx.MYI:存储索引
2.1.3.3 Memory 存储引擎
Memory 引擎的表数据是存储在内存中的,由于受到硬件问题、或断电问题的影响,只能将这些表作为临时表或缓存使用。
Memory 特点:
- 内存存放
- hash索引(默认)
Memory 文件:
- xxx.sdi: 存储表结构消息
2.1.3.3 InnoDB、MyISAM、Memory 存储引擎对比
特点 | InnoDB | MyISAM | Memory |
---|---|---|---|
存储限制 | 64TB | 有 | 有 |
事务安全 | 支持 | ||
锁机制 | 行锁 | 表锁 | 表锁 |
B+tree 索引 | 支持 | 支持 | 支持 |
Hash 索引 | 支持 | ||
全文索引 | 支持(5.6版本之后) | 支持 | |
空间使用 | 高 | 低 | N/A |
内存使用 | 高 | 低 | 中等 |
批量插入速度 | 低 | 高 | 高 |
支持外键 | 支持 |
2.1.4 存储引擎的选择
在选择存储引擎时,应该根据应用系统的特点选择合适的存储引擎。对于复杂的应用系统,还可以根据实际情况选择多种存储引擎进行组合。
适用场景:
- InnoDB:是 MySQL的默认存储引擎,支持事务、外键。如果应用对事务的完整性有比较高的要求,在并发条件下要求数据的一致性,数据操作除了插入和查询外,还包含很多的更新、删除操作,那么InnoDB的存储引擎是比较合适的选择。
- MyISAM: 如果应用是以读操作和插入操作为主,只有很少的更新和删除操作,并且对事务的完整性、并发性要求不是很高,那么选择这个存储引擎是非常合适的。
- Memory: 将所有数据保存到内存中,访问速度快,通常用于临时表及缓存。Memory的缺陷就是对表的大小有限制,太大的表无法缓存到内存中,而且无法保障数据的安全性。
2.2 索引
2.2.1 索引概述
索引(index) 是帮助 MySQL 高效获取数据的数据结构(有序)。
在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法,这种数据结构就是索引。
索引的优点:
- 提高数据检索效率,降低数据库的IO成本。
- 通过索引列对数据进行排序,降低数据排序的成本,降低 CPU 的消耗。
索引的缺点:
- 索引列也是要占用空间的。
- 索引大大提高了查询效率,同时却也降低更新表的速度,如对表进行 insert、update、delete时,效率降低。
2.2.2 索引结构
MySQL 的索引是在存储引擎层实现的,不同的存储引擎有不同的结构,主要包含以下几种:
- B+Tree 索引:最常见的索引类型,大部分引擎都支持 B+ 树索引。
- Hash 索引:底层数据结构使用哈希表实现的,只有精确匹配索引列的查询才有效,不支持范围查询。
- R-tree(空间索引):空间索引是 MyISAM 引擎的一个特殊索引类型,主要用于地理空间数据类型,通常使用较少。
- Full-text(全文索引):是一种通过建立倒排索引,快速匹配文档的方式。类似于 Lucene、Solr、ES
索引类型在各个存储引擎中的支持情况:
索引 | InnoDB | MyISAM | Memory |
---|---|---|---|
B+tree 索引 | 支持 | 支持 | 支持 |
Hash 索引 | 不支持 | 不支持 | 支持 |
R-tree 索引 | 不支持 | 支持 | 不支持 |
Full-text | 5.6版本之后支持 | 支持 | 不支持 |
2.2.2.1 B+tree 索引结构
二叉树:顺序插入时,会形成一个链表,查询性能大大降低。大数据量情况下,层级较深,检索速度慢。
红黑树:大数据量情况下,层级较深,检索速度慢。
B-Tree (多路平衡查找树):
以一颗最大度数(max-degree)为 5(5阶) 的 b-tree 为例(每个节点最多存储4个key,5个指针):
树的度数指的是一个节点的子节点个数。
B+Tree:
以一颗最大度数(max-degree)为 4(4阶) 的 b+tree 为例:
B+Tree 相对于 B-Tree 的区别:
- 所有数据都会出现在叶子节点
- 叶子节点形成一个单向链表
MySQL 中 B+Tree 的结构:
MySQL 索引数据结构对经典的 B+Tree 进行了优化。在原 B+Tree 的基础上,增加一个指向相邻叶子节点的链表指针,就形成了带有顺序指针的 B+Tree, 提高区间访问性能。
2.2.2.2 Hash 索引结构
哈希索引就是采用一定的 hash 算法,将键值换算成新的 hash 值,映射到对应的槽位上,然后存储在 hash 表中。
Hash 索引的特点:
- Hash 索引只能用于对等比较 (=, in), 不支持范围查询 (between,>,<,…)
- 无法利用索引完成排序操作
- 查询效率高,通常只需要一次检索就可以了,效率通常要高于 B+tree 索引。
存储引擎支持:
在 MySQL 中,支持 Hash 索引的是 Memory 引擎,而 InnoDB 中具有自适应 hash 功能,hash 索引是存储引擎根据 B+Tree索引在指定条件下自动构建的。
2.2.3 索引分类
分类 | 含义 | 特点 | 关键字 |
---|---|---|---|
主键索引 | 针对表中主键创建的索引 | 默认自动创建,只能有一个 | PRIMARY |
唯一索引 | 避免同一表某数据列中的值重复 | 可以有多个 | UNIQUE |
常规索引 | 快速定位特定数据 | 可以有多个 | |
全文索引 | 全文索引查找的是文本中的关键字,而不是比较索引中的值 | 可以有多个 | FULLTEXT |
在 InnoDB 存储引擎中,根据索引的存储形式,又可以分为以下两种:
分类 | 含义 | 特点 |
---|---|---|
聚集索引 (Clustered Index) | 将数据存储和索引放到了一块,索引结构的叶子节点保存了行数据 | 必须有,而且只有一个 |
二级索引 (Secondary Index) | 将数据和索引分开存储,索引结构的叶子节点关联的是对应的主键 | 可以存在多个 |
聚集索引选取规则:
- 如果存在主键,主键索引就是聚集索引。
- 如果不存在主键,将使用第一个唯一(unique)索引作为聚集索引。
- 如果表没有主键,或没有合适的唯一索引,则 InnoDB 会自动生成一个 rowid 作为隐藏的聚集索引。
回表查询: 先查二级索引获取主键,再查聚集索引获取行数据。
2.2.4 索引语法
创建索引:
create [UNIQUE | FULLTEXT] index index_name on table_name (index_col_name, ...);
查看索引:
show index from table_name;
删除索引:
drop index index_name on table_name;
2.2.5 SQL性能分析
2.2.5.1 SQL执行频率
MySQL 客户端连接成功后,通过 show[session|global] status 命令可以提供服务器的状态信息。通过如下指令,可以查看当前数据库的 INSERT、UPDATE、DELETE、SELECT的访问频次。
-- 查询SQL执行频率
show global status like 'Com_______';
2.2.5.2 慢查询日志
慢查询日志记录了所有执行时间超过指定参数(long_query_time, 单位:秒,默认10秒)的所有SQL语句的日志。
-- 查询数据库是否开启慢查询日志
show VARIABLES like 'slow_query_log';
MySQL 的慢查询日志默认没有开启,需要在MySQL的配置文件(/etc/my.cnf)中配置如下信息:
# 开启 MySQL 慢日志查询开关
slow_query_log=1
# 设置慢日志的时间为2秒,SQL语句执行时间超过2秒,就会视为慢查询,记录慢查询日志
long_query_time=2;
配置完毕之后,通过以下指令重新启动MySQL服务器进行测试,查看慢日志文件中记录的信息 /var/lib/mysql/localhost-slow.log
# 重启 MySQL 服务
systemctl restart mysqld.service
2.2.5.3 profile详情
show profiles 能够在做 SQL 优化时帮助我们了解时间耗费到哪里去了,通过 have_profiling 参数,能够看到当前 MySQL 是否支持 profile 操作:
-- 查询是否支持 profile 详情
select @@have_profiling;
-- 查询 profiling 是否开启,1 开启;0 关闭;
select @@profiling;
-- 可以通过 set 语句在 session/global 级别开启 profiling
set progiling = 1;
-- 查看每一条SQL的耗时基本情况
show profiles;
-- 查看指定 query_id 的 SQL 语句各个阶段的耗时情况
show profile for query query_id;
-- 查看指定 query_id 的 SQL 语句 CPU 的使用情况
show profile cpu for query query_id;
2.2.5.4 explain 执行计划
explain 或者 DESC 命令获取 MySQL 如何执行 SELECT 语句的信息,包括在 SELECT 语句执行过程中表如何连接和连接的顺序。
-- 直接在 select 语句之前加上 explain/desc;
explain select 字段列表 from 表面 where 条件;
Explain 执行计划各字段的含义:
- id:select 查询的序列号,表示查询中执行 select 子句或者是操作表的顺序(id相同,执行顺序从上到下;id不同,值越大,越先执行)。
- select_type:表示 select 的类型,常见的取值有 SIMPLE(简单表,即不使用表连接或者子查询)、PRIMARY(主查询,即外层的查询)、UNION(UNION 中的第二个或后面的查询语句)、SUBQUERY(SELECT/WHERE之后包含了子查询)等
- type:表示连接的类型,性能的由好到差的连接类型为 NULL、system、const、eq_ref、ref、range、index、all。
- possible_key:显示可能应用在这张表上的索引,一个或多个。
- Key:实际使用的索引,如果为 NULL,则没有使用索引。
- Key_len:表示索引中使用的字节数,该值为索引字段最大可能长度,并非实际使用长度,在不损失精确性的前提下,长度越短越好。
- rows:MySQL 认为必须要执行查询的行数,在 innoDB 引擎的表中,是一个估计值,可能并不总是准确的。
- filtered:表示返回结果的行数占需读取行数的百分比,filtered 的值越大越好。
- Extra:额外的数据。
2.2.6 索引使用
最左前缀法则
如果索引了多列(联合索引),要遵守最左前缀法则。最左前缀法则指的是查询从索引的最左列开始,并且不跳过索引中的列。
如果跳跃某一列,索引将部分失效(后面的字段索引失效)。
范围查询
联合索引中,出现范围查询(>,<),范围查询右侧列索引失效。
索引列运算
不要在索引列上进行运算操作,索引将失效。
字符串不加引号
字符串类型字段使用时,不加引号,索引将失效。
模糊查询
如果仅仅是尾部模糊匹配,索引将不会失效。如果是头部模糊匹配。索引将失效。
or连接的条件
用or分割开的条件,如果or前的条件中的列有索引,而后面的列中没有索引,那么涉及的索引都不会被用到。
数据分布影响
如果MySQL评估使用索引比全表更慢,则不使用索引。
SQL提示
SQL 提示,是优化数据库的一个重要手段,简单来说,就是在SQL语句中加入一些人为的提示来达到优化操作的目的。
-- use index 建议使用的索引
explain select * from user use index(idx_user_name) where name = 'test';
-- ignore index 忽略索引
explain select * from user ignore index(idx_user_name) where name = 'test';
-- force index 强制使用的索引
explain sekect * from user force index(idx_user_name) where name = 'test';
覆盖索引
尽量使用覆盖索引(查询使用了索引,并且需要返回的列,在该索引中已经全部能够找到),减少 select *。
Extra字段值的含义:
- using index condition: 查找使用了索引,但是需要回表查询数据。
- using where; using index: 查找使用了索引,但是需要的数据都在索引列中能找到,所以不需要回表查询数据。
前缀索引
当字段类型为字符串(varchar、text)时,有时候需要索引很长的字符串,这会让索引变得很大,查询时,浪费大量的磁盘IO,影响查询效率。此时可以只将字符串的一部分前缀,建立索引,这样可以大大节约索引空间,从而提高索引效率。
-- 创建前缀索引的语法
create index idx_xxxx on table_name(column(n));
前缀长度:可以根据索引的选择性来决定,而选择性是指不重复的索引值(基数)和数据表的记录总数的比值,索引选择性越高则查询效率越高,唯一索引的选择性是1,这是最好的索引选择性,性能也是最好的。
select count(distinct email) / count(*) from user;
select count(distinct substring(email, 1, 5))/count(*) from user;
单列索引和联合索引
在业务场景中,如果存在多个查询条件,考虑针对于查询字段建立索引时,建议建立联合索引,而非单列索引。
多条件联合查询时,MySQL 优化器会评估哪个字段的索引效率更高,会选择该索引完成本次查询。
2.2.7 索引设计原则
索引的设计原则:
- 针对数据量较大,且查询比较频繁的表建立索引。
- 针对常作为查询条件(where)、排序(order by)、分组(group by) 操作的字段建立索引。
- 尽量选择区分度高的列作为索引,尽量建立唯一索引,区分度越高,使用索引的效率越高。
- 如果是字符串类型的字段,字段的长度越长,可以针对字段的特点,建立前缀索引。
- 尽量使用联合索引,减少单列索引,查询时,联合索引很多时候可以覆盖索引,节省存储空间,避免回表,提高查询效率。
- 要控制索引数量,索引并不是多多益善,索引越多,维护索引结构的代价也就越大,会影响增删改的效率。
- 如果索引列不能存储 NULL 值,请在创建表时使用 NOT NULL 约束它。当优化器知道每列是否包含 NULL 值时,它可以更好地确定哪个索引最有效地用于查询。
2.3 SQL 优化
2.3.1 插入数据
insert 优化:
-- 1. 批量插入
insert into user values(1, 'tom'),(2, 'cat'),(3, 'jerry');
-- 2. 手动事务提交
start transaction;
insert into user values(1, 'tom'),(2, 'cat'),(3, 'jerry');
insert into user values(4, 'tom1'),(5, 'cat1'),(6, 'jerry1');
commit;
-- 3.主键顺序插入 主键顺序插入性能高于乱序插入。
-- 主键乱序插入:8,1,9,23,2,7,67
-- 主键顺序插入:1,2,3,9,65,81
主键顺序插入性能高于乱序插入。
大批量插入数据
如果一次性需要插入大批量数据,使用 insert 语句插入性能较低,此时可以使用 MySQL 数据库提供的 load 指令进行插入。
sql.log 示例:
1,tom
2,cat
3,jerry
4,tom1
5,cat1
6,jerry1
# 客户端连接服务端时,加上参数 --local-infile
mysql --local-infile -u root -p
# 设置全局参数 local_infile 为1,开启从本地加载文件导入数据的开关
set global local_infile = 1;
# 切换到数据库
use databaseName;
# 执行 load 指令将准备好的数据,加载到表结构中。
load data local infile '/root/sql.log' into table 'user' fields terminated by ',' lines terminated by '\n';
2.3.2 主键优化
数据的组织方式
在 InnoDB 存储引擎中,表数据都是根据主键顺序组织存放的,这种存储方式的表称为索引组织表 (index organized table, 简称 IOT)。
页分裂
页可以为空,也可以填充一半,也可以填充 100%。每个页包含了 2-N 行数据(如果一行数据过大,会行溢出),根据主键排列。
主键乱序插入会导致页分裂。
页合并
当删除一行数据时,实际上记录并没有被物理删除,只是记录被标记(flaged)为删除并且它的空间变得允许被其他记录声明使用。
当页中删除的记录达到 MERGE_THRESHOLD (默认页的 50%),InnoDB 会开始寻找最靠近的页(前或后)看看是否可以将两个页合并以优化空间使用。
MERGE_THRESHOLD:合并页的阀值,可以自己设置,在创建表或者创建索引时指定。
主键的设计原则:
- 满足业务需求的情况下,尽量降低主键的长度。
- 插入数据时,尽量选择顺序插入,选择使用 AUTO_INCREMENT 自增主键。
- 尽量不要使用 UUID 做主键或者是其他自然主键,如身份证号。
- 业务操作时,避免对主键的修改。
2.3.3 order by 优化
排序的类型:
- Using filesort:通过表的索引或全表扫描,读取满足条件的数据行,然后在排序缓冲区 sort buffer 中完成排序操作,所有不是通过索引直接返回排序结果的排序都叫 FileSort 排序。
- Using index:通过有序索引顺序扫描直接返回有序数据,这种情况即为 using index,不需要额外排序,操作效率高。
-- 根据 age, phone 进行排序一个升序,一个降序
explain select id, age, phone from user order by age asc, phone desc;
-- 创建索引 指定排序的索引优化
create index idx_user_age_phone on user(age asc, phone desc);
order by 优化的点:
- 根据排序字段建立合适的索引,多字段排序时,也遵循最左前缀法则。
- 尽量使用覆盖索引.
- 多字段排序,一个升序一个降序,此时需要注意联合索引在创建时的规则(asc/desc).
- 如果不可避免的出现 filesort, 大数据量排序时,可以适当增大排序缓冲区大小 sort_buffer_size (默认 256k).
2.3.4 group by 优化
- 在分组操作时,可以通过索引来提高效率。
- 分组操作时,索引的使用也是满足最左前缀法则的。
2.3.5 limit 优化
一个常见又非常头疼的问题就是 limit 2000000,10, 此时需要 MySQL 排序前 2000010 记录,仅仅返回 2000000 - 2000010 的记录,其他记录丢弃,查询排序的代价非常大。
优化思路:一般分页查询时,通过创建覆盖索引能够比较好地提高性能,可以通过覆盖索引加子查询的形式进行优化。
explain select * from tb_sku t, (select id from tb_sku order by id limit 2000000, 10) a where t.id = a.id;
2.3.6 count 优化
count() 是一个聚合函数,对于返回的结果集,一行行的判断,如果count函数的参数不是 null,累计值就加 1,否则不加,最后返回累计值。
explain select count(*) from tb_user;
- MyISAM 引擎把一个表的总行数存在了磁盘上,因此执行 count(*) 的时候会直接返回这个数,效率很高。(只有满足没有 where 条件时,才会直接返回总记录数)
- InnoDB 引擎就麻烦了,它执行 count(*) 的时候,需要把数据一行一行地从引擎里面读出来,然后累积计数。
优化思路:自己计数。可以使用内存型数据库存储表的记录数,如 redis
count的几种用法:
- count(*):InnoDB 引擎并不会把全部字段取出来,而是专门做了优化,不取值,服务层直接按行进行累加。
- count(主键): InnoDB 引擎会遍历整张表,把每一行的 主键id 值取出来,返回给服务层。服务层拿到主键后,直接按行进行累加(主键不可能为null)。
- count(字段):
- 没有 not null 约束:InnoDB 引擎会遍历整张表把每一行的字段值都取出来,返回给服务层,服务层判断是否为 null,不为 null,计数累加。
- 有 not null 约束:InnoDB 引擎会遍历整张表把每一行的字段值都取出来,返回给服务层,直接按行进行累加。
- count(1):InnoDB 引擎会遍历整张表,但不取值。服务层对于返回的每一行,放一个数字 1 进去,直接按行进行累加。
按照效率排序的话: count(字段) < count(主键id) < count(1) ≈ \approx ≈ count(*)。所以尽量使用 count(*);
2.3.7 update 优化
InnoDB 的行锁是针对索引加的锁,不是针对记录加的锁,并且该索引不能失效,否则会从行级锁升级为表锁。
2.4 视图/存储过程/触发器
2.4.1 视图
视图(View):是一种虚拟存在的表。视图中的数据并不在数据库中实际存在,行和列数据来自定义视图的查询中使用的表,并且是在使用视图时动态生成的。
通俗的讲,视图只保存了查询的SQL逻辑,不保存查询结果。所以我们在创建视图的时候,主要的工作就落在创建这条SQL查询语句上。
语法:
-- 创建视图
create [or replace] view 视图名称[(列名列表)] as select语句 [with [cascaded | local ] check option]
-- 查询
-- 1. 查看创建视图的语句
show create view 视图名称;
-- 2. 查看视图的数据
select * from 视图名称...;
-- 修改
-- 方式一
create [or replace] view 视图名称[(列名列表)] as select语句 [with [cascaded | local ] check option]
-- 方式二
alter view 视图名称[(列名列表)] as select语句 [with [cascaded | local] check option]
-- 删除
drop view [if exists] 视图名称 [,视图名称]...
视图的检查选项:
当使用 with check option 子句创建视图时,MySQL 会通过视图检查正在更改的每个行,例如 插入、更新、删除,以使其符合视图的定义。MySQL 允许基于另一个视图创建视图,它还会检查依赖视图中的规则以保持一致性。为了确定检查范围,mysql 提供了两个选项:cascaded 和 local,默认值为 cascaded。
视图的更新:
要使视图可更新,视图中的行与基础表中的行之间必须存在一对一的关系。如果视图包含以下任何一项,则该视图不可更新:
- 聚合函数或窗口函数 (sum()、min()、max()、count()等)
- DISTINCT
- GROUP BY
- HAVING
- UNION 或者 UNION ALL
视图作用:
- 简单:视图不仅可以简化用户对数据的理解,也可以简化他们的操作。那些被经常使用的查询可以被定义为视图,从而使得用户不必为以后的操作每次指定全部的条件。
- 安全:数据库可以授权,但不能授权到数据库特定的行和特定的列上。通过视图用户只能查询和修改他们所能见到的数据。
- 数据独立:视图可帮助用户屏蔽真实表结构变化带来的影响。
2.4.2 存储过程
2.4.2.1 存储过程介绍
存储过程是事先经过编译并存储在数据库中的一段 SQL 语句的集合,调用存储过程可以简化应用开发人员的很多工作,减少数据在数据库和应用服务器之间的传输,对于提高数据处理的效率是有好处的。
存储过程思想上很简单,就是数据库 SQL 语言层面的代码封装与重用。
2.4.2.2 存储过程的特点
- 封装、复用
- 可以接收参数、也可以返回数据
- 减少网络交互,效率提升
2.4.2.3 存储过程的语法
-- 创建
create procedure 存储过程名称([参数列表])
begin
-- sql 语句
end;
-- 调用
call 名称([参数])
-- 查看
-- 查询指定数据库 xxx 中的存储过程及状态信息
select * from information_schema.routines where routine_schema='xxx';
-- 查询某个存储过程的定义
show create procedure 存储过程名称;
-- 删除
drop procedure [if exists] 存储过程名称;
在命令行中,执行创建存储过程的 sql 时,需要通过关键字 delimiter 指定 sql 语句的结束符。
2.4.2.4 存储过程的变量
- 系统变量:是 MySQL 服务器提供的,不是用户定义的,属于服务器层面。分为全局变量(GLOBAL),会话变量(SESSION)。
- 用户定义变量:是用户根据需要自己定义的变量,用户变量不用提前声明,在用的时候直接用 “@变量名” 使用就可以。其作用域为当前连接。
- 局部变量:是根据需要定义的在局部生效的变量,访问之前,需要 Declare 声明。可用作存储过程内的局部变量和输入参数,局部变量的范围是在其内声明的 BEGIN … END 块。
查看系统变量
-- 查看所有系统变量
show [session | global] variables;
-- 可以通过like模糊匹配方式查找变量
show [session | global] variables like 'xx';
-- 查看指定变量的值
select @@[session | global] 系统变量名;
设置系统变量
set [session | global] 系统变量名=值;
set @@[session | global]系统变量名=值;
如果没有指定 session/global 默认是 session,会话变量
mysql 服务重新启动后,所设置的全局参数就会失效,要想不失效,可以在 /etc/my.cnf 中配置
赋值用户定义变量
set @var_name = expr [,@var_name = expr]...;
set @var_name:=expr [,@var_name := expr]...;
select @var_name := expr [,@var_name := expr]...;
select 字段名 into @var_name from 表名;
使用用户定义变量
select @var_name;
用户定义的变量无需对其进行声明或初始化,只不过获取到的值为 null。
声明
declare 变量名 变量类型[default ...];
变量类型是数据库字段类型:int、bigint、char、varchar、date、time 等
赋值
set 变量名 = 值;
set 变量名 := 值;
select 字段名 into 变量名 from 表名;
2.4.2.5 存储过程的IF
if 语法
if 条件1: then
...
-- 可选
ELSEIF 条件2:then
...
-- 可选
else
...
end if;
2.4.2.6 存储过程的参数
类型 | 含义 | 备注 |
---|---|---|
IN | 该类参数作为输入,也就是需要调用时传入值 | 默认 |
out | 该类参数作为输出,也就是该参数可以作为返回值 | |
inout | 即可以作为输入参数,也可以作为输出参数 |
用法
create procedure 存储过程名称([in/out/inout 参数名 参数类型])
begin
-- sql语句
end;
2.4.2.7 存储过程的case
语法一
case case_value
when when_value1 then statement_list1
[when when_value2 then statement_list2]..,
[else statement_list]
end case;
语法二
case
when search_condition1 then statement_list1
[when search_condition2 then statement_list2]
[else statement_list]
end case;
2.4.2.8 存储过程的循环
while 循环:有条件的循环控制语句。满足条件后,再执行循环体中的 sql。
-- 先判断逻辑,如果条件为 true,则执行逻辑,否则,不执行逻辑。
while 条件 DO
-- sql 逻辑
end while;
repeat 循环:是有条件的循环控制语句,当满足条件的时候退出循环。
-- 先执行一次逻辑,然后判定逻辑是否满足,如果满足,则退出,如果不满足,则继续下一次循环。
repeat
sql 逻辑
until 条件
end repeat;
loop 循环:实现简单的循环,如果不在 sql 逻辑中增加退出循环的条件,可以用其来实现简单的死循环。loop 可以配合以下两个语句使用:
- leave: 配合循环使用,退出循环
- iterate:必须用在循环中,作用是跳过当前循环剩下的语句,直接进入下一次循环。
[begin_label:] loop
sql 逻辑
end loop [end label];
-- 退出指定标记的循环体
leave label;
-- 直接进入下一次的循环
iterate label;
2.4.2.9 存储过程的游标
游标 (cursor) 是用来存储查询结果集的数据类型,在存储过程和函数中可以使用游标对结果集进行循环的处理。游标的使用包括游标的声明、open、fetch 和 close。
声明游标
declare 游标名称 cursor for 查询语句;
打开游标
open 游标名称;
获取游标记录
fetch 游标名称 into 变量[,变量];
关闭游标
close 游标名称;
2.4.2.10 存储过程的条件处理程序
条件处理程序 (handler) 可以用来定义在流程控制结构执行过程中遇到问题时相应的处理步骤。
declare handler_action handler for condition_value [,condition_value]... statement;
handler_action
- continue: 继续执行当前程序
- exit: 终止执行当前程序
condition_value
- sqlstate sqlstate_value: 状态码,如 02000
- sqlwarning: 所有以 01 开头的 sqlstate 代码的简写
- not found: 所有以 02 开头的 sqlstate 代码的简写
- sqlexception:所有没有被 sqlwarning 或 not found 捕获的 sqlstate 代码的简写。
2.4.3 存储函数
存储函数是有返回值的存储过程,存储函数的参数只能是 in 类型的。
create function 存储函数名称([参数列表])
returns type [characteristic ...]
begin
-- sql 语句
return ...;
end;
characteristic 说明
- deterministic:相同的输入参数总是产生相同的结果
- no sql:不包含 sql 语句
- reads sql data: 包含读取数据的语句,但不包含写入数据的语句。
2.4.4 触发器
触发器是表有关的数据库对象,指在 insert/update/delete 之前或之后,触发并执行触发器中定义的 SQL 语句集合。触发器的这种特性可以协助应用在数据库端确保数据的完整性,日志记录,数据校验等操作。
使用别名 old 和 new 来引用触发器中发生变化的记录内容,这与其他数据库是相似的。现在触发器还支持行级触发,不支持语句级出发。
触发器类型 | new 和 old |
---|---|
insert 型触发器 | new 表示将要或者已经新增的数据 |
update 型触发器 | old 表示修改之前的数据,new 表示将要或已经修改后的数据 |
delete 型触发器 | old 表示将要或者已经删除的数据 |
创建触发器
create trigger trigger_name
before/after insert/update/delete
on tbl_name for each row -- 行级触发器
begin
trigger_stmt;
end;
查看触发器
show triggers;
删除触发器
-- 如果没有指定 schema_name,默认为当前数据库。
drop trigger [schema_name] trigger_name;
2.5 锁
锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中,除传统的计算资源 (cpu、ram、i/o) 的争用外,数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。从这个角度来说,锁对数据库而言显得尤其重要,也更复杂。
MySQL 中的锁,按照锁的粒度,分为以下三类:
- 全局锁:锁定数据库中的所有表
- 表级锁:每次操作锁住整张表
- 行级锁:每次操作锁住对应的行数据
2.5.1 全局锁
全局锁就是对整个数据库实例加锁,加锁后整个实例就处于只读状态,后续的 DML 的写语句,DDL 语句,以及更新操作的事务提交语句都将被阻塞。
其典型的使用场景是做全库的逻辑备份,对所有的表进行锁定,从而获取一致性视图,保证数据的完整性。
-- 加全局锁
flush tables with read lock;
-- 数据库备份
mysqldump -uroot -p123456 dbname > dbname.sql
-- 解锁
unlock tables;
数据库中加全局锁,是一个比较重的操作,存在以下问题:
- 如果在主库上备份,那么在备份期间都不能执行更新,业务基本上就得停摆。
- 如果在从库上备份,那么在备份期间从库不能执行主库同步过来的二进制日志 (binlog),会导致主从延迟。
在 innoDB 引擎中,我们可以在备份时加上参数 --single-transaction 参数来完成不加锁的一致性数据备份。
# 通过快照
mysqldump --single-transaction -uroot -p123456 dbname > dbname.sql
2.5.2 表级锁
表级锁,每次操作锁住整张表。锁定粒度大,发生锁冲突的概率最高,并发度最低。应用在 MyISAM、InnoDB、BDB等存储引擎中。
对于表级锁,主要分为以下三类:
- 表锁
- 元数据锁 (meta data lock, MDL)
- 意向锁
2.5.2.1 表锁
对于表锁,分为两类:
- 表共享读锁(read lock):会阻塞写,不会阻塞读
- 表独占写锁(write lock):会阻塞其他客户端的读和写。
-- 加锁
lock tables 表名... read/write;
--解锁
unlock tables; -- 或者客户端断开连接
读锁不会阻塞其他客户端的读,但是会阻塞写。写锁既会阻塞其他客户端的读,又会阻塞其他客户端的写。
2.5.2.2 元数据锁
元数据锁 (meta data lock,MDL): 加锁的过程是系统自动控制,无需显式使用,在访问一张表的时候会自动加上。元数据锁主要作用是维护表元数据的数据一致性,在表上有活动事务的时候,不可以对元数据进行写入操作。
为了避免 DML 与 DDL 冲突,保证读写的正确性。
在 MySQL 5.5 中引入了 MDL, 当对一张表进行增删改查的时候,加 MDL 读锁(共享);当对表结构进行变更操作时,加 MDL 写锁(排他)。
对应 SQL | 锁类型 | 说明 |
---|---|---|
lock tables xxx read/write | shared_read_only/shared_no_read_write | |
select、select … lock in mode | shared_read | 与 shared_read、shared_write兼容,与 exclusive 互斥 |
insert、update、delete、select … for update | shared_write | 与 shared_read、shared_write兼容,与 exclusive 互斥 |
alter table … | exclusive | 与其他的 MDL 互斥 |
-- 查看元数据锁
select object_type,object_schema,object_name,lock_type,lock_duration from performance_schema.metadata_locks;
2.5.2.3 意向锁
为了避免 DML 在执行时,加的行锁和表锁的冲突,在 InnoDB 中引入了意向锁,使得表锁不用检查每行数据是否加锁,使用意向锁来减少表锁的检查。
意向锁的类型
- 意向共享锁 (IS): 由语句 select … lock in share mode 添加。
- 意向排他锁 (IX): 由 insert、update、delete、select … for update 添加。
意向锁和表锁的兼容情况
- 意向共享锁 (IS): 与表锁共享锁 (read) 兼容,与表锁排它锁 (write) 互斥
- 意向排他锁 (IX): 与表锁共享锁 (read) 及排它锁 (write) 都互斥。意向锁之间不会互斥。
-- 查看意向锁及行锁的加锁情况
select object_type,object_schema,object_name,lock_type,lock_duration from performance_schema.data_locks;
2.5.3 行级锁
行级锁,每次操作锁住对应的行数据。锁定粒度最小,发生锁冲突的概率最低,并发度最高。应用在 InnoDB 存储引擎中。
InnoDB 的数据是基于索引组织的,行锁是通过对索引上的索引项加锁来实现的,而不是对记录加的锁。对于行级锁,主要分为以下三类:
- 行锁(record lock):锁定单个行记录的锁,防止其他事务对此进行 update 和 delete。在 RC、RR 隔离级别下都支持。
- 间隙锁 (Gap lock):锁定索引记录间隙(不含该记录),确保索引记录不变,防止其他事务在这个间隙进行 insert,产生幻读。在 RR 隔离级别下都支持。
- 临键锁 (next-key lock): 行锁和间隙锁的组合,同时锁住数据,并锁住数据前面的间隙 Gap。在 RR 隔离级别下支持。
2.5.3.1 行锁
InnoDB 实现了以下两种类型的行锁:
- 共享锁 (S): 允许一个事务去读一行,阻止其他事务获得相同数据集的排它锁。
- 排他锁 (X):允许获取排他锁的事务更新数据,阻止其他事务获得相同数据集的共享锁和排他锁。
SQL | 行锁类型 | 说明 |
---|---|---|
insert … | 排他锁 | 自动加锁 |
update … | 排他锁 | 自动加锁 |
delete … | 排他锁 | 自动加锁 |
select (正常) | 不加任何锁 | |
select … lock in share mode | 共享锁 | 需要手动在 select 之后加 lock in share mode |
select … for update | 排他锁 | 需要手动在 select 之后加 for update |
默认情况下,InnoDB 在 repeatable read 事务隔离级别运行,InnoDB 使用 next-key 锁进行搜索和索引扫描,以防止幻读。
- 针对唯一索引进行检索时,对已存在的记录进行等值匹配时,将会自动优化为行锁。
- InnoDB 的行锁是针对于索引加的锁,不通过索引条件检索数据,那么 InnoDB 将对表中的所有记录加锁,此时就会升级为表锁。
2.5.3.2 间隙锁/临键锁
默认情况下,InnoDB 在 repeatable read 事务隔离级别运行,InnoDB 使用 next-key 锁进行搜索和索引扫描,以防止幻读。
- 索引上的等值查询(唯一索引), 给不存在的记录加锁时,优化为间隙锁。
- 索引上的等值查询(普通索引),向右遍历时最后一个值不满足查询需求时,next-key lock 退化为 间隙锁。
- 索引上的范围查询(唯一索引),会访问到不满足条件的第一个值为止。
间隙锁唯一目的是防止其他事务插入间隙。间隙锁可以共存,一个事务采用的间隙锁不会阻止另一个事务在同一间隙上采用间隙锁。
2.6 InnoDB 引擎
2.6.1 逻辑存储结构
2.6.2 架构
MySQL 5.5 版本开始,默认使用 InnoDB 存储引擎,它擅长事务处理,具有奔溃恢复特性,在日常开发中使用非常广泛。下面是 InnoDB 架构图,左侧为 内存结构,右侧为磁盘结构。
2.6.2.1 内存架构
Buffer pool: 缓冲池是主内存中的一个区域,里面可以缓存磁盘上经常操作的真实数据,在执行增删改查操作时,先操作缓冲池中的数据(若缓冲池没有数据,则从磁盘中加载并缓存),然后再以一定频率刷新到磁盘,从而减少磁盘 IO,加快处理速度。
缓冲池以 page 页为单位,底层采用链表数据结构管理page。根据状态,将 page 分为三种类型:
- free page: 空闲page,未被使用。
- clean page: 被使用page,数据没有被修改过。
- dirty page: 脏页,被使用page,数据被修改过,页中数据与磁盘的数据产生了不一致。
Change Buffer:更改缓冲区(针对于非唯一二级索引页),在执行 DML 语句时,如果这些数据 Page 没有在 Buffer Pool 中,不会直接操作磁盘,而会将数据变更存在更改缓冲区 Change Buffer 中,在未来数据被读取时,再将数据合并恢复到 Buffer pool 中,再将合并后的数据刷新到磁盘中。
Change Buffer 的意义是什么?
与聚集索引不同,二级索引通常是非唯一的,并且以相对随机的顺序插入二级索引。同样,删除和更新可能会影响索引树中不相邻的二级索引页,如果每一次都操作磁盘,会造成大量的磁盘IO。有了Change Buffer 之后,我们可以在缓冲池中进行合并处理,减少磁盘IO。
Adaptive Hash Index: 自适应 hash 索引,用于优化对 Buffer Pool 数据的查询。InnoDB 存储引擎会监控对表上各索引页的查询,如果观察到hash索引可以提升速度,则建立hash索引,称之为自适应hash索引。
自适应哈希索引,无需人工干预,是系统根据情况自动完成的。
参数:adaptive_hash_index
Log Buffer: 日志缓冲区,用来保存要写入磁盘中的log日志数据(redo log、undo log),默认大小为 16 MB,日志缓冲区的日志会定期刷新到磁盘中,如果需要更新、插入或删除多行的事务,增加日志缓冲区的大小可以节省磁盘I/O。
参数:
- innodb_log_buffer_size: 缓冲区大小
- innodb_flush_log_at_trx_commit: 日志刷新到磁盘时机。
- 1:日志在每次事务提交时写入并刷新到磁盘
- 0:每秒将日志写入并刷新到磁盘一次
- 2:日志在每次事务提交后写入,并每秒刷新到磁盘一次。
2.6.2.2 磁盘架构
System Tablespace: 系统表空间是更改缓冲区的存储区域。如果表是在系统表空间而不是每个表文件或通用表空间中创建,它也肯能包含表和索引数据。(在 MySQL 5.X 版本中还包含 InnoDB 数据字典、undolog 等)
参数: innodb_data_file_path
File-Per-Table Tablespaces:每个表的文件表空间包含单个 InnoDB 表的数据和索引,并存储在文件系统上的单个数据文件中。
参数:innodb_file_per_table
General Tablespaces: 通用表空间,需要通过 create tablespace 语法创建通用表空间,在创建表时,可以指定该表空间。
-- 创建表空间
create tablespace xxx add datafile 'filename。ibd' engin=engin_name;
Undo Tablespaces: 撤销表空间,MySQL 实例在初始化时会自动创建两个默认的 undo 表空间(初始大小 16 M),用于存储 undo log 日志。
Temporary Tablespaces:InnoDB 使用会话临时表空间和全部临时表空间。存储用户创建的临时表等数据。
Doublewrite Buffer Files:双写缓冲区,innoDB 引擎将数据页从 Buffer Pool 刷新到磁盘前,先将数据页写入双写缓冲区文件中,便于系统异常时恢复数据。
双写缓冲区文件
#ib_16384_0.dblwr
#ib_16384_1.dblwr
Redo log: 重做日志,是用来实现事务的持久性。该日志文件由两部分组成,重做日志缓冲区(redo log buffer)以及重做日志文件 (redo log),前者是在内存中,后者在磁盘中。当事务提交之后会把所有修改信息都会存到该日志中,用于在刷新脏页到磁盘时,发生错误时,进行数据恢复使用。
以循环的方式写入重做日志文件,涉及两个文件:
- ib_logfile0
- ib_logfile1
2.6.2.3 架构-后台线程
- Master Thread:核心后台线程,负责调度其他线程,还负责将缓冲池中的数据异步刷新到磁盘中,保持数据的一致性。还包括脏页的刷新、合并插入缓冲、undo 页的回收。
- IO Thread:在InnoDB存储引擎中大量使用了AIO来处理IO请求,这样可以极大地提高数据库的性能,而 IO Thread 主要负责这些 IO 请求的回调。
- Read thread:默认个数 4,负责读操作
- Write thread:默认个数 4,负责写操作
- log thread:默认个数 1,负责将日志缓冲区刷新到磁盘
- insert buffer thread:默认个数 1,负责将写缓冲区内容刷新到磁盘。
- Purge Thread:主要用于回收事务已经提交了的 undo log, 在事务提交之后,undo log 可能不用了,就用它来回收。
- Page Cleaner Thread: 协助Master Thread 刷新脏页到磁盘的线程,它可以减轻 Master Thread 的工作压力,减少阻塞。
2.6.3 事务原理
2.6.3.1 redo log
redo log 重做日志,记录的是事务提交时数据页的物理修改,是用来实现事务的持久性。
该日志文件由两部分组成:重做日志缓冲(redo log buffer) 以及 重做日志文件 (redo log file), 前者是在内存中,后者是在磁盘中。当事务提交之后会把所有修改信息都存到该日志文件中,用于在刷新脏页到磁盘,发生错误时,进行数据恢复使用。
2.6.3.2 undo log
undo log 回滚日志,用于记录数据被修改前的信息,作用包含两个:提供回滚 和 MVCC(多版本并发控制)。
undo log 和 redo log 记录物理日志不一样,它是逻辑日志。可以认为当 delete 一条记录时,undo log 中会记录一条对应的 insert 记录,反之亦然,当 update 一条记录时,它记录一条对应相反的 update 记录。当执行 rollback 时,就可以从 undo log 中的逻辑记录读取到相应的内容并进行回滚。
undo log 销毁时: undo log 在事务执行时产生,事务提交时,并不会立即删除 undo log,因为这些日志可能还用于 MVCC。
Undo log存储:undo log 采用段的方式进行管理和记录,存放在前面介绍的rollback segment 回滚段中,内部包含 1024 个undo log segment。
2.6.4 MVCC
2.6.4.1 MVCC 基本概念
当前读:读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。对于我们日常操作,如 select … lock in share mode(共享锁),select … for uodate、update、insert、delete(排他锁)都是一种当前读。
快照读:简单的 select (不加锁) 就是快照读,快照读,读取的记录数据的可见版本,有可能是历史数据,不加锁,是非阻塞读。
- Read Committed: 每次 select,都生成一个快照读。
- Repeatable Read:开启事务后第一个 select 语句才是快照读的地方。
- Serializable:快照读会退化为当前读。
MVCC:全称 Multi-Version Concurrency Control, 多版本并发控制。指维护一个数据库的多个版本,使得读写操作没有冲突,快照读为 MySQL 实现 MVCC 提供了一个非阻塞读功能。MVCC 的具体实现,还需要依赖于数据库记录中的三个隐式字段、undo log日志、readview。
2.6.4.2 记录中的隐藏字段
- DB_TRX_ID: 最近修改事务ID,记录这条记录或最后一次修改该记录的事务ID。
- DN_ROLL_PTR: 回滚指针,指向这条记录的上一个版本,用于配合 undo log,指向上一个版本。
- DB_ROW_ID: 隐藏主键,如果表结构没有指定主键,将会生成该隐藏字段。
2.6.4.3 Undo Log
Undo Log 回滚日志,在 insert、update、delete 的时候产生的便于数据回滚的日志。
当 insert 的时候,产生的 undo log 日志只在回滚时需要,在事务提交后,可被立即删除。
而 update、delete 的时候,产生的 undo log 日志不仅在回滚时需要,在快照读时也需要,不会立即被删除。
undo log 版本链:不同事务或相同事务对同一条记录进行修改,会导致该记录的 undo log 生成一条记录版本链表,链表的头部是最新的旧记录,链表尾部是最早的旧纪录。
2.6.4.3 Readview
Readview (读视图) 是快照读 SQL 执行时 MVCC 提取数据的依据,记录并维护系统当前活跃的事务(未提交的)id。
Readview 中包含了四个核心字段:
- m_ids: 当前活跃的事务id集合
- min_trx_id: 最小活跃事务id
- max_trx_id: 预分配事务id, 当前最大事务id +1 (因为事务id是自增的)
- creator_trx_id: ReadView 创建者的事务id
版本链数据访问规则:trx_id 代表当前事务id
- trx_id == creator_trx_id ? 可以访问该版本 成立,说明数据是当前这个事务更改的。
- trx_id < min_trx_id ? 可以访问该版本 成立,说明数据已经提交。
- trx_id > max_trx_id ? 不可以访问该版本 成立,说明该事务是在ReadView生成后才开启的。
- min_trx_id <= trx_id <= max_trx_id ? 如果 trx_id 不在 m_ids 中是可以访问该版本的。 成立,说明数据已经提交。
不同的隔离级别,生成 ReadView 的时机不同:
- Read Committed: 在事务中每一次执行快照读时生成 ReadView。
- Repeatable Read:仅在事务中第一次执行快照读时生成 ReadView,后面复用该 ReadView。
2.7 MySQL 管理
2.7.1 系统数据库
MySQL 数据库安装完成后,自带了以下四个数据库,具体作用如下:
- mysql: 存储MySQL服务器正常运行所需要的各种信息 (时区、主从、用户、权限等)
- information_schema: 提供了访问数据库元数据的各种表和视图,包含数据库、表、字段类型及访问权限等。
- performance_schema: 为 MySQL 服务器运行时状态提供了一个底层监控功能,主要用于收集数据库服务器性能参数。
- sys: 包含了一系列方便 DBA 和开发人员利用 performance_schema 性能数据库进行性能调优和诊断的视图。
2.7.2 常用工具
- mysql
- mysqladmin
- mysqlbinlog
- mysqlshow
- mysqldump
- mysqlimport/source
3. MySQL 运维篇
3.1 日志
日志分类:
- 错误日志
- 二进制日志
- 查询日志
- 慢查询日志
3.1.1 错误日志
错误日志,它记录了当 mysqld 启动和停止时,以及服务器在运行过程中发生任何严重错误时的相关信息。当数据库出现任何故障导致无法正常使用时,建议首先查看此日志。
该日志是默认开启的,默认存放目录 /var/log/,默认的日志文件名为 mysqld.log。查看日志位置:
show variables like '%log_error%';
3.1.2 二进制日志
二进制日志 (BINLOG) 记录了所有的 DDL (数据定义语言) 语句 和 DML (数据操纵语言) 语句,但不包括数据查询 (select、show) 语句。
作用:
- 灾难时的数据恢复
- MySQL 的主从复制。
在MySQL8版本中,默认二进制日志是开启的。
show variables like '%log_bin%';
日志格式:
- STATEMENT:基于 SQL 语句的日志记录,对数据进行修改的SQL都会记录在日志文件中。
- ROW:基于行的日志记录,记录的是每一行的数据变更。(默认)
- MIXED:混合了 STATEMENT 和 ROW 两种格式,默认采用 STATEMENT,在某些特殊情况下会自动切换为 ROW 进行记录。
show variables like '%binlog_format%';
日志查看:由于日志是以二进制方式存储的,不能直接读取,需要通过二进制日志查询工具 mysqlbinlog 来查看。
mysqlbinlog [参数选项] logfilename
# -d 指数据库名称,只列出指定的数据库相关操作; -o 忽略掉日志中的前n行命令; -v 将行事件(数据变更)重构为SQL语句; -w 将行事件(数据变更)重构为SQL语句,并输出注释信息;
日志删除:对于比较繁忙的业务系统,每天生成的binlog数据巨大,如果长时间不清楚,将会占用大量磁盘空间。
- reset master:删除全部的 binlog 日志,删除之后,日志编号,将从 binlog.000001 重新开始。
- purge master logs to ‘binlog.xxx’:删除 xxx 编号之前的所有日志。
- purge master logs before ‘yyyy-mm-dd hh24:mi:ss’:删除日志为 ‘yyyy-mm-dd hh24:mi:ss’ 之前产生的所有日志。
也可以在 mysql 的配置文件中配置二进制日志的过期时间,设置了之后,二进制日志过期会自动删除。
show variables like '%binlog_expire_logs_seconds%';
3.1.3 查询日志
查询日志中记录了客户端的所有操作语句,而二进制日志不包含查询数据的SQL语句。默认情况下,查询日志是未开启的。
show variables like 'general';
# 该选项用来开启查询日志,可选值 0 关闭;1 开启;
general_log = 1
# 设置日志的文件名,如果没有指定,默认的文件名未 host_name.log
general_log_file = mysql_query.log
3.1.4 慢查询日志
慢查询日志记录了所有执行时间超过参数 long_query_time 设置值并且扫描记录数不小于 min_examined_row_limit 的所有的 SQL 语句的日志,默认未开启。long_query_time 默认为 10 秒,最小为 0,精度可以到微妙。
#慢查询日志开启
slow_query_log = 1
#执行时间参数
long_query_time = 2
默认情况下,不会记录管理语句,也不会记录不使用索引进行查找的查询。
#记录执行较慢的管理语句
log_slow_admin_statements=1
#记录执行较慢的未使用索引的语句
log_queries_not_indexes=1
3.2 主从复制
3.2.1 主从复制概述
主从复制是指将主数据库的 DDL 和 DML 操作通过二进制日志传到从库服务器中,然后在从库上对这些日志重新执行(也叫重做),从而使得从库和主库的数据保持同步。
MySQL 支持一台主库同时向多台从库进行复制,从库同时也可以作为其他从服务器的主库,实现链状复制。
MySQL 复制的优点主要包含以下三个方面:
- 主库出现问题,可以快速切换到从库提供服务。
- 实现读写分离,降低主库的访问压力。
- 可以在从库中执行备份,以避免备份期间影响主库服务。
3.2.2 主从复制原理
**1. Master 主库在事务提交时,会把数据变更记录在二进制日志文件 Binlog 中。
2. 从库读取主库的二进制日志文件 Binlog,写入到从库的中继日志 Relay log。
3. slave 重做中继日志中的事件,将改变反映它自己的数据。
3.2.3 主从复制搭建
准备两台服务器。分别安装MySQL,完成初始化。
Centos中MySQL安装教程(打开教程后直接跳到安装MySQL的部分)
5.0 版本需要手动开启 binlog
# my.cnf 添加
log_bin=ON
# 开放指定的3306端口号 或者 关闭服务器的防火墙
firewall-cmd --zone=public --add-port=3306/tcp -permanent
firewall-cmd -reload
# 或者
systemctl stop firewalld
systemctl disable firewalld
主库配置
# 1. 修改配置文件 /etc/my.cnf
# mysql 服务id,保证整个集群环境中唯一,取值范围:1-2^32-1,默认1。
server-id=1
# 是否只读,1代表只读,0代表读写
read-only=0
# 忽略的数据,指不需要同步的数据库
#binlog-ignore-db=mysql
# 指定同步的数据库
#binlog-do-db=db01
#2. 重启Mysql 服务
systemctl restart mysqld
-- 3. 登录 mysql,创建远程连接的账号,并授予主从复制的权限
-- 创建 mstest 用户,并设置密码,该用户可在任意主机连接该 MySQL 服务。
create user 'mstest'@'%' identified with mysql_native_password by 'Gjw%123456';
-- 为 'mstest'@'%' 用户分配主从复制权限
grant replication slave on *.* to 'mstest'@'%';
-- 4.通过指令,查看二进制日志的坐标
-- 字段含义, file 从哪个日志文件开始推送日志文件;position 从哪个位置开始推送日志;binlog_ignore_db 指定不需要同步的数据库。
show master status;
从库配置
# 1. 修改配置文件 /etc/my.cnf
# mysql 服务id,保证整个集群环境中唯一,取值范围:1-2^32-1,默认1。
server-id=2
# 是否只读,1代表只读,0代表读写
read-only=1
# 超级用户也是只读
#super-read-only=1
#2. 重启Mysql 服务
systemctl restart mysqld
-- 3. 登录 mysql,设置主库配置
-- mysql 版本是 8.0.23 中语法如下
change replication source to source_host='xxx',source_user='xxx',source_password='xxx',source_log_file='xxx',source_log_pos=xxx;
-- mysql 版本是 8.0.23之前的版本语法如下
change master to master_host='xxx',master_user='xxx',master_password='xxx',master_log_file='xxx',master_log_pos=xxx;
-- 4. 开启同步操作
-- 8.0.22 之后的语法
start replica;
-- 8.0.22 之前的语法
start slave;
-- 5. 查看主从同步状态
-- 8.0.22 之后的语法
show replica status;
-- 8.0.22 之前的语法
show slave status;
3.3 分库分表
3.3.1 介绍
随着互联网及移动互联网的发展,应用系统的数据量也是成指数式增长的,若采用单数据库进行数据存储,存在以下性能瓶颈:
- IO瓶颈:热点数据太多,数据库缓存不足,产生大量磁盘IO,效率较低。请求数据太多,带宽不够,网络IO瓶颈。
- CPU瓶颈:排序、分组、连接查询、聚合统计等SQL会耗费大量的CPU资源,请求数太多,CPU出现瓶颈。
分库分表的中心思想都是将数据分散存储,使得单一数据库/表的数据量变小来缓解单一数据库性能的问题,从而达到提升数据库性能的目的。
3.3.1.1 拆分策略
- 垂直拆分
- 垂直分库
- 垂直分表
- 水平拆分
- 水平分库
- 水平分表
3.3.1.1.1 垂直拆分
垂直分库:以表为依据,根据业务将不同表拆分到不同库。
垂直分库的特点:
- 每个库的表结构都不一样。
- 每个库的数据也不一样。
- 所有库的数据是全量数据。
垂直分表:以字段为依据,根据字段属性将不同字段拆分到不同表种。
垂直分表的特点:
- 每个表的结构都不一样。
- 每个表的数据也不一样,一般通过一列(主键/外键) 关联。
- 所有表的并集是全量数据。
3.3.1.1.2 水平拆分
水平分库:以字段为依据,按照一定策略,将一个库的数据拆分到多个库中。
水平分库的特点:
- 每个库的表结构都一样。
- 每个库的数据都不一样。
- 所有库的并集是全量数据。
水平分表:以字段为依据,按照一定策略,将一个表的数据拆分到多个表中。
水平分表的特点:
- 每个表的表结构都一样。
- 每个表的数据都不一样。
- 所有表的并集是全量数据。
3.3.1.2 实现技术
- shareingJDBC:基于 AOP 原理,在应用程序中对本地执行的SQL进行拦截,解析、改写、路由处理。需要自行编码配置实现,只支持java语言,性能较高。
- MyCat:数据库分库分表中间件,不用调整代码即可实现分库分表,支持多种语言,性能不及前者。
3.3.2 Mycat概述
Mycat 是开源的、活跃的、基于 Java 语言编写的 MySQL 数据库中间件。可以像使用 mysql 一样来使用 mycat,对于开发人员来说根本感觉不到 mycat 的存在。
Mycat 优势:
- 性能可靠稳定
- 强大的技术团队
- 体系完整
- 社区活跃
Mycat 安装:MyCat 支持 Windows 和 Linux 运行环境,我们需要在准备好的服务器上安装如下软件。
- MySQL
- JDK
- Mycat
服务器 | 安装软件 | 说明 |
---|---|---|
server-1 | jdk、Mycat、MySQL | MyCat中间件服务器、分片服务器 |
server-2 | MySQL | 分片服务器 |
server-3 | MySQL | 分片服务器 |
# 下载 MyCat Linux 版本,在 centos7 安装
wget https://github.com/MyCATApache/Mycat-Server/releases/download/Mycat-server-1675-release/Mycat-server-1.6.7.5-release-20200422133810-linux.tar.gz
# 解压 MyCat 压缩包到 /usr/local/
tar -zxvf Mycat-server-1.6-RELEASE-20161028204710-linux.tar.gz -C /usr/local/
MyCat 的架构:
3.3.3 Mycat入门
需求:
由于 tb_order 表中数据量很大,磁盘IO及容量都到达了瓶颈,现在需要对 tb_order 表进行数据分片,分为三个数据节点,每一个节点主机位于不同的服务器上,具体结构。参考下图:
环境准备:
分片配置 schema.xml:
vim /usr/local/mycat/conf/schema.xml
分片配置 server.xml :
启动服务:
# 切换目录到 /usr/local/mycat/
cd /usr/local/mycat/
#启动
bin/mycat start
#停止
bin/mycat stop
#查看服务是否启动成功
tail -f wrapper.log
Mycat 启动之后,占用端口号 8066;
分片测试:
# 连接并登录 Mycat
mysql -h 192.168.3.27 -P 8066 -u root -p123456
3.3.4 Mycat配置
3.3.4.1 schema.xml
schema.xml 作为 MyCat 中最重要的配置文件之一,涵盖了 MyCat 的逻辑库、逻辑表、分片规则、分片节点及数据源的配置。
主要包含以下三组标签:
- schema 标签
- datanode 标签
- datahost 标签
schema 标签:用于定义 MyCat 实例中的逻辑库,一个 MyCat 实例,可以有多个逻辑库,可以通过 schema 标签来划分不同的逻辑库。
MyCat 中的逻辑库的概念,等同于 MySQL 中的 database 概念,需要操作某个逻辑库下的表时,也需要切换逻辑库 (use xxx);
schema核心属性:
- name: 指定自定义的逻辑库名,大小写敏感
- checkSQLschema: 在 SQL 语句操作时指定了数据库名称,执行时是否自动去除;true 自动去除;false 不自动去除;
- sqlMaxLimit: 如果未指定 limit 进行查询,列表查询模式查询多少条记录。
schema 标签(table): 定义了 MyCat 中逻辑库 schema 下的逻辑表,所有需要拆分的表都需要在 table 标签中定义。
schema 下 table 核心属性:
- name: 定义逻辑表表名,在该逻辑库下唯一,大小写敏感
- dataNode: 定义逻辑表所属的dataNode,该属性需要与dataNode标签中name对应;多个dataNode 逗号分割。
- rule: 分片规则的名字,分片规则名字是在rule.xml中定义的
- primaryKey: 逻辑表对应真实表的主键
- type: 逻辑表的类型,目前逻辑表只有全局表和普通表,如果未配置,就是普通表,全局表,配置为 global
dataNode 标签: 定义了 MyCat 中的数据节点,也就是我们通常说的数据分片。一个 dataNode 标签就是一个独立的数据分片。
dataNode 核心属性:
- name: 定义数据节点的名称
- dataHost: 数据库实例主机名称,引用自 dataHost 标签中的 name 属性。
- database: 定义分片所属数据库。
dataHost标签:在 MyCat 逻辑库中作为底层标签存在,直接定义了具体的数据库实例、读写分离、心跳语句。
dataHost 核心属性:
- name: 唯一标识,供上层使用。
- maxConn/minConn: 最大连接数/最小连接数
- balance: 负载均衡策略,取值 0、1、2、3
- writeType: 写操作分发方式(0: 写操作转发到第一个 writeHost,第一个挂了,切换到第二个;1:写操作随机分发到配置的 writeHost)
- dbDriver: 数据库驱动 native、jdbc
3.3.4.2 rule.xml
rule.xml 中定义所有拆分表的规则,在使用过程中可以灵活的使用分片算法,或者对同一个分片算法使用不同的参数,它让分片过程可配置化。主要包含两类标签:
- tableRule:
- Function:
3.3.4.3 server.xml
server.xml 配置文件包含了 MyCat 的系统配置信息,主要有两个重要的标签:system、user
3.3.5 Mycat分片
3.3.5.1 垂直拆分
全局表配置:对于省、市、区表是属于数据字典表,在多个业务模块中都可能会遇到,可以将其设置为全局表,利于业务操作。
3.3.5.2 水平拆分
分片规则:
- 范围:根据指定字段及其配置的范围与数据节点的对应情况,来决定该数据属于哪一个分片。autopartition-long
- 取模:根据指定字段值与节点数量进行求模运算,根据运算结果,来决定该数据属于哪一个分片。mod-long
- 一致性hash: 相同的哈希因子计算总是被划分到相同的分区表中,不会因为分区节点的增加而改变原来数据的分区位置。sharding-by-murmur
- 枚举:通过在配置文件中配置可能的枚举值,指定数据分布到不同数据节点上,本规则适用于按照省份、性别、状态拆分数据等业务。sharding-by-intfile
- 应用指定:运行阶段由应用自主决定路由到那个分片,直接根据字符串子串(必须是数字)计算分片号。sharding-by-substring
- 固定分片hash算法:该算法类似于十进制的求模运算,但是为二进制的操作,例如,取 id 的二进制低 10 位与 1111111111 进行位&运算 sharding-by-long-hash
- 字符串hash解析:截取字符串中指定位置的子字符串,进行hash算法,算出分片。sharding-by-stringhash
- 按天分片:sharding-by-date
- 按自然月分片:sharding-by-month
<function name="sharding-by-substring" class="io.mycat.route.function.PartitionDirectBySubString">
<property name="startIndex">0</property>
<property name="size">2</property>
<property name="partitionCount">3</property>
<property name="defaultPartition">2</property>
</function>
<function name="sharding-by-long-hash" class="io.mycat.route.function.PartitionByLong">
<property name="partitionCount">2,1</property>
<!-- 约束:分片长度,默认 1024; count\length 数组长度必须一致 -->
<property name="partitionLength">256,512</property>
</function>
<function name="sharding-by-stringhash" class="io.mycat.route.function.PartitionByString">
<property name="partitionLength">512</property>
<property name="partitionCount">2</property>
<!-- hash 运算位,格式 start:end; 0 在end中出现代表 str.length(); -1 代表 str.length()-1;大于0代表数字本身; -->
<property name="hashSlice">0:2</property>
</function>
3.3.6 Mycat管理及监控
3.3.6.1 MyCat 原理
3.3.6.2 MyCat 管理
Mycat 默认开通2个端口,可以在 server.xml 中进行修改。
- 8066 数据访问端口,即进行 DML 和 DDL 操作。
- 9066 数据库管理端口,即 Mycat 服务管理控制功能,用于管理 Mycat 的整个集群状态。
mysql -h 192.168.3.27 -P 9066 -u root -p123456
Mycat-eye: Mycat-web(Mycat-eye) 是对 mycat-server 提供监控服务,功能不局限于对 mycat-server 使用,他通过 JDBC 连接对 Mycat、MySQL 监控,监控远程服务器(目前仅限于Linux 系统)的cpu、内存、网络、磁盘。
Mycat-eye 运行过程中需要依赖 zookeeper。因此需要先安装 zookeeper。
# zookeeper 安装
wget https://dlcdn.apache.org/zookeeper/zookeeper-3.8.3/apache-zookeeper-3.8.3-bin.tar.gz
tar -zxvf apache-zookeeper-3.8.3-bin.tar.gz -C /usr/local/
mkdir /usr/local/apache-zookeeper-3.8.3-bin/data
bin/zkServer.sh start
bin/zkServer.sh status
# mycat-web 安装 需要先获取 MyCatWeb 压缩包
tar -zxvf Mycat-web-1.0-SNAPSHOT-20170102153329-linux.tar.gz -C /usr/local/
cd /usr/local/mycat-web/
sh start.sh
# 访问 localhost:8082/mycat 访问
3.4 读写分离
3.4.1 读写分离介绍
读写分离,简单地说是把对数据库的读和写操作分开,以对应不同的数据库服务器。主数据库提供写操作,从数据库提供读操作,这样能有效地减轻单台数据库的压力。
通过 MyCat 即可轻易实现上述功能,不仅可以支持 MySQL,也可以支持 Oracle 和 SQL Server。
3.4.2 一主一从读写分离
MyCat 控制后台数据库的读写分离和负载均衡由 schema.xml 文件 datahost 标签的 balance 属性控制。
<!-- schema.xml -->
<schema name="DB_RW" checkSQLschema="true" sqlMaxLimit="100" dataNode="dn1">
</schema>
<dataNode name="dn1" dataHost="dhost1" database="testdb"/>
<dataHost name="dhost1" maxCon="1000" minCon="10" balance="1" writeType="0" dbType="mysql" dbDriver="jdbc">
<heartbeat>select user()</heartbeat>
<writeHost host="master" url="jdbc:mysql://192.168.3.27:3306?useSSL=false" user="root" password="1234">
<readHost host="slave" url="jdbc:mysql://192.168.3.28:3306?useSSL=false" user="root" password="1234"/>
</writeHost>
</dataHost>
<!-- server.xml -->
<user name="root" defaultAccount="true">
<property name="password">123456</property>
<property name="schema">DB_RW</property>
</user>
负载均衡 balance 取值的含义:
- 0:不开启读写分离机制,所有读操作都发送到当前可用的 writeHost 上。
- 1:全部的 readHost 与备用的 writeHost 都参与 select 语句的负载均衡(主要针对于双主双从模式)
- 2:所有的读写操作都随机在 writeHost、readHost 上分发。
- 3:所有的读请求随机分发到 writeHost 对应的 readHost 上执行,writeHost 不负担读压力。
问题: 主节点 Master 宕机之后,业务系统就只能够读,而不能写入数据了。
3.4.3 双主双从读写分离
双主双从,一个主机 Master1 用于处理所有写请求,它的从机 slave1 和另一台主机 Master2 还有它的从机 slave2 负责所有读请求。当 Master1 主机宕机后,Master2 主机负责写请求,Master1 和 Master2 互为备机。
主库配置 master1:
#修改 /etc/my.cnf
# mysql 服务id,保证整个集群环境中唯一,取值范围 1-2^32-1,默认为1.
server-id=1
#指定同步的数据库
binlog-do-db=db01
binlog-do-db=db02
binlog-do-db=db03
# 在作为从数据库的时候,有写入操作也要更新二进制日志文件
log-slave-updates
主库配置 master2:
#修改 /etc/my.cnf
# mysql 服务id,保证整个集群环境中唯一,取值范围 1-2^32-1,默认为1.
server-id=3
#指定同步的数据库
binlog-do-db=db01
binlog-do-db=db02
binlog-do-db=db03
# 在作为从数据库的时候,有写入操作也要更新二进制日志文件
log-slave-updates
两台主库创建账户并授权:
-- 创建 test 用户,并设置密码,该用户可在任意主机连接该 MySQL 服务
create user 'test'@'%' identified with mysql_native_password by '1123';
-- 为 test 用户分配主从复制权限
grant replication slave on *.* to 'test'@'%';
-- 查看两台主库的二进制日志坐标
show master status;
从库配置 slave1:
#修改 /etc/my.cnf
# mysql 服务id,保证整个集群环境中唯一,取值范围 1-2^32-1,默认为1.
server-id=2
从库配置 slave2:
#修改 /etc/my.cnf
# mysql 服务id,保证整个集群环境中唯一,取值范围 1-2^32-1,默认为1.
server-id=4
两台从库配置关联的主库:
需要注意 slave1 对应的是 master1,slave2 对应的是 master2
-- mysql 版本是 8.0.23 中语法如下
change replication source to source_host='xxx',source_user='xxx',source_password='xxx',source_log_file='xxx',source_log_pos=xxx;
-- mysql 版本是 8.0.23之前的版本语法如下
change master to master_host='xxx',master_user='xxx',master_password='xxx',master_log_file='xxx',master_log_pos=xxx;
-- 开启同步操作
-- 8.0.22 之后的语法
start replica;
-- 8.0.22 之前的语法
start slave;
-- 5. 查看主从同步状态
-- 8.0.22 之后的语法
show replica status\G;
-- 8.0.22 之前的语法
show slave status\G;
两台主库相互复制:Master2 复制 Master1,Master1 复制 Master2
-- mysql 版本是 8.0.23 中语法如下
change replication source to source_host='xxx',source_user='xxx',source_password='xxx',source_log_file='xxx',source_log_pos=xxx;
-- mysql 版本是 8.0.23之前的版本语法如下
change master to master_host='xxx',master_user='xxx',master_password='xxx',master_log_file='xxx',master_log_pos=xxx;
-- 开启同步操作
-- 8.0.22 之后的语法
start replica;
-- 8.0.22 之前的语法
start slave;
-- 5. 查看主从同步状态
-- 8.0.22 之后的语法
show replica status\G;
-- 8.0.22 之前的语法
show slave status\G;
双主双从的读写分离:MyCat 控制后台数据库的读写分离和负载均衡由 schema 标签的 balance 属性控制,通过 writeType 及 switchType 来完成失败自动切换。
<!-- schema.xml -->
<schema name="DB_RW" checkSQLschema="true" sqlMaxLimit="100" dataNode="dn1">
</schema>
<dataNode name="dn1" dataHost="dhost1" database="testdb"/>
<dataHost name="dhost1" maxCon="1000" minCon="10" balance="1" writeType="0" switchType="1" dbType="mysql" dbDriver="jdbc" slaveThreahold="100">
<heartbeat>select user()</heartbeat>
<!-- M1-S1 -->
<writeHost host="master1" url="jdbc:mysql://192.168.3.27:3306?useSSL=false" user="root" password="1234">
<readHost host="slave1" url="jdbc:mysql://192.168.3.28:3306?useSSL=false" user="root" password="1234"/>
</writeHost>
<!-- M2-S2 -->
<writeHost host="master2" url="jdbc:mysql://192.168.3.27:3306?useSSL=false" user="root" password="1234">
<readHost host="slave2" url="jdbc:mysql://192.168.3.28:3306?useSSL=false" user="root" password="1234"/>
</writeHost>
</dataHost>
<!-- server.xml -->
<user name="root" defaultAccount="true">
<property name="password">123456</property>
<property name="schema">DB_RW</property>
</user>
balance=“1”: 代表全部的 readHost 与 stand by writeHost 参与 select 语句的负载均衡,简单的说,当双主双从模式(M1->S1,M2->S2),并且M1和M2互为主备。正常情况下,M2、S1、S2都参与 select 语句的负载均衡。
writeType:
- 0: 写操作都转发到第一台 writeHost,writeHost 挂了,会切换到writeHost2上。
- 1: 所有的写操作都随机地发送到配置的 writeHost 上
switchType:
- -1: 不自动切换
- 1: 自动切换
4. 面试题
-
为什么InnoDB存储引擎选择使用B+Tree索引结构
相对于二叉树,层级更少,搜索效率高
相对于 B-Tree, 无论叶子节点还是非叶子节点,都会保存数据,这样导致页中存储的键值减少,指针跟着减少,要同样保存大量数据,只能增加树的高度,导致性能降低。
相对于 Hash 索引, B+Tree 支持范围匹配和排序操作。 -
一张表,有四个字段(id、username、password、status),由于数据量大,需要对以下SQL语句进行优化,该如何进行才是最优方案:
-- username\password 建立联合索引
select id,username,password from user where username = 'test';
5.参考文献
黑马程序员
MySQl 官方网站
可视化数据结构演示
MySQL doc
Mycat开源项目