目录
3.1.我们采用 开启慢查询日志 或者 show processlist 进行对SQL语句的搜寻
引言
在当今数字化时代,数据是企业和组织的核心资产之一。如何高效地存储、管理和查询数据成为了至关重要的问题。关系型数据库作为一种广泛应用的数据管理解决方案,凭借其结构化的数据存储方式、强大的查询功能和数据完整性保证,在众多领域发挥着关键作用。MySQL 作为开源关系型数据库的代表,以其高性能、可靠性和易用性,深受开发者和企业的喜爱。本文将深入探讨 MySQL 的基本概念、安装配置、常用操作以及优化技巧,帮助读者全面了解和掌握这一强大的数据库工具
一.SQL的基本操作
1.数据库是什么? 什么是SQL?
按照数据结构来组织、存储和管理数据的仓库 ;是一个长期存储在计算机内的、有组织的、可共享的、统一管理的大量数据的集合;
1.1.OLTP
OLTP ( On-Line transaction processing )翻译为联机事务处理;主要对数据库增删改查;OLTP 主要用来记录某类业务事件的发生;数据会以增删改的方式在数据库中进行数据的更新处理操作,要求实时性高、稳定性强、确保数据及时更新成功;
1.2.OLAP
OLAP ( On-Line Analytical Processing )翻译为联机分析处理;主要对数据库查询;当数据积累到一定的程度,我们需要对过去发生的事情做一个总结分析时,就需要把过去一段时间内产生的数据拿出来进行统计分析,从中获取我们想要的信息,为公司做决策提供支持,这时候就是在做 OLAP 了;
1.3.SQL
结构化查询语言 ( Structured Query Language ) 简称 SQL ,是一种 特殊目的的编程语言,是一种数据库查询和程序设计语言,用 于存取数据以及查询、更新和管理关系数据库系统 。SQL 是关 系数据库系统的标准语言。关系型数据库包括: MySQL , SQL Server , Oracle , Sybase , postgreSQL 以及 MS Acces 等;SQL 命令包括: DQL 、 DML 、 DDL 、 DCL 以及 TCL ;
1.4.DQL
Data Query Language - 数据查询语言;select :从一个或者多个表中检索特定的记录;
1.5.DML
Data Manipulate Language - 数据操作语言;insert :插入记录;update :更新记录;delete :删除记录;
1.6.DDL
Data Define Languge - 数据定义语言;create :创建一个新的表、表的视图、或者在数据库中的对 象;alter :修改现有的数据库对象,例如修改表的属性或者字 段;drop :删除表、数据库对象或者视图;
1.7.DCL
Data Control Language - 数据控制语言;grant :授予用户权限;revoke :收回用户权限;
1.8.TCL
Transaction Control Language - 事务控制语言;commit :事务提交;rollback :事务回滚;
1.9.数据库术语
数据库:数据库是一些关联表的集合;数据表:表是数据的矩阵;列:一列包含相同类型的数据;行:或者称为记录是一组相关的数据;主键: 主键是唯一的;一个数据表只能包含一个主键 ;外键:外键用来关联两个表,来保证参照完整性; MyISAM 存储引擎本身并不支持外键,只起到注释作用;而 innoDB 完整支持 外键;复合键:或称组合键;将多个列作为一个索引键;索引:用于快速访问数据表的数据;索引是对表中的一列或者多列的值进行排序的一种结构;
2.MySQL体系结构
MySQL 由以下几部分组成:连接池组件管理服务和工具组件、 SQL 接口组件、查询分析 器组件、优化器组件、缓冲组件、插件式存储引擎、物理文 件。
2.1.连接者
不同语言的代码程序和 MySQL 的交互( SQL 交互);
2.2.MySQL 内部连接池
管理缓冲用户连接、用户名、密码、权限校验、线程处理等需 要缓存的需求;网络处理流程: 主线程接收连接,接收连接交 由连接池处理;主要处理方式: IO多路复用 select + 阻塞的 io ;需要理解: MySQL 命令处理是多线程并发处理的;
主线程负责接收客户端连接,然后为每个客户端 fd 分配一个连接线程,负责处理该客户端的 sql 命令处理;
2.3.管理服务和工具组件
系统管理和控制工具,例如备份恢复、 MySQL 复制、集群等;
2.4.SQL接口
将 SQL 语句解析生成相应对象; DML , DDL ,存储过程,视 图,触发器等;
2.5.查询解析器
将 SQL 对象交由解析器验证和解析,并生成语法树;
2.6.查询优化器
SQL 语句执行前使用查询优化器进行优化;
2.7.缓冲组件
是一块内存区域,用来弥补磁盘速度较慢对数据库性能的影响;在数据库进行读取页操作,首先将从磁盘读到的页存放在缓冲池中,下一次再读相同的页时,首先判断该页是否在缓冲池中,若在缓冲池命中,直接读取;否则读取磁盘中的页,说明该页被 LRU 淘汰了;缓冲池中 LRU 采用最近最少使用算法来进行管理;缓冲池缓存的数据类型有:索引页、数据页、以及与存储引擎缓存相关的数据(比如 innoDB 引擎: undo 页、插入缓冲、自适应 hash 索引、 innoDB 相关锁信息、数据字典信息等);
2.8.一条select查询语句的执行流程
①客户端向mysql服务器发起连接通过 ,mysql服务器接收连接并且分配到连接池中对应的连接线程中,并且校验用户的信息。
②查询缓存,如果缓存中有对应的数据则直接返回,在mysql8.0版本中该功能已被删除,毕竟有又快性能又高的redis
③分析器,对输入的
SELECT
语句进行词法和语法分析,将语句拆解成一个个单词(词法分析 ),并根据 MySQL 的语法规则检查语句是否合法,最终生成语法树,以此理解语句的结构和意图
④优化器,对于涉及多表连接的查询,确定表的连接顺序、选择合适的索引等,制定出执行成本最小的执行计划,以提升查询执行效率 。
⑤执行器,根据执行任务,从存储引擎中获取数据,并返回给客户端
3.数据库设计三范式
为了 建立冗余较小、结构合理的数据库 ,设计数据库时必须遵循一定的规则。在关系型数据库中这种规则就称为范式。范式是符合某一种设计要求的总结。要想设计一个结构合理的关系型数据库,必须满足一定的范式。
3.1.范式一
确保每列保持原子性;数据库表中的所有字段都是不可分解的原子值;例如:某表中有一个地址字段,如果经常需要访问地址字段中的城市属性,则需要将该字段拆分为多个字段,省份、城市、详细地址等;
3.2.范式二
确保表中的每列都和主键相关,而不能只与主键的某一部分相关(组合索引);
3.3.范式三
确保每列都和主键直接相关,而不是间接相关;减少数据冗余;
3.4.反范式
范式可以避免数据冗余,减少数据库的空间,减小维护数据完整性的麻烦;但是采用数据库范式化设计,可能导致数据库业务涉及的表变多,并且造成更多的联表查询,将导致整个系统的性能降低;因此基于性能考虑,可能需要进行反范式设计;
4.CRUD
4.1.执行过程
4.2创建数据库
CREATE DATABASE `数据库名` DEFAULT CHARACTER SET
utf8;
4.3删除数据库
CREATE DATABASE `数据库名` DEFAULT CHARACTER SET
utf8;
4.4删除数据库
DROP DATABASE `数据库名`;
4.5选择数据库
USE `数据库名`;
4.6创建表
CREATE TABLE `table_name` (column_name
column_type);
CREATE TABLE IF NOT EXISTS `0voice_tbl` (
`id` INT UNSIGNED AUTO_INCREMENT COMMENT '编
号',
`course` VARCHAR(100) NOT NULL COMMENT '课程',
`teacher` VARCHAR(40) NOT NULL COMMENT '讲师',
`price` DECIMAL(8,2) NOT NULL COMMENT '价格',
PRIMARY KEY ( `id` )
)ENGINE=innoDB DEFAULT CHARSET=utf8 COMMENT = '课
程表';
4.7删除表
DROP TABLE `table_name`;
4.8清空数据表
TRUNCATE TABLE `table_name`; -- 截断表 以页为单位(至
少有两行数据),有自增索引的话,从初始值开始累加
DELETE TABLE `table_name`; -- 逐行删除,有自增索引的
话,从之前值继续累加
4.9增
INSERT INTO `table_name`(`field1`, `field2`, ...,
`fieldn`) VALUES (value1, value2, ..., valuen);
INSERT INTO `0voice_tbl` (`course`, `teacher`,
`price`) VALUES ('C/C++Linux服务器开发/高级架构师',
'Mark', 7580.0);
4.10删
DELETE FROM `table_name` [WHERE Clause];
DELETE FROM `0voice_tbl` WHERE id = 3;
4.11改
UPDATE table_name SET field1=new_value1,
field2=new_value2 [, fieldn=new_valuen]
UPDATE `0voice_tbl` SET `teacher` = 'Mark' WHERE
id = 2;
-- 累加
UPDATE `0Voice_tbl` set `age` = `age` + 1 WHERE id
= 2;
4.12查
SELECT field1, field2,...fieldN FROM table_name
[WHERE Clause]
4.13 大约束
not null 非空约束:限定字段必须有值,不允许为空值,确保数据完整性,避免关键信息缺失 。
auto_increment 自增约束:用于数值型字段,值按顺序自动递增,常作主键生成唯一标识,无需手动赋值 。
unique 唯一约束:保证字段值在表内唯一,可出现一个空值(若字段允许空值 ),防止重复数据录入 。
primary 主键约束:唯一标识表中记录,兼具非空和唯一性,一个表仅有一个主键,是数据定位和关联基础 。
foreign 外键约束:构建表间关联,保证参照完整性,外键值需来自关联表主键或唯一键,维护数据逻辑关系 。
4.14删除数据注意事项
drop
(DDL)快,删整表(结构、数据、索引等 ),不能回滚,释空间 。
truncate
(DDL)较快,删表数据,留结构(
auto_increment
置 1 ),不能回滚,以页删,释空间 。
delete
(DML)慢,可部分或全部删数据,留结构,能回滚,逐行删,可条件删 。
4.15去重
group by column
按指定列分组,每组留一条记录实现去重,常配合聚合函数,用于分组统计场景 。
select distinct column
直接扫描指定列,去除重复值来实现去重,单纯获取列的唯一值
5.高级查询
5.1基础查询
-- 全部查询
SELECT * FROM student;
-- 只查询部分字段
SELECT `sname`, `class_id` FROM student;
-- 别名 列明 不要用关键字
SELECT `sname` AS '姓名' , `class_id` AS '班级ID'
FROM student;
-- 把查询出来的结果的重复记录去掉
SELECT distinct `class_id` FROM student;
5.2条件查询
-- 查询姓名为 邓洋洋 的学生信息
SELECT * FROM `student` WHERE `name` = '邓洋洋';
-- 查询性别为 男,并且班级为 2 的学生信息
SELECT * FROM `student` WHERE `gender`="男" AND
`class_id`=2;
5.3范围查询
-- 查询班级id 1 到 3 的学生的信息
SELECT * FROM `student` WHERE `class_id` BETWEEN 1
AND 3;
5.4判空查询
# is null 判断造成索引失效
# 索引 B+ 树
SELECT * FROM `student` WHERE `class_id` IS NOT
NULL; #判断不为空
SELECT * FROM `student` WHERE `class_id` IS NULL;
#判断为空
SELECT * FROM `student` WHERE `gender` <> '';
#判断不为空字符串
SELECT * FROM `student` WHERE `gender` = '';
#判断为空字符串
5.5模糊查询
-- 使用 like关键字,"%"代表任意数量的字符,”_”代表占位符
-- 查询名字为 m 开头的学生的信息
SELECT * FROM `teacher` WHERE `tname` LIKE '谢%';
-- 查询姓名里第二个字为 小 的学生的信息
SELECT * FROM `teacher` WHERE `tname` LIKE '_小%';
5.6分页查询
-- 分页查询主要用于查看第N条 到 第M条的信息,通常和排序查询
一起使用
-- 使用limit关键字,第一个参数表示从条记录开始显示,第二个
参数表示要显示的数目。表中默认第一条记录的参数为0。
-- 查询第二条到第三条内容
SELECT * FROM `student` LIMIT 1,2;
5.7查询后排序
-- 关键字:order by field, asc:升序, desc:降序
SELECT * FROM `score` ORDER BY `num` ASC;
-- 按照多个字段排序
SELECT * FROM `score` ORDER BY `course_id` DESC,
`num` DESC;
5.8聚合查询
SELECT sum(`num`) FROM `score`;
SELECT avg(`num`) FROM `score`;
SELECT max(`num`) FROM `score`;
SELECT min(`num`) FROM `score`;
SELECT count(`num`) FROM `score`;
5.9分组查询
-- 分组加group_concat
SELECT `gender`, group_concat(`age`) as ages FROM
`student` GROUP BY `gender`;
-- 可以把查询出来的结果根据某个条件来分组显示
SELECT `gender` FROM `student` GROUP BY `gender`;
-- 分组加聚合
SELECT `gender`, count(*) as num FROM `student`
GROUP BY `gender`;
-- 分组加条件
SELECT `gender`, count(*) as num FROM `student`
GROUP BY `gender` HAVING num > 6;
5.10分组聚合
分组的作用是去重
聚合的作用是合并重复的
举例子
-- 创建表
CREATE TABLE students (
student_id INT PRIMARY KEY AUTO_INCREMENT,
student_name VARCHAR(50),
gender ENUM('男', '女')
);
-- 插入数据
INSERT INTO students (student_name, gender)
VALUES
('张三', '男'),
('李四', '男'),
('王五', '女'),
('赵六', '女'),
('孙七', '男');
SELECT gender, COUNT(student_id) AS student_count
FROM students
GROUP BY gender;
查询结果
6.联表查询
6.1 INNER JOIN
只取两张表有对应关系的记录
SELECT
cid
FROM
`course`
INNER JOIN `teacher` ON course.teacher_id =
teacher.tid;
6.2 LEFT JOIN
在内连接的基础上保留左表没有对应关系的记录
SELECT
course.cid
FROM
`course`
LEFT JOIN `teacher` ON course.teacher_id =
teacher.tid;
6.3 RIGHT JOIN
在内连接的基础上保留右表没有对应关系的记录
SELECT
course.cid
FROM
`course`
RIGHT JOIN `teacher` ON course.teacher_id =
teacher.tid;
7 子查询/合并查询
7.1.单行子查询
select * from course where teacher_id = (select
tid from teacher where tname = '谢小二老师')
7.2多行子查询
多行子查询即返回多行记录的子查询IN 关键字 :运算符可以检测结果集中是否存在某个特定的值,如果检测成功就执行外部的查询。EXISTS 关键字 :内层查询语句不返回查询的记录。而是返回一个真假值。如果内层查询语句查询到满足条件的记录,就返回一个真值( true ),否则,将返回一个假值( false )。当返回的值为 true 时,外层查询语句将进行查询;当返回的为false 时,外层查询语句不进行查询或者查询不出任何记录。ALL 关键字 :表示满足所有条件。使用 ALL 关键字时,只有满足内层查询语句返回的所有结果,才可以执行外层查询语句。ANY 关键字 :允许创建一个表达式,对子查询的返回值列表,进行比较,只要满足内层子查询中的,任意一个比较条件,就返回一个结果作为外层查询条件。在 FROM 子句中使用子查询 :子查询出现在 from 子句中,这种情况下将子查询当做一个临时表使用。
select * from student where class_id in (select
cid from course where teacher_id = 2);
select * from student where exists(select cid
from course where cid = 5);
SELECT
student_id,
sname
FROM
(SELECT * FROM score WHERE course_id = 1 OR
course_id = 2) AS A
LEFT JOIN student ON A.student_id =
student.sid;
8.正则表达式
SELECT * FROM `teacher` WHERE `tname` REGEXP '^
谢';
9.视图
9.1定义
视图( view )是一种虚拟存在的表,是一个逻辑表,本身并不包含数据。其内容由查询定义。基表:用来创建视图的表叫做基表;通过视图,可以展现基表的部分数据;视图数据来自定义视图的查询中使用的表,使用视图动态生成;
9.2优点
简单:使用视图的用户完全不需要关心后面对应的表的结构、关联条件和筛选条件,对用户来说已经是过滤好的复合条件的结果集。安全:使用视图的用户只能访问他们被允许查询的结果集,对表的权限管理并不能限制到某个行某个列,但是通过视图就可以简单的实现。数据独立:一旦视图的结构确定了,可以屏蔽表结构变化对用户的影响,源表增加列对视图没有影响;源表修改列名,则可以通过修改视图来解决,不会造成对访问者的影响。
9.3语法
CREATE VIEW <视图名> AS <SELECT语句>
CREATE VIEW view_test1 AS SELECT
A.student_id
FROM
(
SELECT
student_id,
num
FROM
score
WHERE
course_id = 1
) AS A -- 12
LEFT JOIN (
SELECT
student_id,
num
FROM
score
WHERE
course_id = 2
) AS B -- 11
ON A.student_id = B.student_id
WHERE
A.num >
IF (isnull(B.num), 0, B.num);
可复用,减少重复语句书写;类似程序中函数的作用;
重构利器
逻辑更清晰,屏蔽查询细节,关注数据返回;
权限控制,某些表对用户屏蔽,但是可以给该用户通过视图来对该表操作;
10.综合案例演示
-- 创建部门表
CREATE TABLE departments (
department_id INT PRIMARY KEY AUTO_INCREMENT,
department_name VARCHAR(50) NOT NULL
);
-- 创建员工表
CREATE TABLE employees (
employee_id INT PRIMARY KEY AUTO_INCREMENT,
employee_name VARCHAR(50) NOT NULL,
department_id INT,
salary DECIMAL(10, 2),
hire_date DATE,
FOREIGN KEY (department_id) REFERENCES departments(department_id)
);
-- 创建订单表
CREATE TABLE orders (
order_id INT PRIMARY KEY AUTO_INCREMENT,
employee_id INT,
order_date DATE,
order_amount DECIMAL(10, 2),
FOREIGN KEY (employee_id) REFERENCES employees(employee_id)
);
-- 向部门表插入数据
INSERT INTO departments (department_name)
VALUES
('销售部'),
('研发部'),
('财务部');
-- 向员工表插入数据
INSERT INTO employees (employee_name, department_id, salary, hire_date)
VALUES
('张三', 1, 5000.00, '2022-01-01'),
('李四', 1, 5500.00, '2022-02-15'),
('王五', 2, 7000.00, '2021-10-10'),
('赵六', 2, 7500.00, '2021-11-20'),
('孙七', 3, 6000.00, '2022-05-01');
-- 向订单表插入数据
INSERT INTO orders (employee_id, order_date, order_amount)
VALUES
(1, '2023-03-01', 1000.00),
(1, '2023-04-10', 1500.00),
(2, '2023-05-20', 2000.00),
(3, '2023-06-15', 800.00),
(4, '2023-07-05', 1200.00);
-- 1. 简单查询
-- 查询所有员工的姓名和工资
SELECT employee_name, salary FROM employees;
-- 2. 条件查询
-- 查询工资大于 6000 的员工信息
SELECT * FROM employees WHERE salary > 6000;
-- 查询 2022 年之后入职的销售部员工姓名
SELECT e.employee_name
FROM employees e
JOIN departments d ON e.department_id = d.department_id
WHERE d.department_name = '销售部' AND e.hire_date > '2022-12-31';
-- 3. 联表查询
-- 内连接:查询每个员工的订单信息(包括员工姓名、订单日期和订单金额)
SELECT e.employee_name, o.order_date, o.order_amount
FROM employees e
JOIN orders o ON e.employee_id = o.employee_id;
-- 左连接:查询所有员工及其订单信息,没有订单的员工也显示
SELECT e.employee_name, o.order_date, o.order_amount
FROM employees e
LEFT JOIN orders o ON e.employee_id = o.employee_id;
-- 右连接:查询所有订单及其对应的员工信息,没有员工的订单也显示
SELECT e.employee_name, o.order_date, o.order_amount
FROM employees e
RIGHT JOIN orders o ON e.employee_id = o.employee_id;
-- 全连接(MySQL 没有直接的全连接关键字,通过左连接和右连接并集实现)
SELECT e.employee_name, o.order_date, o.order_amount
FROM employees e
LEFT JOIN orders o ON e.employee_id = o.employee_id
UNION
SELECT e.employee_name, o.order_date, o.order_amount
FROM employees e
RIGHT JOIN orders o ON e.employee_id = o.employee_id;
-- 4. 子查询
-- 查询工资高于平均工资的员工信息
SELECT * FROM employees
WHERE salary > (SELECT AVG(salary) FROM employees);
-- 查询在 2023 年有订单的员工所属部门名称
SELECT d.department_name
FROM departments d
JOIN employees e ON d.department_id = e.department_id
JOIN orders o ON e.employee_id = o.employee_id
WHERE o.order_date BETWEEN '2023-01-01' AND '2023-12-31';
-- 5. 分组查询
-- 按部门统计员工数量
SELECT d.department_name, COUNT(e.employee_id) AS employee_count
FROM departments d
LEFT JOIN employees e ON d.department_id = e.department_id
GROUP BY d.department_name;
-- 按员工统计订单总金额,并筛选出订单总金额大于 1500 的员工
SELECT e.employee_name, SUM(o.order_amount) AS total_amount
FROM employees e
JOIN orders o ON e.employee_id = o.employee_id
GROUP BY e.employee_name
HAVING SUM(o.order_amount) > 1500;
-- 6. 排序查询
-- 按工资从高到低查询员工信息
SELECT * FROM employees ORDER BY salary DESC;
-- 按入职日期升序,工资降序查询员工信息
SELECT * FROM employees ORDER BY hire_date ASC, salary DESC;
-- 7. 聚合函数查询
-- 查询所有员工的平均工资
SELECT AVG(salary) AS average_salary FROM employees;
-- 查询员工的最高工资和最低工资
SELECT MAX(salary) AS max_salary, MIN(salary) AS min_salary FROM employees;
department表
employees表
order表
-- 1. 简单查询 -- 查询所有员工的姓名和工资 SELECT employee_name, salary FROM employees;
-- 2. 条件查询 -- 查询工资大于 6000 的员工信息 SELECT * FROM employees WHERE salary > 6000;
-- 查询 2022 年之后入职的销售部员工姓名 SELECT e.employee_name FROM employees e JOIN departments d ON e.department_id = d.department_id WHERE d.department_name = '销售部' AND e.hire_date > '2022-12-31';
-- 3. 联表查询 -- 内连接:查询每个员工的订单信息(包括员工姓名、订单日期和订单金额) SELECT e.employee_name, o.order_date, o.order_amount FROM employees e JOIN orders o ON e.employee_id = o.employee_id;
-- 左连接:查询所有员工及其订单信息,没有订单的员工也显示 SELECT e.employee_name, o.order_date, o.order_amount FROM employees e LEFT JOIN orders o ON e.employee_id = o.employee_id;
-- 右连接:查询所有订单及其对应的员工信息,没有员工的订单也显示 SELECT e.employee_name, o.order_date, o.order_amount FROM employees e RIGHT JOIN orders o ON e.employee_id = o.employee_id;
-- 全连接(MySQL 没有直接的全连接关键字,通过左连接和右连接并集实现) SELECT e.employee_name, o.order_date, o.order_amount FROM employees e LEFT JOIN orders o ON e.employee_id = o.employee_id UNION SELECT e.employee_name, o.order_date, o.order_amount FROM employees e RIGHT JOIN orders o ON e.employee_id = o.employee_id;
-- 4. 子查询 -- 查询工资高于平均工资的员工信息 SELECT * FROM employees WHERE salary > (SELECT AVG(salary) FROM employees);
-- 查询在 2023 年有订单的员工所属部门名称 SELECT d.department_name FROM departments d JOIN employees e ON d.department_id = e.department_id JOIN orders o ON e.employee_id = o.employee_id WHERE o.order_date BETWEEN '2023-01-01' AND '2023-12-31';
-- 5. 分组查询 -- 按部门统计员工数量 SELECT d.department_name, COUNT(e.employee_id) AS employee_count FROM departments d LEFT JOIN employees e ON d.department_id = e.department_id GROUP BY d.department_name;
-- 按员工统计订单总金额,并筛选出订单总金额大于 1500 的员工 SELECT e.employee_name, SUM(o.order_amount) AS total_amount FROM employees e JOIN orders o ON e.employee_id = o.employee_id GROUP BY e.employee_name HAVING SUM(o.order_amount) > 1500;
-- 6. 排序查询 -- 按工资从高到低查询员工信息 SELECT * FROM employees ORDER BY salary DESC;
-- 按入职日期升序,工资降序查询员工信息 SELECT * FROM employees ORDER BY hire_date ASC, salary DESC;
-- 7. 聚合函数查询 -- 查询所有员工的平均工资 SELECT AVG(salary) AS average_salary FROM employees;
-- 查询员工的最高工资和最低工资 SELECT MAX(salary) AS max_salary, MIN(salary) AS min_salary FROM employees;
二.MySQL索引原理以及SQL优化
1.索引是什么?分为什么?
MySQL 索引是一种数据结构,用于提高数据库中数据的查询效率,就像书的目录一样,能让数据库快速定位到所需数据的位置。
1.1索引的分类
索引分类:主键索引、唯一索引、普通索引、组合索引、以及 全文索引(elasticsearch );
1.2主键索引
非空唯一索引,一个表只有一个主键索引;在 innodb 中,主键 索引的 B+ 树包含表数据信息;
PRIMARY KEY(key1, key2)
1.3唯一索引
不可以出现相同的值,可以有 NULL 值;
UNIQUE(key)
1.4普通索引
允许出现相同的索引内容;
INDEX(key)
-- OR
KEY(key[,...])
1.5组合索引
对表上的多个列进行索引
INDEX idx(key1,key2[,...]);
UNIQUE(key1,key2[,...]);
PRIMARY KEY(key1,key2[,...]);
1.6全文索引
将存储在数据库当中的整本书和整篇文章中的任意内容信息查 找出来的技术;关键词 FULLTEXT;
在短字符串中用 LIKE % ;在全文索引中用 match 和 against;
1.7主键选择
innodb 中表是索引组织表,每张表有且仅有一个主键;
1. 如果显示设置 PRIMARY KEY ,则该设置的 key 为该表的主 键;
2. 如果没有显示设置,则从非空唯一索引中选择;
1. 只有一个非空唯一索引,则选择该索引为主键;
2. 有多个非空唯一索引,则选择声明的第一个为主键;
3. 没有非空唯一索引,则自动生成一个 6 字节的 _rowid 作为 主键;
1.8约束
为了实现数据的完整性,对于 innodb ,提供了以下几种约束, primary key , unique key , foreign key , default , not null ;
1.9外键约束
外键用来关联两个表,来保证参照完整性; MyISAM 存储引擎本 身并不支持外键,只起到注释作用;而 innodb 完整支持外键, 并具备事务性;
create table parent (
id int not null,
primary key(id)
) engine=innodb;
create table child (
id int,
parent_id int,
foreign key(parent_id) references parent(id)
ON DELETE CASCADE ON UPDATE CASCADE
) engine=innodb;
-- 被引用的表为父表,引用的表称为子表;
-- 外键定义时,可以设置行为 ON DELETE 和 ON UPDATE,行
为发生时的操作可选择:
-- CASCADE 子表做同样的行为
-- SET NULL 更新子表相应字段为 NULL
-- NO ACTION 父类做相应行为报错
-- RESTRICT 同 NO ACTION
INSERT INTO parent VALUES (1);
INSERT INTO parent VALUES (2);
INSERT INTO child VALUES (10, 1);
INSERT INTO child VALUES (20, 2);
DELETE FROM parent WHERE id = 1;
1.10 约束与索引的区别
创建主键索引或者唯一索引的时候同时创建了相应的约束;但 是约束时逻辑上的概念;索引是一个数据结构既包含逻辑的概 念也包含物理的存储方式;
2.索引的实现原理
2.1索引存储
innodb 由段、区、页组成;段分为数据段、索引段、回滚段 等;区大小为 1 MB (一个区由 64 个连续页构成);页的默认 值为 16k ;页为逻辑页,磁盘物理页大小一般为 4K 或者 8K ; 为了保证区中的页的连续,存储引擎一般一次从磁盘中申请 4~5 个区;
2.2页
页是 innodb 磁盘管理的最小单位;默认 16K ,可通过 innodb_page_size 参数来修改;
B+ 树的一个节点的大小就是该页的值;
2.3B+树
全称:多路平衡搜索树,减少磁盘访问次数;用来组织磁盘数 据,以页为单位,物理磁盘页一般为 4K , innodb 默认页大小为 16K;对页的访问是一次磁盘 IO ,缓存中会缓存常访问的页;平衡二叉树(红黑树、 AVL 树)特征:非叶子节点只存储索引信息,叶子节点存储具体数据信息;叶子节点之间互相连接,方便范围查询;每个索引对应着一个 B+ 树;
B+ 树层高问题
B+ 树的一个节点对应一个数据页; B+ 树的层越高,那么要读取到内存的数据页越多, IO 次数越多;innodb 一个节点 16KB ;
假设 : key 为 10 byte 且指针大小 6 byte ,假设一行记录的大小为 1KB ;那么一个非叶子节点可存下 16 KB / 16 byte=1024 个 (key+point );每个叶子节点可存储 1024 行数据;结论 :2 层 B+ 树叶子节点 1024 个,可容纳最大记录数为: 1024 * 16 = 16384 ;3 层 B+ 树叶子节点 1024 * 1024 ,可容纳最大记录数为: 1024 * 1024 * 16 = 16777216 ;4 层 B+ 数叶子节点 1024 * 1024 * 1024 ,可容纳最大记录数 为:1024 * 1024 * 1024 * 16 = 17179869184 ;
2.4关于自增 id
超过类型最大值会报错;类型 bigint 范围: ;假设采用 bigint , 1 秒插入 1 亿条数据,大概需要 5849 年才 会用完索引;
2.5聚集索引
按照主键构造的 B+ 树;叶子节点中存放数据页;数据也是索引 的一部分
# table id name
select * from user where id >= 18 and id < 40;
2.6辅助索引
叶子节点不包含行记录的全部数据;辅助索引的叶子节点中, 除了用来排序的 key 还包含一个 bookmark ;该书签存储了聚 集索引的 key ;
-- 某个表 包含 id name lockyNum; id是主键,lockyNum
辅助索引;
-- KEY()
select * from user where lockyNum = 33;
2.7innodb 体系结构
2.8Buffer pool
Buffer pool 缓存表和索引数据;采用 LRU 算法(原理如下图)让 Buffer pool 只缓存比较热的数据;
2.9Change buffer
Change buffer 缓存辅助(二级)索引的数据变更( DML 操作)这些数据并不在 buffer pool 中, Change buffer 中的数据将会异步 merge 到 buffer pool 中,当下次从磁盘当中读取非唯一索引的数据;同时会定期合并到索引页中。
free list 组织 buffer pool 中未使用的缓存页; flush list 组织 buffer pool 中脏页,也就是待刷盘的页; lru list 组织 buffer pool 中冷热数据,当 buffer pool 没有空闲页,将从 lru list 中最久未使用的数据进行淘汰;
2.10最左匹配原则
对于组合索引,从左到右依次匹配,遇到就停止匹配;
2.11覆盖索引
从辅助索引中就能找到数据,而不需通过聚集索引查找;利用辅助索引树高度一般低于聚集索引树;较少磁盘 IO ;
2.12索引下推
为了减少回表次数,提升查询效率;在 MySQL 5.6 的版本开始推出;MySQL 架构分为 server 层和存储引擎层;
没有索引下推机制之前, server 层向存储引擎层请求数据,在 server 层根据索引条件判断进行数据过滤;
有索引下推机制之后,将部分索引条件判断下推到存储引擎中过滤数据;最终由存储引擎将数据汇总返回给 server 层;
2.13索引失效
select ... where A and B 若 A 和 B 中有一个不包含索引,则索引失效;索引字段参与运算,则索引失效;例如: from_unixtime(idx)= '2021 - 04 - 30' ; 改成 idx = unix_timestamp("2021 - 04 - 30")索引字段发生隐式转换,则索引失效;例如:将列隐式转换为某个类型,实际等价于在索引列上作用了隐式转换函数;LIKE 模糊查询,通配符 % 开头,则索引失效;例如: select* from user where name like '%Mark' ;在索引字段上使用 NOT <> != 索引失效;如果判断 id <> 0则修改为 idx > 0 or idx < 0 ; 组合索引中,没使用第一列索引,索引失效;
2.14索引原则
查询频次较高且数据量大的表建立索引;索引选择使用频次较 高,过滤效果好的列或者组合;
使用短索引;节点包含的信息多,较少磁盘 IO 操作;比如: smallint, tinyint ;
对于很长的动态字符串,考虑使用前缀索引;
于组合索引,考虑最左侧匹配原则和覆盖索引;
尽量选择区分度高的列作为索引;该列的值相同的越少越好
尽量扩展索引,在现有索引的基础上,添加复合索引;最多 6 个 索引不要 select * ; 尽量只列出需要的列字段;方便使用覆盖索 引;索引列,列尽量设置为非空;可选:开启自适应 hash 索引或者调整 change buffer ;
3.出现了sql比较慢怎么办
在面试的过程中如果遇到面试官拷问SQL比较慢 应该怎么解决这个问题?
我们应该先找到是哪条哪些SQL语句导致了 SQL比较慢?
3.1.我们采用 开启慢查询日志 或者 show processlist 进行对SQL语句的搜寻
慢日志查询
开启
查看
SHOW GLOBAL VARIABLES LIKE 'slow_query%';
SHOW GLOBAL VARIABLES LIKE 'long_query%';
设置
SET GLOBAL slow_query_log = ON; -- on 开启 off
关闭
SET GLOBAL long_query_time = 4; -- 单位秒;默认
10s;此时设置为4s
或者修改配置
slow_query_log = ON
long_query_time = 4
slow_query_log_file = D:/mysql/mysql57-slow.log
mysqldumpslow
查找最近10条慢查询日志
mysqldumpslow -s t -t 10 -g 'select'
D:/mysql/mysql57-slow.log
SHOW PROFILE
# 查看是否开启
SELECT @@profiling;
# 设置开启
SET profiling = 1;
# 查看所有 profiles
show profiles;
# 查看query id 为 10 那条查询
show profile for query 10;
# 查看最后一条查询
show profile;
# 最后关闭
SET profiling = 0;
查看连接线程;可以查看此时线上运行的 SQL 语句;如果要查看完整的 SQL 语句: SHOW FULL PROCESSLIST ; 然后优化该语句;
3.2找到对应的SQL语句进行分析和优化
①我们可以在索引方面进行优化
我们查看where group by order by 这三个条件语句后面的字段是否走了索引 如果没走索引我们可以设置为走索引 从而增加SQL语句的执行效率
②我们可以查看SQL语句用了哪些,我们如果看到in 和 not 优化成联合查询 或者 减少联合查询 增加SQL语句的执行效率
③工作中尽量不要使用age字段 因为顺着人的年龄变化 我们需要修改数据库中age字段的数据 ,我们可以直接存储他的生日就行