基础篇
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 字段列表 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 条件 ...;
SELECT 字段列表 FROM 表A 别名A LEFT[OR RIGHT] [OUTER] JOIN 表B 别名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_size
、innodb_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;