Back
Featured image of post MySQL

MySQL

基础篇

MySQL概述

数据库相关概念

数据库(Data Base):存储数据的仓库,并且是有组织的存储。

数据库管理系统(Data Base Management System):操纵和管理数据库的大型软件。

SQL(Structured Query Language):操作关系型数据库的编程语言。

MySQL 数据库

# 连接 MySQL
mysql -u username -p

数据模型:客户端 » 数据库管理系统 » 数据库。

关系型数据库(RDBMS):由多张互相连接的二维表组成的数据库。

SQL

SQL 通用语法

-- 注释写法1 : 不区分大小写,关键字建议大写
# 注释写法2 : 可以使用缩进或空格
/*	注释写法3 */

SQL 分类

DDL(Data Definition Language):定义数据库、表、字段。

DML(Data Manipulation Language):表中数据增、删、改。

DQL(Data Query Language):查询数据库表记录。

DCL(Data Control Language):创建数据库用户、控制数据库访问权限。

数据类型

数值类型:

TINYINT(1 byte)、SMALLINT(2 bytes)、MEDIUMINT(3 bytes)、INT(4 bytes)、BIGINT(8 bytes)、FLOAT(4 bytes)、DOUBLE(8 bytes)。

字符串类型:

CHAR、VARCHAR、TINYBLOB、TINYTEXT、BLOB、TEXT、MEDIUMBLOB、MEDIUMTEXT、LONGBLOB、LONGTEXT。

日期类型:

DATE(YYYY-MM-DD)、TIME(HH:MM:SS)、YEAR(YYYY)、DATETIME(YYYY-MM-DD HH:MM:SS)、TIMESTAMP(YYYY-MM-DD HH:MM:SS)

DDL

/* 数据库的操作 */
# 查询所有数据库
SHOW DATABASES;

# 查询当前数据库
SELECT DATABASE();

# 创建数据库
CREATE DATABSE [IF NOT EXISTS] 数据库名 [DEFAULT CHARSET 字符集] [COLLATE 排序规则];

# 使用数据库 
USE 数据库名;

# 删除
DROP DATABASE [IF NOT EXISTS] 数据库名;
/* 表操作 */
# 查询所有数据表
SHOW TABLES;

# 查询表结构
DESC 表名;

# 查询表的建表语句
SHOW CREATE TABLE 表名;

# 创建表
CREATE TABLE 表名(
    字段1 字段1类型[COMMENT 字段1注释],
	字段2 字段2类型[COMMENT 字段2注释],
    字段3 字段3类型[COMMENT 字段3注释],
)[COMMENT 表注释];

# 添加字段
ALTER TABLE 表名 ADD 字段名 类型(长度) [COMMENT 注释][约束];

# 修改数据类型
ALTER TABLE 表名 MODIFY 字段名 新数据类型(长度);

# 修改字段名和字段类型
ALTER TABLE 表名 CHANGE 旧字段名 新字段名 类型(长度) [COMMENT 注释] [约束];

# 删除字段
ALTER TABLE 表名 DROP 字段名;

# 修改表名
ALTER TABLE 表名 RENAME TO 新表名;

# 删除表
DROP TABLE [IF EXISTS] 表名;

# 删除指定表并重新创建该表
TRUNCATE TABLE 表名;

DML

# 给指定字段添加数据
INSERT INTO 表名(字段名1, 字段名2, ...) VALUES(值1, 值2, ...);

# 给全部字段添加数据
INSERT INTO 表名字 VALUES(值1, 值2, ...);

# 修改数据
UPDATE 表名 SET 字段名1 =1, 字段名2 =2, ... [WHERE 条件];

# 删除数据
DELETE FROM 表名 [WHERE 条件];

DQL

SELECT
	字段列表
FROM
	表名列表
WHERE
	条件列表
GROUP BY
	分组字段列表
HAVING
	分组后条件列表
ORDER BY
	排序字段列表
LIMIT
	分页参数
# 查询多个字段
SELECT 字段1, 字段2, 字段3 ... FROM 表名;
SELECT * FROM 表名;

# 设置别名
SELECT 字段1[AS 别名1], 字段2[AS 别名2] .. FROM 表名;

# 去除重复记录
SELECT DISTINCT 字段列表 FROM 表名;

# 查询没有身份证号的员工信息
SELECT * FROM emp WHERE idcard IS NULL;

# 查询 15岁 到 20岁 之间的员工信息
SELECT * FROM emp WHERE age >= 15 and age <= 20;
SELECT * FROM emp WHERE age BETWEEN 20 and 15;

# 查询年龄为 18岁 或 20岁 或 40岁 的员工信息
SELECT * FROM emp WHERE age = 18 OR age = 20 OR age = 40;
SELECT * FROM emp WHERE age IN(18, 20, 40);

# 查询姓名为两个字的员工信息
SELECT * FROM emp WHERE name LIKE '__';

# 查询身份证最后一位是X的员工信息
SELECT * FROM emp WHERE idcard LIKE '%X';

# 统计企业员工数据量
SELECT COUNT(*) FROM emp;

# 根据性别分组,统计男性员工和女性员工的数量
SELECT gender, count(*) FROM emp GROUP BY gender;

# 查询年龄小于 45岁 的员工,并根据工作地址分组,获取员工数量大于3的工作地址
SELECT workaddress, count(*) FROM emp WHERE age<45 GROUP BY workaddress HAVING count(*)>3;

# 根据年龄对公司的员工进行升序排序
SELECT * FROM emp ORDER BY age ASC;

# 根据入职时间对员工进行降序排序
SELECT * FROM emp ORDER BY entrydate DESC;

# 先按年龄升序排,年龄同的按入职时间降序排
SELECT * FROM emp ORDER BY age ASC, entrydate DESC;

# 查询第 1 页员工数据,每页展示 10 条记录
SELECT * FROM emp LIMIT 0, 10;

# 查询第 2 页员工数据,每页展示 10 条记录
SELECT * FROM emp LIMIT 10,10;

DCL

# 查询用户
USE mysql;
SELECT * FROM USER;

# 创建用户
CREATE USER '用户名'@'主机名' IDENTIFIED BY '密码';

# 修改用户密码
ALTER USER '用户名'@'主机名' IDENTIFIED WITH mysql_native_password BY '新密码';

# 删除用户
DROP USER '用户名'@'主机名';

# 查询权限
SHOW GRANTS FOR '用户名'@'主机名';

# 授予权限
GRANT 权限列表 ON 数据库名.表名 TO '用户名'@'主机名';

# 撤销权限
REVOKE 权限列表 ON 数据库名.表名 FROM '用户名'@'主机名';

函数

字符串函数

-- concat
SELECT CONCAT('Hello ', 'MySQL');

-- LOWER
SELECT LOWER('Hello');

-- UPPER
SELECT UPPER('Hello');

-- LPAD
SELECT LPAD('01', 5, '-');

-- RPAD
SELECT RPAD('01', 5, '-');

-- TRIM
SELECT TRIM(' Hello MySQL ');

-- SUBSTRIMG
SELECT SUBSTRING('Hello MySQL', 1, 5);

数值函数

-- CEIL(x)
SELECT CEIL(1.1);

-- FLOOR(x)
SELECT FLOOR(1.9);

-- MOD(x,y)
SELECT MOD(3, 4);

-- RAND()
SELECT RAND();

-- ROUND(x,y)
SELECT ROUND(3.1415926, 2);

日期函数

-- CURDATE()
SELECT CURDATE();

-- CURTIME()
SELECT CURTIME();

-- NOW()
SELECT NOW();

-- YEAR, MONTH, DAY
SELECT YEAR(NOW());
SELECT MONTH(NOW());
SELECT DAY(NOW());

-- DATE_ADD(date, INTERVAL expr type)
SELECT DATE_ADD(NOW(), INTERVAL 70 DAY);

-- DATEDIFF(date1, date2)
SELECT DATEDIFF('2024-02-29', '2023-11-11');

流程函数

-- IF
SELECT IF(FALSE, 'OK', 'ERROR');

-- IFNULL
SELECT IFNULL('Get', 'DEFAULT');
SELECT IFNULL('', 'DEFATLT');
SELECT IFNULL(null, 'DEFATLT');

-- CASE WHEN[val1] THEN[res1] ... ELSE[default] END
SELECT name, CASE WHEN score >= 85 THEN '优秀' WHEN score >= 60 THEN '及格' ELSE '不及格' END FROM student_score;

-- CASE [expr] WHEN[val1] THEN[res1] ... ELSE[default] END
SELECT name, CASE workaddress WHEN '北京' THEN '一线城市' WHEN '上海' THEN '一线城市'ELSE '二线城市' END FROM emp; 

约束

约束包括:

  • 1、主键约束:每个表会有一个唯一标识表的字段。
  • 2、外键约束:表中的某字段由另外一个表的主键决定。
  • 3、唯一约束:确保该字段的每个字段值是唯一的,允许空值。
  • 4、检查约束:用于检查数据表中,字段值是否有效。
  • 5、非空约束:用来约束数据表中,字段值不能为空。
  • 6、默认值约束:当表中某字段不输入值时,自动为其添加设定好的值。

常见约束

CREATE TABLE USER(
	id INT PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
    name VARCHAR(10) NOT NULL UNIQUE COMMENT '姓名',
    age INT CHECK(age > 0 AND age <= 120) COMMENT '年龄',
    status CHAR(1) DEFAULT '1' COMMENT '状态',
	gender CHAR(1) COMMENT '性别'
) COMMENT '用户表';

外键约束

CREATE TABLE emp(
    id INT PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
    name VARCHAR(10) NOT NULL UNIQUE COMMENT '姓名',
	age INT CHECK(age > 0 AND age <= 120) COMMENT '年龄',
    salary INT CHECK(salary > 0) NOT NULL COMMENT '薪资',
    entrydate NOT NULL COMMENT '入职时间',
    managerid INT COMMENT '直属领导ID',
    dept_id INT '部门ID'
) COMMENT '员工表';

CREATE TABLE dept(
	id INT PRIMARY KEY AUTO_INCREMENT COMMENT 'ID',
    name VARCHAR(50) NOT NULL COMMENT '部门名称'
) COMMENT '部门表';

# 添加外键
ALTER TABLE emp ADD CONSTRAINT fk_emp_dept_id FOREIGN KEY (dept_id) REFERENCES dept(id) ON UPDATE CASCADE ON DELETE CASCADE;

ALTER TABLE emp ADD CONSTRAINT fk_emp_dept_id FOREIGN KEY (dept_id) REFERENCES dept(id) ON UPDATE CASCADE ON DELETE SET NULL;

# 删除外键
ALTER TABLE emp DROP FOREIGN KEY fk_emp_dept_id;

多表查询

多表关系

CREATE TABLE student(
	id INT PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
    name VARCHAR(10) COMMENT '姓名',
    sid VARCHAR(10) COMMENT '学号'
) COMMENT '学生表';

CREATE TABLE course(
	id INT PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
    name VARCHAR(10) COMMENT '课程名称'
)COMMENT '课程表';

CREATE TABLE student_course(
	id INT PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
    studentid INT COMMENT '学生ID',
    courseid INT COMMENT '课程ID',
    CONSTRAINT fk_courseid FOREIGN KEY (courseid) REFERENCES course(id),
    CONSTRAINT fk_studentid FOREIGN KEY (studentid) REFERENCES student(id)
)COMMENT '学生选课表';

多表查询概述

# 多表查询 - 笛卡尔积
SELECT * FROM emp, dept WHERE emp.detp_id = dept.id;

内连接

# 内连接查询的是两张交集的部分
SELECT 字段列表 FROM1, 表2 WHERE 条件 ...;
SELECT 字段列表 FROM1 [INNER] JOIN2 ON 连接条件;

外连接

# 左外连接
SELECT 字段列表 FROM1 LEFT [OUTER] JOIN2 ON 条件 ...;

# 右外连接
SELECT 字段列表 FROM1 RIGHT [OUTER] JOIN2 ON 条件 ...;

自连接

# 自连接可以是内连接也可以是外连接
SELECT 字段列表 FROMA 别名A JOINA 别名B ON 条件 ...;
SELECT 字段列表 FROMA 别名A LEFT[OR RIGHT] [OUTER] JOINB 别名B ON 条件 ...;

联合查询

# UNION ALL 是不合并相同信息
SELECT * FROM emp WHERE salary < 5000
UNION ALL
SELECT * FROM emp WHERE age > 50;

子查询

# 嵌套查询就是子查询
SELECT * FROM t1 WHERE column1 = (SELECT column1 FROM t2);

# 查询销售部所有员工信息
SELECT * FROM emp WHERE dept_id = (SELECT id FROM dept WHERE name = '销售部');

# 查询销售部和市场部所有员工信息
SELECT * FROM emp WHERE dept_id IN((SELECT id FROM dept WHERE name IN('销售部', '市场部')));

# SOME、ANY、ALL 使用
# 比财务部所有人工资都高
SELECT * FROM emp WHERE salary > ALL(SELECT salary FROM emp WHERE dept_id = (SELECT id FROM dept WHERE name = '财务部'));

事务

事务:就是一组操作,不可分割单工作单位,将作为一个整体提交到系统。

默认 MySQL 的事务是自动提交的,也就是,当执行一条 DML 语句, MySQL 会隐式地提交事务。

事务操作

# 查看、设置事务提交方式
SELECT @@autocommint;
SET @@autocommint = 0;

# 开启事务
START TRANSACTION;
BEGIN;

# 提交事务
COMMIT;

# 回滚事务
ROLLBACK;
CREATE TABLE account(
	ID INT AUTO_INCREMENT PRIMARY KEY  COMMENT '主键',
	NAME VARCHAR(10) COMMENT '姓名',
	MONEY INT COMMENT '余额'
) COMMENT '账户表';
INSERT INTO account(ID, NAME, MONEY) VALUES (null, '张三', 2000), (null, '李四', 2000);

# 恢复数据
UPDATE account SET MONEY = 2000 WHERE NAME IN('张三', '李四');

# 张三给李四转账 1000
# 1.查询张三账户余额
SELECT MONEY FROM account WHERE name = '张三';
# 2.将张三用户余额 -1000
UPDATE account SET MONEY = MONEY - 1000 WHERE name = '张三';
# 3.将李四用户余额 +1000
UPDATE account SET MONEY = MONEY + 1000 WHERE name = '李四';

事务特性

原子性(Atomicity):事务不可分割。

一致性(Consistency):事务完成时,所有数据状态一致。

隔离性(Isolation):保证事务在不受并发操作下独立运行。

持久性(Durabiliry):事务一旦提交或回滚,数据改变是永久的。

并发事务

脏读:一个事务读到另一个事务还没有提交的数据。

不可重复读:一个事务先后读取同一条记录,但是两次读取的数据不同。

幻读:查询时没有对应数据行,但插入数据时数据又存在。

事务隔离级别

隔离级别 脏读 不可重复读 幻读
Read uncommitted
Read committed ×
Repeatable Read (Default) × ×
Serializable × × ×
# 查看事务隔离级别
SELECT @@TRANSACTION_ISOLATION;

# 设置事务隔离级别
SET [SESSION|GLOBAL] TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE};

进阶篇

Linux MySQL 使用

MySQL 服务

# 启动
systemctl start mysqld

# 重启
systemctl restart msyqld

# 停止
systemctl stop mysqld

查询自动生成的 root 密码

grep 'temporary password' /var/log/mysqld.log

修改 root 密码

# 设置密码复杂度和密码长度
SET GLOBAL VALIDATE_PASSWORD.policy = 0;
SET GLOBAL VALIDATE_PASSWORD.length = 4;

# 修改密码
ALTER USER 'root'@'localhost' IDENTIFIED BY '1234';

创建用户

CREATE USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '1234';

分配所有权限

grant all on *.* to 'root'@'%';

存储引擎

MySQL 体系结构

MySQL 客户端 « === » MySQL Server (连接层 + 服务层 + 引擎层 + 存储层)

MySQL Server = 连接池 + 系统管理工具 + SQL 接口 + 解析器 + 查询优化器 + 缓存 + 可插拔存储引擎 + 系统文件 + 文件和日志

存储引擎简介

存储引擎就是存储数据、建立索引、更新/查询数据等技术的实现方式。存储殷勤是基于表的,而不是基于库的,所以存储引擎也可被称为表类型。

# 创建表时指定存储引擎
CREATE TABLE 表名(

)ENGINE = INNODB
# 查询数据库支持的存储引擎
SHOW ENGINES

存储引擎特点

InnoDB:高可靠性和高性能的通用存储引擎。

xxx.ibd:xxx 代表表名,innoDB 引擎每张表都会对应一个表空间文件,存储该表的表结构(frm、sdi)、数据和索引。

Tablespace -> Segment -> EXtent -> Page -> Row (Trx id + Roll Pointer + col1 + col2 + col3 + …)

  • DML 操作遵循 ACID 模型,支持事务;
  • 行级锁,提高并发访问性能;
  • 支持外键 FOREIGN KEY 约束,保证数据的完整性和正确性。

MyISAM:MySQL 早期默认存储引擎。

xxx.sdi:存储表结构信息。

xxx.MYD:存储数据。

xxx.MYI:存储索引。

  • 不支持事务,不支持外键;
  • 支持表锁,不支持行锁;
  • 访问速度快。

Memoery:表数据存储在内存中,只做临时表或缓存。

xxx.sdi:存储表结构信息。

  • 内存存放;
  • hash 索引(默认)。

存储引擎选择

InnoDB:对事务完整性要求高,并发条件下数据的一致性,除了插入和查询外,还包含很多更新、删除,则选该引擎。

MyISAM:以读取和插入为主,很少更新和删除操作,对事务完整性、并发性要求不是很高,就用该引擎。

MEMORY:访问速度快,但大小有限制,因为是临时存储也无法保证数据的安全性。

索引

索引概述

索引(Index) : 是帮助 MySQL 高效获取数据的数据结构。数据之外,数据库管理系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法,这种数据结构就是索引。

  • 优点:提高检索的效率,降低数据库的 IO 成本;通过索引列对数据进行排序,降低数据排序的成本,降低CPU的消耗;
  • 缺点:索引列也是要占用空间的;索引提高了查询效率,同时也降低了更新表的速度。

索引结构

MySQL 的索引是在存储引擎层实现的,不同的存储引擎有不同的结构:

B+Tree:最常见的索引类型,大部分引擎支持。

Hash:底层数据用哈希表实现,只有精确匹配索引列查询才有效。

R-tree:空间索引为MyISAM引擎的特殊索引类型,主要用于地理空间数据类型。

Full-text:一种通过建立倒排索引,快速匹配文档的方式。

二叉树:顺序插入到时候,会形成一个链表,查询性能会大大降低,大数据量情况下,层级较深,检索速度慢。

红黑树:大量数据情况下,层级深,检索速度慢。

B-Tree:(最大度数为 5 的 b-tree 每个节点存 4 个 key)。

B+-Tree:所有的数据都有在叶子节点形成单向链表,非叶子节点起到索引的作用。

MySQL 索引在经典 B+Tree 进行了优化,增加了一个指向相邻叶子节点的链表指针。

Hash 索引就是采用一定的 hash 算法,将键值换算成新的 hash 值,再映射到对应的槽位上,然后存储在 hash 表中。如果两个或多个键值,映射到相同的位置上,就产生了哈希冲突,可以通过链表来解决。

Hash的特点:不支持范围搜索,无法通过索引完成排序,查询效率高。

Memory 引擎支持 hash 索引,而 InnoDB 自适应 hash 功能。

为什么 InnoDB 用 B+tree 索引结构?

相对于二叉树,层级更少,搜索的效率高;

对于 B-tree ,因为他叶子和非叶子都保存数据,这样一个页内存储的键值对少,如果要保存大量数据,只能增加树都高度;

相对于 Hash 索引,B+tree 支持范围匹配和排序操作。

索引分类

主键索引:针对表中主键创建的索引,默认自动创建。PRIMARY。

唯一索引:避免同一个表中某数据列中的值重复,可以有多个。UNIQUE。

常规索引:快速定位特定数据,可以有多个。

全文索引:全文索引查找的是文本中的关键词,可以有多个。FULLTEXT。

在 InnoDB 存储引擎中,根据索引的存储形式,还可以划分:

聚群索引(Clustered Index)将数据与索引放到一块,索引结构的叶子节点保存了数据。必须有,且只一个。

  • 如果存在主键,主键索引就是聚集索引。
  • 如果不存在主键,将使用第一个 UNIQUE 索引作为聚集索引。
  • 如果都没有,则 InnoDB 自动生成 rowid 为隐藏的聚集索引。

二级索引(Secondary Index)将数据与索引分开存储,索引结构的叶子节点关联对应主键。可以存在多个。

例如:SELECT * FROM USER WHERE NAME='Arm';

1、进行 NAME 字段的二级索引,找到对应的 ID;

2、根据 ID 进行回表查询,即聚集索引找到 ID 下那一行的数据。

索引语法

# 创建索引
CREATE [UNIQUE | FULLTEXT] INDEX index_name ON table_name (index_col_name, ...);

# 查看索引
SHOW INDEX FROM table_name;

# 删除索引
DROP INDEX index_name ON table_name;

SQL 性能分析

SQL 执行频率:SHOW [SESSION|GLOBAL] STATUS; 可以查看数据库指令的访问频次。

SHOW GLOBAL STATUS LIKE 'com______';

慢查询日志:它记录了所有执行时间超过指定参数(long_query_time,单位:秒,默认 10 秒)的所有 SQL 语句的日志。MySQL 的慢查询日志默认没有开启,需要在 MySQL 的配置文件(/etc/my.cnf)中配置信息:

# 开启 MySQL 慢日志查询
slow_query_log = 1

# 设置超 2 秒就视为慢查询
long_query_time = 2

# 重启后,慢查询日志
SHOW VARIABLES LIKE 'slow_query_log';

# 生成文件 /var/lib/mysql/localhost-slow.log

show profiles 能够在 SQL 优化时帮我们了解时间都耗费在哪里。通过 have_profiling 参数,能看见 MySQL 是否支持 profile 操作:

# 默认关闭,通过 SET 在 session / global 开启
SET profiling = 1;

# 查看 profiles 是否开启
SELECT @@have_profiling;

# 查看每一条 SQL 的耗时基本情况
SHOW profiles;

# 查看指定 query_id 的 SQL 语句各个阶段的耗时情况
SHOW profile FOR query query_id;

# 查看指定 query_id 的 SQL 语句 CPU 使用情况
SHOW profile cpu FOR query query_id;

EXPLAIN 或 DESC 命令获取 MySQL 如何执行 SELECT 语句的信息,包括在 SELECT 语句执行过程中表如何连接或连接的顺序。

# 直接在 SELECT 语句前加上关键字 explain / desc
EXPLAIN SELECT 字段 FROM 表名 WHERE 条件;

/*
	id 相同,从上到下顺序执行;不同则值大的先执行。
	select_type 表示 SELECT 类型。
	tyep 表连接类型,性能好到差为 NULL、system、const、eq_ref、ref、range、index、all。
	possible_key 显示可能应用这个表上的索引,一个或多个。
	key 为实际使用的索引。
	key_len 索引中使用的字节数,即索引字段最大可能值。
	rows MySQL 认为必须要执行查询的行数,这是估计值。
	filtered 表示返回结果的行数占所需读取行数的百分比,值越大越好。
*/

索引使用

验证索引效率

# 未建立索引前执行 SQL
SELECT * FROM tb_sku WHERE sn = '100000003145001';
# 针对字段建立索引
CREATE INDEX idx_sku_sn ON tb_sku(sn);
# 再次执行
SELECT * FROM tb_sku WHERE sn = '100000003145001';

最左前缀法:如果进行了联合索引,要遵循最左前缀法则。最左前缀法则指的是查询从索引的最左列开始,并且不跳过索引中的列。如果跳跃某一列,索引将部分失效(后面的字段索引失效)。

范围查询:联合索引中,出现范围查询(>, <),范围查询右侧的列索引失效。在业务允许的情况,尽量使用 >= 或 <=。

索引列运算:不要在索引列上进行运算操作,索引将失效。此外,如果字符串不加引号,也不会走索引的。

模糊查询:如果仅仅是尾部模糊匹配,索引不会失效。如果是头部模糊匹配,索引失败。

or 连接条件:如果 or 前字段有索引,而后面的没有索引,则不会用到索引。

数据分布影响:如果 MySQL 评估使用索引比全表更慢,则不使用索引。

SQL 提示:就是在 SQL 语句中加入一些人为的提示来达到优化操作的目的。

# USE INDEX
explain select * from tb_user use index(idx_user_pro) where profession='软件工程';

# IGNORE INDEX
explain select * from tb_user ignore index(idx_user_pro) where profession='软件工程';

# FORCE
explain select * from tb_user force index(idx_user_pro) where profession='软件工程';

覆盖索引:尽量使用覆盖索引(查询使用了索引,且需要返回的列,在该索引中已经全部能够找到),减少 select *。

using index condition : 查找使用了索引,但是需要回表查询数据。

using where; using index :查找使用了索引,但是需要的数据都在索引列中能找到,所以不需要回表查询数据。

前缀索引:当字段类型为字符串时,要索引很长的字符串,这样会浪费磁盘IO,影响查询效率。此时可以只将字符串的一部分前缀,建立索引,这样可以节约索引空间。

CREATE INDEX idx_xxx ON table_name(column(n));

# 前缀长度:不重复数据 和 总数据的比值
SELECT count(distinct email)/count(*) FROM tb_user;
SELECT count(distinct substring(email, 1,5)) / count(*) FROM tb_user;

单列索引:即一个索引只包含单个列。

联合索引:即一个索引包含了多个列。

业务场景中,如果存在多个查询条件,考虑针对于查询字段建立索引时,建议建立联合索引,而非单列索引。

# 单列索引情况 MysQL 优化器会自动评估哪个字段的索引效率更高
EXPLAIN select id,phone,name from tb_user where phone='12314123' and name = '李白';

索引设计原则

1、数据量大,且查询比较频繁就建立索引;

2、针对尝作为查询、排序、分组的字段建立索引;

3、选择区分度高的建立索引,尽量建立唯一索引;

4、字符串类型字段可建立前缀索引;

5、尽量使用联合索引,很多时候可以覆盖索引;

6、控制索引数量,维护索引结构代价要考虑;

7、索引列不能存储NULL值,就用NOT NULL进行约束。

SQL 优化

插入数据

-- 批量插入
INSERT INTO tb_test VALUES (1, 'Tom'), (2, 'Cat');

-- 手动提交事务
START TRANSACTION;
INSERT INTO tb_test VALUES (1, 'Tom'), (2, 'Cat');
INSERT INTO tb_test VALUES (3, 'Jerry'), (4, 'Mike');
COMMIT;

-- 主键顺序插入
-- 大批量插入
# 连接服务器
mysql --local-infile -u root -p

# 设置变量,从本地加载文件导入数据
SET GLOBAL local_infile = 1;

# 执行 load 指令,加载数据到表
load data local infile '/root/sql1.log' into table 'tb_user' fields terminated by ',' lines terminated by '\n';

主键优化

在 InnoDB 存储引擎中,表数据都是根据主键顺序组织存放的,这种存储方式的表称为 索引组织表(index organized table IOT)。

逻辑结构为:TableSpace > Segement > Extend > Page > Row。

页可以为空,也可以填充一半。每个页包含了 2-N 行数据,如果一行数据太大会行溢出,是根据主键排序的。

页分裂:主键乱序插入的时候会新建一个页空间,将原有的页一分为二,然后插入新数据。

页合并:当页删除记录达到 MERGE_THRESHOLD,会查找前后页是否可以进行合并。

设计原则:

  • 满足业务需求的情况下,尽量降低主键的长度;
  • 插入数据尽量顺序插入,选择 AUTO_INCREMENT;
  • 尽量不用 UUID 做主键或是其他自然主键,如身份证号;
  • 业务操作时,尽量不对主键修改。

order by 优化

  • Using filesort:通过表索引或全表扫描,读取满足条件的数据行,然后在排序缓冲区 sort buffer 中完成排序操作,所有不是通过索引直接返回排序结果的排序都叫 FileSort 排序。
  • Using index:通过有序索引顺序扫描直接返回有序数据,不需要额外排序。
  • 可以通过指定索引时排序字段的升降序,来进行优化;
  • 注意要遵循左前缀法进行索引。
  • 如果要 filesort 可以适当增加排序缓冲区大小 sort_buffer_size。

group by 优化

  • 分组操作时,可以通过索引来提高效率。
  • 分组操作时,索引的使用也是满足最左前缀法。

limit 优化

# 不直接使用 limit 获取全部数据,而是只获取 id 然后再作为表查询
SELECT s.* FROM tb_sku s, (SELECT id FROM tb_sku ORDER BY id LIMIT 9000000, 10) a WHERE s.id = a.id;

count 优化

MyISAM 引擎会把一个表的总行数存在磁盘上;InnoDB 引擎则需要一行一行地计算。

# 效率排行 :count(*) > count(1) > count(主键) > count(字段)

update优化

InnoDB 的行锁是针对索引加的锁,不是针对记录加的锁,并且该索引不能失效,否则会从行锁升级为表锁。

视图/存储过程/触发器

视图

视图(View)是一种虚拟存在的表。视图中的数据并不在数据库中实际存在,行和列数据来自定义视图的查询中使用的表,并且是在使用视图时动态生成的。其实,视图就是保留了 SQL 逻辑,不保存查询结果。

WITH CHECK OPTION 子句创建时,MySQL 会检查正在更改的行是否符合视图的定义。MySQL 允许基于另一个视图创建视图,它还会检查依赖视图中的规则以保持一致性。为了确定检查范围,MySQL 提供了两个选项: CASCADED(默认) 和 LOCAL。

# 创建
CREATE [OR REPLACE] VIEW 视图名称[(列名列表)] AS SELECT语句 [with cascaded check option]; 

# 查询
SHOW CREATE VIEW 视图名称;
SELECT * FROM 视图名称 ...;

# 修改
CREATE [OR REPLACE] VIEW 视图名称[(列名列表)] AS SELECT语句ALTER VIEW 视图名称[(列名列表)] AS SELECT语句
# 删除
DROP VIEW [IF EXISTS] 视图名;

视图若包含一下项则不可更新:

  • 1、聚合函数或窗口函数(SUM()、MIN()、MAX()、COUNT()等);
  • 2、DISTINCT、GROUP BY、HAVING、UNION 或 UNION ALL。

作用:

  • 经常使用的查询可以被定义为视图,从而用户不必每次操作都指定操作来进行查询了;
  • 数据库授权,通过视图用户只能查询和修改所能见到的数据;
  • 视图可以帮助用户屏蔽真实表结构变化带来的影响。

存储过程

存储过程就是事先经过编译并存储在数据库中的一段 SQL 语句的集合,调用存储过程可以简化应用开发人员的很多工作,减少数据在数据库和应用服务器之间的传输,对于提高数据处理的效率是有好处的。

存储过程思想上,就是数据库 SQL 语句层面的代码封装与重用。可以接收参数,也可以返回数据,减少网络交互,提高效率。

# 创建
# 通过关键字 delimiter 指定 SQL 语句结束符
/*
delimiter $$
CREATE PROCEDURE 存储过程名称([参数列表])
BEGIN
	-- SQL 语句
END$$
*/
CREATE PROCEDURE 存储过程名称([参数列表])
BEGIN
	-- SQL 语句
END;

# 调用
CALL 名称([参数]);

# 查看
SELECT * FROM INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_SCHEMA = 'xxx';
SHOW CREATE PROCEDURE 存储过程名称;

# 删除
DROP PROCEDURE [IF EXISTS] 存储过程名称;

系统变量 是 MySQL 服务器提供,不是用户定义的,属于服务器层面。分为全局变量(GLOBAL)、会话变量(SESSIOM)。

  • 默认是改变 SESSION 变量。
  • MySQL 服务器重启后,设置的全局参数会失效,想要不失效,可以在 /etc/my.cnf 中配置。
# 查看系统变量
SHOW [SESSION|GLOBAL] VARIABLES;
SHOW [SESSION|GLOBAL] VARIABLES LIKE ...;
SELECT @@[SESSION|GLOBAL] 系统变量名;

# 设置系统变量
SET [SESSION|GLOBAL] 系统变量名 = 值;
SET @@[SESSION|GLOBAL] 系统变量名 = 值;

用户定义变量 是用户根据自己定义的变量,用户变量不用提前声明,在用的时候直接用 “@变量名” 使用。其作用域为当前连接。

# 赋值
SET @var_name = expr;
SET @var_name := expr;
SELECT @var_name := expr;
SELECT 字段名 INTO @var_name FROM 表名;
# 使用
SELECT @var_name;

局部变量 根据需要定义的在局部生效的变量,访问之前,需要 DECLARE 声明。可用作存储过程内的局部变量和输入参数,局部变量的范围是在其内声明的 BEGIN … END 块。

# 声明
DECLARE 变量名 变量类型 [DEFAULT ...];
# 赋值
SET 变量名 = 值;
SET 变量名 := 值;
SELECT 字段名 INTO 变量名 FROM 表名 ...;
# if 语句
IF 条件1 THEN
...
ELSEIF 条件2 THEN	
...
ELSE
...
END IF;
#  参数 IN、OUT、INOUT
CREATE PROCEDURE 存储过程名称([IN/OUT/INOUT 参数名 参数类型])
BEGIN
	-- SQL 语句
END;
# case 语句
CASE case_value
	WHEN when_value1 THEN statement_list1
	...
END CASE;

CASE
	WHEN search_condition1 THEN statement_list1
	...
END CASE;
# while 语句
WHILE 条件 DO
	SQL 逻辑
END WHILE;
# repeat 语句
REPEAT
	SQL 逻辑
	UNTIL 条件
END REPEAT;
# loop 函数
# LEAVE:退出循环;ITERATE:跳过当前循环,进入下次循环。
[begin_label:]LOOP
	SQL 逻辑
END LOOP [end_label];

CREATE PROCEDURE pp(IN n INT)
BEGIN
	DECLARE total INT DEFAULT 0;
	
	sum:LOOP
		IF n <= 0 THEN
			LEAVE sum;
		END IF;
		
		SET total := total + n;
		SET n := n - 1;
	END LOOP sum;
	SELECT total;
END;
/* 
	游标(CURSOR)用来存储查询结果集的数据类型,
	在存储过程和函数中可使用游标对结果集进行循环处理。
*/
# 游标声明
DECLARE 游标名称 CURSOR FOR 查询语句;
# 打开游标
OPEN 游标名称;
# 获取游标记录(循环获取)
FETCH 游标名称 INTO 变量;
# 关闭游标
CLOSE 游标名称;
# 条件处理程序 Handler
# handler_action:CONTINUE 继续执行当前程序、EXIT 终止执行当前程序。
# SQLSTATE sqlstate_value 状态码、SQLWARNING 以01开头的SQLSTATE代码简写、NOT FOUND 以02开头的SQLSTATE代码简写、SQLEXCEPTION 其他。
DECLARE handler_action HANDLER FOR condition_value statement;

DECLARE exit HANDLER FOR SQLSTATE '02000' CLOSE u_cursor;

存储函数

存储函数是有返回值的存储过程,存储函数的参数只能是 IN 类型的。

/*
	DETREMINISTIC:相同的输入参数总是产生相同的结果;
	NO SQL:不包含 SQL 语句;
	READS SQL DATA:包含连续读取数据的语句,但不包含写入数据语句。
*/
CREATE FUNCTION 存储函数名称([参数列表])
RETURNS type [characteristic ...]
BEGIN
	-- SQL 语句
	RETURN ...;
END;

触发器

触发器是与表有关的数据库对象。在 insert/update/delete 之前或之后,触发并执行触发器中定义的 SQL 语句集合。

触发器可以协助应用在数据库端保证数据的完整性,日记记录,数据校验等操作。

OLD 和 NEW 引用触发器中变化的记录内容。支持行级触发。

INTER 型触发器:NEW 表示将要或者已经新增的数据;

UPDATE 型触发器:OLD 表示修改之前的数据,NEW 表示将要或已经修改后的数据。

DELETE 型触发器:OLD 表示将要或已经删除的数据。

# 创建
CREATE TRIGGER trigger_name
BEFORE / AFTER INSERT / UPDATE / DELETE
ON table_name FOR EACH ROW
BEGIN
	triggle_statement;
END;
# 查看
SHOW TRIGGERS;
# 删除
DROP TRIGGER [schema_name.]trigger_name;
-- 通过触发器记录 tb_user 表的变更数据,将变更日志插入到日志表 user_logs 中,包含增加、修改、删除
# 日志表 user_log
create table user_logs(
	id int(11) not null auto_increment;
    operation varchar(20) not null comment '操作类型',
    operation_tim datetime not null comment '操作时间',
    operate_id int(11) not null comment '操作ID',
    operate_params varchar(500) comment '操作参数',
    primary key(id)
)engine=innodb default charset=utf8;

# 插入数据触发器
create trigger tb_user_insert_trigger
	after insert on tb_user for each row
begin
	insert into user_logs(id, operation, operate_time, operate_id, operate_params) values (null, 'insert', now(), new.id, concat('插入的数据内容为:id=', new.id, ', name=', new.name, ', phone=', new.phone, ', email=', new.email))
end;

# 更新数据触发器
create trigger tb_user_update_trigger
	after insert on tb_user for each row
begin
	insert into user_logs(id, operation, operate_time, operate_id, operate_params) values (null, 'update', now(), new.id, concat('更新前内容为:id=', old.id, ', name=', old.name, ', phone=', old.phone, ', email=', old.email,'更新后内容为:id=', new.id, ', name=', new.name, ', phone=', new.phone, ', email=', new.email))
end;

锁是计算机协调多个进程或线程并发访问某一资源的机制。

  • 全局锁:锁定数据库中的所有表。
  • 表级锁:每次操作都锁住整张表。
  • 行级锁:每次操作锁住对应的行数据。

全局锁

加锁后,整个实例就处于只读状态。

典型场景为全库数据的逻辑备份,对所有表进行锁定。

1、如果在主库备份,那么备份期间数据不能更新,业务基本停摆;

2、如果在从库备份,那么备份期间无法执行主库同步过来的二进制日志,主从不一致。

# 不加锁一致性数据备份
mysqldump --single-transaction -h [address] -uroot -p1234 TestDB > TestDB.sql;
# 加锁
flush tables with read lock;

# 备份
mysqldump -h [address] -uroot -p1234 TestDB > TestDB.sql;

# 解锁
unlock tables;

表级锁

锁定粒度大,发生锁冲突的概率最高,并发度最低。

主要有这几类:

  • 表锁
    • 表共享读锁(read lock):不会阻塞其他客户端的读,但会阻塞写。
    • 表独享写锁(write lock):写锁既会阻塞其他客户端的读,又会阻塞其他客户端的写。
  • 元数据锁(meta data lock):系统自动控制,维护表元数据的一致性,在表上有活动事务时,不可以对元数据进行写入操作。
  • 意向锁:避免行锁和表锁冲突,用意向锁使表锁不用检查每行数据是否加锁,使用意向锁来减少表锁的检查。
    • 意向共享锁(IS):与表共享锁(read)兼容,与表共享锁(read)互斥。
    • 意向排他锁(IX):与表共享锁(read)和表共享锁(read)都互斥。意向锁之间不互斥。
/*
	当开启事务时,就不能对元数据进行操作,只有事务结束了才能操作。
	一般来说,读数据是可以加表锁;而改数据是不能加表锁的。
*/
# 表锁
LOCK TABLES 表名 READ / WRITE;
UNLOCK TABLES / 客户端断开连接;

# 加锁
SELECT 语句 LOCK IN SHARE MODE;

# 查看元数据锁
SELECT * FROM performance_schema.metadata_locks;

# 查看意向锁和行锁
SELECT * FROM performance_schema.data_locks;

行级锁

行级锁,每次操作锁对应的行数据。锁定粒度最小,发生锁冲突的概率最低,并发度最高。

InooDB 的数据是基于索引组织的,行锁是通过对索引上的索引项加锁来实现的,而不是对应记录加的锁。

  • 行锁(Record Lock):锁定单个行记录的锁,防止其他事务对此进行 update 和 delete。在 RC、RR 隔离级别下都支持。
  • 间隙锁(Gap Lock):锁定索引记录间隙,确保索引记录间隙不变,防止其他事务在间隙进行 insert ,产生幻读。在 RR 隔离级别下都支持。
  • 临键锁(Next-Key-LOCK):行锁和同隙锁组合,同时锁住数据,并锁住数据前面的间隙Gap。在RR隔离级别下支持。

共享锁(S):允许一个事务去读一行,组织其他事务获取相同数据集的排他锁。

排他锁(X):允许获取排他锁的事务更新数据,阻止其他事务活得相同的数据集的共享锁和排他锁。

SQL 行锁类型 说明
INSERT 排他锁 自动加锁
UPDATE 排他锁 自动加锁
DELETE 排他锁 自动加锁
SELECT 不加锁 /
SELECT … LOCK IN SHARE MODE 共享锁 手动加锁
SELECT … FOR UPDATE 排他锁 手动加锁

1、对唯一索引进行检索,就是行锁。

2、对没有索引的条件进行检索,会升级为表锁。

3、索引上进行等值查询(唯一索引),给不存在的记录加锁时,优化为间隙锁。

4、索引上进行等值查询(普通索引),向右遍历到最后一个值都不满足查询需求时,next-key lock 退化为间隙。

5、索引上进行范围查询(唯一索引),会访问直到不满足条件的第一个值为止。

InnoDB 引擎

逻辑存储结构

TableSpace »> Segment »> Extent »> Page »> ROW

表空间(ibd文件):一个 MySQL 实例可以对应多个表空间,用于存储记录、索引等数据。

段:数据段(Leaf node segment)、索引段(Non-leaf node segment)、回滚段(Rollback segment)、InnoDB 是索引组织表,数据段就是 B+ 树的叶子节点,索引段为 B+ 树的非叶子几点。段用来管理区。

区:表空间的单元结构,每个区大小为 1M。默认情况,InnoDB 存储引擎页大小为 16k,即一个区中一共有 64个 连续的页。

页:InnoDB 存储引擎磁盘管理的最小单元,每个页的大小默认为 16KB。为了保证页的连续性,InnoDB 存储引擎每次从磁盘申请 4-5 个区。

行:InnoDB 存储引擎数据是按行进行存放的。

Trx_id:每次对某条记录进行改动时,都会把对应的事务 id 赋值给 trx_id 隐藏列。

Roll_pointer:每次对某条引记录改动时,都会把旧版本写入到 undo 日志中,然后这个隐藏列就相当于一个指针,可以通过它找到该记录修改前的信息。

架构

内存架构

Buffer Pool:缓冲池是主内存中的一个区域,缓存磁盘上经常操作的真实数据,在执行增删改查操作时,先操作缓冲池中的数据(若没有数据,则从磁盘加载并缓冲),然后以一定频率刷新到磁盘,从而减少磁盘IO,加快处理速度。

缓冲池以 Page 页为单位,底层采用链表数据结构管理 Page。根据状态,将Page分为三种类型:free page 空闲页,没使用过;clean page 被使用过,但数据未修改;dirty page 脏页,就是被使用过也被修改过,与磁盘中的数据产生了不一致。

Change Buffer:更改缓冲区(针对于二级索引页),在执行 DML 语句,如果数据没有在 Buffer Pool中,不会直接操作磁盘,而是将数据变更后的内容存在更改缓冲区 Change Buffer中,在未来数据被读取时,再将数据合并恢复到 Buffer Pool中,再将合并后的数据刷新到磁盘中。

因为二级索引是非唯一的,并且插入顺序是相对随机的,如果要对其更改每次操作都要依赖磁盘,这样会造成大量的磁盘 IO,有了 ChangeBuffer 后,我们可以在缓冲池汇总进行合并处理,减少磁盘 IO。

Adaptive Hash Index:自适应 hash 索引,用于优化对 Buffer Pool 数据的查询。InnoDB 存储引擎会监控对表上各索引页的查询,如果观察到 Hash 索引可以提升速度,则建立 Hash 索引,称之为自适应 Hash 索引。参数:adaptive_hash_index

Log Buffer:日志缓冲区,用来保护要写入到磁盘中的 log 日志数据(redo log、undo log),默认大小为 16MB,日志缓冲区的日志会定期刷新到磁盘中。如果需要更新、插入或删除多行的事务,增加日志缓冲区的大小可以节省磁盘 IO。参数:innodb_log_buffer_sizeinnodb_flush_log_at_trx_commit。1:日志在每次事务提交时写入并刷新到磁盘;0:每秒将日志写入并刷新到磁盘一次;2:日志在每次事务提交后写入,并每秒刷新到磁盘一次。

磁盘结构

System TableSapce:系统表空间是更改缓冲区的存储区域。如果表是在系统空间而不是每个表文件或通用表空间创建的,它也可能包含表和索引数据。参数:innodb_data_file_path

File-Per-Table Tablespace:每个表的文件表空间包含单个 InnoDB 表的数据和索引,并存储在文件系统上的单个数据文件。参数:innodb_file_per_table

General Tablespaces:通用表空间,需要通过 CREATE TABLESPACE 语法创建通用表空间,在创建表时,可以指定该表空间。

# 创建表空间
CREATE TABLESPACE xxx ADD 
DATAFILE 'file_name'
ENGINE = engine_name;
# 创建表
CREATE TABLE xxx TABLESPACE ts_name;

Undo TableSpaces:撤销表空间,MySQL 实例在初始化时会自动创建两个默认的 undo 表空间(初始大小16M),用于存储undo log日志。

Temporay Tablespaces:InnoDB 使用会话临时表空间和全局临时表空间。存储用户创建的临时表数据。

Doublewrite Buffer Files:双写缓冲区,InnoDB 引擎将数据页从 Buffer Pool 刷新到磁盘前,线将数据页写入双写缓冲区文件中,便于系统异常时恢复数据。

Redo Log:重写日志,是用来实现事务的持久性。该日志文件由两部分组成:重做日志缓冲(redo log buffer)以及重做日志文件(redo log),前者是在内存中,后者在磁盘中。当事务提交后会把所有修改信息都会存到日志中,用于在刷新脏页到磁盘时,发生错误时,进行数据恢复使用。循环写入重做日志文件,涉及两个文件:ib_logfile0、ib_logfile1。

后台线程

Master Thread:核心后台线程,负责调度其他线程,还负责将缓冲池中的数据异步刷新到磁盘中,保持数据的一致性,还包括脏页的刷新、合并插入缓冲、undo页的回收。

IO Thread:AIO负责IO请求的回调。Read thread (4)、Write thread(4)、Log thread(1)、Insert buffer thread(1)。

SHOW ENGINE innodb status;

Purge Thread:主要用于回收事务已经提交了的undo log,在事务提交后,undo log可能不用了,就用它来回收。

Page Cleaner Thread:协助 Master Thread 刷新脏页到磁盘的线程,它可以减轻 Master Thread 的工作压力,减少阻塞。

事务原理

事务就是一组操作的集合,将所有操作视作是一个整体向系统提交,若是其中某个操作失败了,就会执行回滚操作。

事务具有 ACID 的特性:

原子性,就是事务不可分割,要么全部执行成功,要么全部执行失败。

一致性,事务完成时,必须让所有数据保持一致性。

隔离性,事务不受外部并发操作影响。

持久性,事务一旦提交或回滚,数据的改变就是永久的。

原子性 - undo log

持久性 - redo log

一致性 - undo log + redo log

隔离性 - 锁 + MVCC

redo:重做日志,记录的是事务提交时数据页的物理修改,用来实现事务的持久性。

该日志文件由两部分组成,重做日志缓冲(redo log buffer)、重做日志文件(redo log file)。前者是在内存中,后者在磁盘中,当事务提交后会把所有修改信息都存到该日志文件中,用于在刷新脏页到磁盘,发生错误时,进行数据恢复使用。

undo:回滚日志,用于记录数据被修改前的信息,包括提交回滚和MVCC(多版本并发控制)。undo log 和 redo log 记录物理日志不一样,他是逻辑日志。也就是当用户执行一次 delete 记录时,undo log 会记录一条 insert 记录,以方便回滚。

undo log 销毁:undo log 在事务执行时产生,事务提交时,不会立刻删除 undo log,因为这些日志还用于 MVCC。

undo log 存储:undo log 采用段方式进行管理和记录,存放在前面介绍 rollback segment 回滚段中,内部包含 1024 个 undo log segment。

MVCC

当前读:读取记录的最新版本,读取时保证其他并发事务不能修改当前记录,会对读取记录进行加锁。

快照读:简单的 select(不加锁)就是快照读,快照读,读取的是记录数据的可见版本,有可能是历史数据,不加锁,是非阻塞读。

  • Read Committed:每次 select 都生成一个快照读。
  • Repetabel Read:开启事务后第一个 select 语句是快照读。
  • Serializable:快照读退化为当前读。

MVCC:Multi-Version Concurrency Control,多版本并发控制。也就是维护一个数据的多个版本,使得读写操作没有冲突,快照读为 MySQL 实现 MVCC 提供了一个非阻塞读功能。MVCC 的具体实现,依赖数据库记录中的三个隐式字段、undo log日志、readView。

隐藏字段

  • DB_TRX_ID 最近修改事务ID,记录插入这条记录或最后一次修改为该记录的事务ID。
  • DB_ROLL_PTR 回滚指针,指向这条记录的上个版本,用于配合 undo log,指向上个版本。
  • DB_ROW_ID 隐藏主键,如果表结构没有指定主键,将会生成该隐藏字段。

undo log:回滚日志,在 insert、update、delete 时产生便于数据回滚的日志。当 insert 的时候,产生的 undo log 日志只在回滚时需要,在事务提交后,可被立即删除。而 update、delete 的时候,产生的 undo log 日志不仅在回滚时需要,在快照读时也需要,不会立即被删除。

不同事务或相同事务对同一条记录修改,会导致该记录的 undo log 生成一条记录版本链表,链表头部是最新的旧记录,链表尾部是最早的旧记录。

readview:读视图,是快照读SQL执行时MVCC提取数据的依据,记录并维护系统当前活跃的事务(未提交的)id。

  • m_ids 当前活跃的事务ID集合。
  • min_trx_id 最小活跃事务ID。
  • max_trx_id 预分配事务ID,当前最大事务ID+1。
  • creator_trx_id ReadView 创建者事务ID。

版本链数据访问规则:

READ COMMITED:在事务中每一次执行快照读时生成ReadView。

REPEATABLE READ:仅在事务第一次执行汇快照读时生成ReadView,后续复用该 ReadView。

  • TRX_ID == CREATOR_TRX_ID(✓)- 数据是当前事务更改的。
  • TRX_ID < MIN_TRX_ID(✓)- 说明数据已经提交了。
  • TRX_ID > MAX_TRX_ID(×)- 说明事务是在 ReadView 生产后才开启。
  • MIN_TRX_ID <= TRX_ID <= MAX_TRX_ID(不在集合中可访问)- 说明数据已经提交。

MySQL 管理

系统数据库

MySQL 自带四个数据库:

  • mysql 存储 MySQL 服务器正常运行所需要的各种信息 (时区、主从、用户、权限等)。
  • information_schema 提供了访问数据库元数据的各种表和视图,包含数据库、表、字段类型及访问权限等。
  • performance_schema 为 MySQL 服务器运行时状态提供了一个底层监控功能,主要用于收集数据库服务器性能参数。
  • sys 包含了一系列方便 DBA 和开发人员利用 performance_schema 性能数据库进行性能调优和诊断的视图。

常用工具

MySQL 客户端

语法:mysql [options] [database]

选项:-u, –user=name

​ -p,–password[=name]

​ -h,–host=name

​ -p,–port=port

​ -e,–excute=name

mysql -uroot -p123456 db01 -e "select * from stu*"

mysqladmin:执行管理操作的客户端程序,用它来检查服务器的配置和当前状态、创建并删除数据库等。

mysqladmin -uroot -p123456 drop 'test01'

mysqladmin -uroot -p123456 version

mysqlbinlog:由服务器生成对二进制日志文件以二进制格式保存,如果想要检查这些文本格式,就会用到该日志管理工具

语法:mysqlbinlog [options] log-files1 log-file2

选项:-d,–database=name 指定数据库名称,只列出指定的数据库相关操作

​ -o,–offset=# 忽略日志中的前n行命令

​ -r,–result-file=name 将输出的文本格式日志输出到指定文件

​ -s,–short-form 显示简单格式,省略掉一些信息

​ -v 将行事件(数据变更)重构为 SQL 语句

​ -vv 将行事件(数据变更)重构为 SQL 语句,并输出注释信息

​ –start-datetime=date1 –stop-datetime=date2 指定日期间隔内的所有日志

​ –start-position=pos1 –stop-position=pos2 指定位置间隔内的所有日志

mysqlshow:mysqlshow 客户端对象查找工具,用来很快地查找存在哪些数据库、数据库中的表、表中的列或者索引。

语法:mysqlshow [options] [db_name [table_name [col_name]]]

选项:–count 显示数据库及表的统计信息(数据库,表均可以不指定)

​ -i 显示指定数据库或者执行表的状态信息

# 查询每个数据库的表的数量以及表中记录的数量
mysqlshow -uroot -p1234 --count

# 查询 test 库中每个表的字段数,以及行数
mysqlshow -uroot -p1234 test --count

# 查询 test 库中 book 表的详细情况
mysqlshow -uroot -p1234 test book --count

mysqldump:用来备份数据库或在不同数据库之间进行数据迁移。备份内容包含创建表,以及插入表的 SQL 语句。

语法:mysqldump [options] db_name [tables]

​ mysqldump [options] –database/-B db1 [db2 db3 …]

​ mysqldump [options] –all-database/-A

连接选项:-u,–user=name

​ -p,–password[=name]

​ -h,–host=name

​ -p,–port=#

输出选项:–add-drop-database 在每个数据库创建语句前加上 drop database

​ –add-drop-table 在表创建语句前加上 drop table

​ -n,–no-create-db 不包含数据库的创建

​ -t,–no-create-info 不包含数据表的创建

​ -d,–no-data 不包含数据

​ -T,–tab-name 自动生成两个文件,一个.sql文件,创建表结构;一个.txt文件,数据文件。(show variables liek '%secure_file_priv;'

mysqlimport/soucre:数据导入工具,用来导入 mysqldump -T 导出的文本文件。

语法:mysqlimport [options] db_name textfile1 [textfile2 …]

​ source /root/xxxx.sql

source /root/test.sql
mysqlimport -uroot -p1234 test /tmp/city.txt

运维篇

日志

错误日志

错误日志记录了 mysqld 启动和停止时,以及在运行过程中发生任何错误时的相关信息。该日志默认开启,存放目录为 /var/log/,默认日志文件名mysqld.log

# 查看日志文件地址
SHOW VARIABLES LIKE '%log_error%';

二进制日志

二进制日志(BINLOG)记录了所有 DDL 和 DML 语句,但不包含 DQL 语句。

作用:灾难时的数据恢复;MySQL 主从复制

# 查看日志文件地址
SHOW VARIABLES LIKE '%log_bin';

MySQL 提供了多种格式来记录二进制日志:

  • STATEMENT - 基于 SQL 语句的日志记录,记录的是 SQL 语句,对数据进行修改的 SQL 都会记录在日志文件中。
  • ROW - 基于行的日志记录,记录的是每一行的数据。
  • MIXED - 混合了 STATEMENT 和 ROW 两种格式,默认采用 STATEMENT,在某些特殊情况下会自动切换为 ROW 进行记录。
# 查看格式
SHOW VARIABLES LIKE '%binlog_format%';

日志删除方式:

  • reset master 删除全部 binlog 日志,删除后,日志编号,将从 binlog.000001 重新开始
  • purge master logs to ‘binlog.xxxxxx’ 删除 xxxxxx 编号之前的所有日志
  • purge master logs before ‘yyyy-mm-dd hh24:mi:ss’ 删除日志为 ‘yyyy-mm-dd hh24:mi:ss’ 之前产生的所有日志

可以在 MySQL 配置文件 /etc/my.cnf 中配置二进制日志:

SHOW VARIABLES LIKE '%binlog_expire_logs_seconds%';

查询日志

查询日志记录了客户端所有操作语句,而二进制日志不包含数据的 SQL 语句。默认情况下,查询日志是未开启的。如果需要开启查询日志,可以设置配置:

# 查询 general_log 是否开启
SHOW VARIABLES LIKE '%general%';

# 在配置中开启查询日志
general_log = 1

# 设置文件名默认为 host_name.log
general_log_file = mysql_query.log

慢查询日志

慢查询日志记录了所有执行时间超过参数 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 和更改此行为 log_queries_not_using_indexes。

# 记录执行较慢的管理语句
log_slow_admin_statements = 1

# 记录执行较慢的未使用索引的语句
log_queries_not_using_indexs = 1

主从复制

概述

主从复制就是将数据库的 DDL 和 DML 操作通过二进制日志传到从库服务器中,然后在从库上对这些日志重新执行(也叫重做),从而使得从库和主库的数据保持同步。

MySQL 支持一台主库同时向多台从库进行复制,从库同时也可以作为其他从服务器的主库,实现链状复制。

  • 1.主库出现问题,可以快速切换到从库提供服务。
  • 2.实现读写分离,降低主库的访问压力。
  • 3.可以在从库中执行备份,以避免备份期间影响主库服务。

原理

1、Master 主库提交事务时,会将数据变更记录在二进制日志文件 Binlog 中。

2、从库读取主库的二进制文件 Binlog ,写入到从库的中继日志 Replay Log。

3、slave 重做中继日志中的事件,将改变反映它自己的数据。

搭建

服务器准备:

192.168.200.200(master)

192.168.200.201(slave)

# 开放指定 3306 端口号
firewall-cmd --zone=public --add-port=3306/tcp -permanent
firewall-cmd -reload

# 关闭服务器防火墙
systemctl stop firewalld
systemctl diable firewalld

主库配置:

1、修改配置文件 /etc/my.cnf

# mysql 服务器 ID,确保集群中唯一
server-id = 1

# 是否只读,1 代表只读,0代表读写
read-only = 0

# 忽略的数据,指不需要同步的数据库
# binlog-ignore-db = mysql

# 指定同步的数据库
# binlog-do-db = db01

2、重启 MySQL 服务器

systemctl restart mysqld

3、登录 MySQL,创建远程连接账号,并授予主从复制权限

# 创建用户
CREATE USER 'master'@'%' IDENTIFIED WITH mysql_native_password BY 'root@123456';

# 分配主从复制权限
GRANT REPLICATION SLAVE ON *.* TO 'master'@'%'

4、查看二进制日志坐标

/*
	file 从哪个日志文件开始推送
	postions 从哪个位置开始推送
	binlog_ignore_db 指定不需要同步的数据库
*/
SHOW MASTER STATUS;

从库配置:

1、修改配置文件 /etc/my.cnf

# 设置服务器 ID
server-id = 2

# 是否只读
read-only = 1

2、重启 MySQL

systemctl mysql restart

3、登录 MySQL,设置主库配置

CHANGE REPLICATION SOURCE TO SOURCE_HOST='xxx.xxx',SOURCE_USER='xxx',SOURCE_PASSWORD='xxx',SOURCE_LOG_FILE='xxx',SOURCE_LOG_POS=xxx;

4、开启同步操作

START REPLICA;

5、查看主从同步状态

SHOW REPLICA STATUS;

Built with Hugo
Theme Stack designed by Jimmy
© Licensed Under CC BY-NC-SA 4.0