MySQL笔记详解

MySQL

– 2021


文章目录

  • MySQL
    • @[toc]
    • 1.介绍
      • 1.1 mysql的特点
      • 1.2 SQL语言的组成
      • 1.3 非关系型数据库
    • 2. 数据库操作
      • 2.1 数据库的常用操作(DDL)
      • 2.2 数据库表的操作(DDL)
      • 2.3 数据库的基本操作(DML)
        • 2.3.1 数据插入:insert
        • 2.3.2 数据修改:update
        • 2.3.3 数据删除:delete
      • 2.4 MySQL约束
        • 2.4.1 主键约束
          • 2.4.1.1 添加单列主键
        • 2.4.2 非空约束(not null)
        • 2.4.3 唯一约束
        • 2.4.4 默认约束
        • 2.4.5 零填充约束
      • 2.5 数据库基本操作(DQL)--基本查询
        • 2.5.1 案例
        • 2.5.2 基础使用
        • 2.5.3 分页查询-limit
        • 2.5.4 INSERT INTO SELECT语句
        • 2.5.5 正则表达式
      • 2.6 多表操作
        • 2.6.1 多表联合查询
        • 2.6.2 笛卡尔乘积
        • 2.6.3 内连接(查询求多张表的交集)
        • 2.6.4 外连接
        • 2.6.5 子查询
          • 2.6.5.1 ALL
          • 2.6.5.2 any 和 some
          • 2.6.5.3 in
          • 2.6.5.4 exists (推荐使用)
        • 2.6.6 函数
          • 2.6.6.1 聚合函数
          • 2.6.6.2 数学函数
          • 2.6.6.3 字符串函数
          • 2.6.6.4 日期函数
          • 2.6.6.5 控制流函数
        • 2.6.7 窗口函数
          • 2.6.7.1 序号函数
          • 2.6.7.2 **开窗聚合函数- SUM,AVG,MIN,MAX**
          • 2.6.7.3 **分布**函数- CUME_DIST和PERCENT_RANK
          • 2.6.7.4 **前后函数**-LAG和LEAD
          • 2.6.7.5 头尾函数-FIRS和LAST_VALUE
          • 2.6.7.6 其他函数-NTH_VALUE(expr, n)、NTILE(n)
    • 3. 索引
      • 3.1 索引的分类
      • 3.2 单列索引
        • 3.2.1普通索引
        • 3.2.1.1 查询删除索引
        • 3.2.2 唯一索引
        • 3.2.3 主键索引
      • 3.3 组合索引
      • 3.4 全文索引
      • 3.5 空间索引
      • 3.6 索引的原理(算法)
      • 3.7 索引的优缺点
        • 3.7.1 **索引的优点**
        • 3.7.2 **索引的缺点**
        • 3.7.3 创建索引的原则
    • 4.存储引擎
      • 4.1 InnoDB 和 MyISAM的区别
      • 4.2 存储引擎相关操作
    • 5. 事务
      • 5.1 事务操作
      • 5.2 事务的特性
      • 5.3 事务的隔离级别
        • 5.3.1 概念
        • 5.3.2 隔离界别的操作
    • 6. 锁机制
      • 6.1 MyISAM
        • 6.1.1 表锁特点
      • 6.2 InnoDB
    • 7. 日记
      • 7.1 **错误日记**
      • 7.2 **二进制日记 -- binlog**
      • 7.3 查询日记
      • 7.4 慢日记查询
    • 8. MySQL的优化
      • 8.1 查看MYSQL的执行效率
      • 8.2 **定位低效率执行**SQL
      • 8.3 ***explain*** 分析执行计划
        • 8.3.1 **Explain**分析执行计划 **-- **Explain 之 id
        • 8.3.2 **Explain**分析执行计划-Explain之 **select_type**
        • 8.3.3 **Explain**析执行计划-Explain**之** **type**
        • 8.3.4 **Explain分析执行计划**-其他指标字段
      • 8.4 **show profile**分析 - SQL
      • 8.5 **trace**分析优化器执行计划
      • 8.6 使用索引优化
        • 8.6.1 最左前缀法则
        • 8.6.2 范围查询中的索引失效
        • 8.6.3 尽量使用覆盖索引
        • 8.6.4 用 or 分隔开的条件,那么涉及到的索引都不会被用到
        • 8.6.5 % 开头的模糊查询
        • 8.6.6 其他的情况
      • 8.7 SQL优化
        • 8.7.1 大批量插入数据
          • 8.7.1.1 主键顺序插入
          • 8.7.1.2 关闭唯一性校验
          • 8.7.1.3 优化insert语句
        • 8.7.2 优化Order by 语句
        • 8.7.3 优化 group by
        • 8.7.4 优化子查询
        • 8.7.5 优化limit查询
        • 8.7.3 优化 group by
        • 8.7.4 优化子查询
        • 8.7.5 优化limit查询

1.介绍

1.1 mysql的特点

  • 具有综合统一性,不同数据库的支持的SQL稍有不同
  • 非过程化语言
  • 语言简捷,用户容易接受
  • 以一种语法结构提供两种使用方式

1.2 SQL语言的组成

  • 数据定义语言-DDL
  • 数据操纵语言-DML
  • 数据控制语言-DCL
  • 数据查询语言-DQL

1.3 非关系型数据库

  • Redis(最好的缓存数据库)
  • MongoDB(最好的文档型数据库)
  • Elasticsearch(最好的搜索服务)
  • Cassandra(最好的列式数据库)
  • HBase(优秀的分布式、列式数据库)

2. 数据库操作

2.1 数据库的常用操作(DDL)

-- 常看当前的数据库
SHOW DATABASES;
-- 创建数据库表
CREATE DATABASE [IF NOT EXISTS] 数据库名 [CHARSET=utf8];
-- 使用数据库
use 数据库名;
-- 删除指定的数据库
drop database 数据库名;
-- 更改数据库的编码方式
ALTER DATABASE 数据库名 CHARACTER SET utf8;

2.2 数据库表的操作(DDL)

-- 查看当前数据库的所有数据库表
SHOW TABLES;
-- 查看某个表的创建语句
SHOW CREATE TABLE 数据库表名 
-- 查看表结构
DESC 数据库表名;
-- 删除表
DROP TABLE 数据库表名;

-- 创建数据库表
create table [if not exists]表名(
  字段名1 类型[(宽度)] [约束条件] [comment '字段说明'],
  字段名2 类型[(宽度)] [约束条件] [comment '字段说明'],
  字段名3 类型[(宽度)] [约束条件] [comment '字段说明']
)[表的一些设置];

-- 比如
use mydb1;
create  table if not exists student(
    sid int,
    name varchar(20),
    gender varchar(20),
    age int,
    birth date, 
    address varchar(20),
    score double
);


-- 对表的结构进行修改
-- 给指定的表添加一列
alter table 表名 add 列名 类型(长度) [约束];
# 为student表添加一个新的字段为:系别 dept 类型为 varchar(20)
-- ALTER TABLE student ADD `dept` VARCHAR(20); 

-- 修改表中的指定字段名
alter table 表名 change 旧列名 新列名 类型(长度) 约束; 
#为student表的dept字段更换为department varchar(30)
-- ALTER TABLE student change `dept` department VARCHAR(30); 

-- 删除表中的某一列
alter table 表名 drop 列名;
#删除student表中department这列
-- ALTER TABLE student DROP department;

-- 修改表的名字
rename table 表名 to 新表名;
#将表student改名成 stu
-- rename table `student`  to stu;

2.3 数据库的基本操作(DML)

  • 基本关键字
    • insert 插入
    • update 更新
    • delete 删除
2.3.1 数据插入:insert
-- 公式
insert into 表 (列名1,列名2,列名3...) values (值1,值2,值3...); //向表中插入某些
insert into 表 values (值1,值2,值3...);     //向表中插入所有列

-- 例子
INSERT INTO student(sid,NAME,gender,age,birth,address,score) VALUES(1001,'男',18,'1996-12-23','北京',83.5);
INSERT INTO student VALUES(1001,'男',18,'1996-12-23','北京',83.5);
2.3.2 数据修改:update
-- 公式
update 表名 set 字段名=值,字段名=值...;
update 表名 set 字段名=值,字段名=值... where 条件;

-- 例子
-- 将所有学生的地址修改为重庆 
update student set address = '重庆’; 

-- 讲id为1004的学生的地址修改为北京 
update student set address = '北京' where id = 1004 

-- 讲id为1005的学生的地址修改为北京,成绩修成绩修改为100 
update student set address = '广州',score=100 where id = 1005
2.3.3 数据删除:delete
-- 公式
delete from 表名 [where 条件];
truncate table  表名 或者 truncate 表名

-- 例子
-- 1.删除sid为1004的学生数据
delete from student where sid  = 1004;
-- 2.删除表所有数据
delete from student;
-- 3.清空表数据
truncate table student;
truncate student;

-- 注意:delete和truncate原理不同,delete只删除内容,而truncate类似于drop table
-- 可以理解为是将整个表删除,然后再创建该表;

2.4 MySQL约束

  • 作用:
    • 表在设计的时候加入约束的目的就是为了保证表中的记录完整性和有效性,比如用户表有些列的值(手机号)不能为空,有些列的值(身份证号)不能重复。
  • 分类
    • 主键约束(primary key) PK
    • 自增长约束(auto_increment)
    • 非空约束(not null)
    • 唯一性约束(unique)
    • 默认约束(default)
    • 零填充约束(zerofill)
    • 外键约束(foreign key) FK
2.4.1 主键约束
  • MySQL主键约束是一个列或者多个列的组合,其值能唯一地标识表中的每一行,方便在RDBMS中尽快的找到某一行。
  • 主键约束相当于 唯一约束 + 非空约束 的组合,主键约束列不允许重复,也不允许出现空值。
  • 每个表最多只允许一个主键
  • 主键约束的关键字是:primary key
  • 当创建主键的约束时,系统默认会在所在的列和列组合上建立对应的唯一索引。
2.4.1.1 添加单列主键
-- 在 create table 语句中,通过 PRIMARY KEY 关键字来指定主键。
-- 在定义字段的同时指定主键,语法格式如下:
-- 在定义字段的同时指定主键
create table 表名(
   ...
   <字段名> <数据类型> primary key 
   ...
)

-- 例子
create table emp1(
    eid int primary key,
    name VARCHAR(20),
    deptId int,
    salary double
);

-- 作为主键的列: 不能为 null, 值不能重复
insert into emp1(eid,name,deptId,salary) values(1001,'zhangsan',20,2000);

-- 定义完字段之后指定主键
-- 语法
--在定义字段之后再指定主键,语法格式如下:
create table 表名(
   ...
   [constraint <约束名>](可以不写) primary key [字段名]
);

-- 例子
create table emp2(
    eid INT,
    name VARCHAR(20),
    deptId INT,
    salary double,
    [constraint  pk1] primary key(id)
 );

-- 所谓的联合主键,就是这个主键是由一张表中多个字段组成的。
-- 注意:
   -- 1. 当主键是由多个字段组成时,不能直接在字段名后面声明主键约束。
   -- 2. 一张表只能有一个主键,联合主键也是一个主键
-- 联合主键不能出现重复,必须有一个不同,或者全不同,任何一列都不能为空
create table 表名(
   ...
   primary key (字段1,字段2,…,字段n)
);


-- 或者直接修改表结构
-- 添加主键
alter table emp1 add primary key(name,deptId)

-- 删除主键(包括了联合主键)
alter table <数据表名> drop primary key;

-- 在 MySQL 中,当主键定义为自增长后,这个主键的值就不再需要用户输入数据了,而由数据库系统根据定义自动赋值。每增加一条记录,主键会自动以相同的步长进行增长。
-- 通过给字段添加 auto_increment 属性来实现主键自增长
-- 字段名 数据类型 auto_increment
-- 从指定的数字100开始自增:auto_increment=100
create table emp2(
    eid INT primary key auto_increment,
    name VARCHAR(20),
    deptId INT,
    salary double,
 );
 
-- auto_increment的特点:
-- 默认情况下,auto_increment的初始值是 1,每新增一条记录,字段值自动加 1。
-- 一个表中只能有一个字段使用 auto_increment约束,且该字段必须有唯一索引,以避免序号重复(即为主键或主键的一部分)。
-- auto_increment约束的字段必须具备 NOT NULL 属性。
-- auto_increment约束的字段只能是整数类型(TINYINT、SMALLINT、INT、BIGINT 等。
-- auto_increment约束字段的最大值受该字段的数据类型约束,如果达到上限,auto_increment就会失效。
2.4.2 非空约束(not null)
  • MySQL 非空约束(not null)指字段的值不能为空。对于使用了非空约束的字段,如果用户在添加数据时没有指定值,数据库系统就会报错。
-- 方式1:<字段名><数据类型> not null;
-- 方式2:alter table 表名 modify 字段 类型 not null;

-- 方式1,创建表时指定
create table t_user6 ( 
  id int , 
  name varchar(20) not null, 
  address varchar(20) not null 
);

-- 或者直接使用语句
alter table t_user6 modify name varchar(20) not null; 

-- 删除非空约束
-- alter table 表名 modify 字段 类型 
alter table t_user6 modify name varchar(20) ; 
2.4.3 唯一约束
  • 唯一约束(Unique Key)是指所有记录中字段的值不能重复出现。例如,为 id 字段加上唯一性约束后,每条记录的 id 值都是唯一的,不能出现重复的情况。
-- 方式1:<字段名> <数据类型> unique
-- 方式2: alter table 表名 add constraint 约束名 unique(列);

-- 创建表时指定
create table t_user8 ( 
 id int , 
 name varchar(20) , 
 phone_number varchar(20) unique -- 指定唯一约束 
);
-- 或者
alter table t_user8 add [constraint unique_ph] unique(phone_number);

-- 删除唯一索引
-- alter table <表名> drop index <唯一约束名>;
alter table t_user8 drop index unique_ph;

2.4.4 默认约束
  • MySQL 默认值约束用来指定某列的默认值。
-- 方式1: <字段名> <数据类型> default <默认值>;
-- 方式2: alter table 表名 modify 列名 类型 default 默认值; 
create table t_user10 ( 
  id int , 
  name varchar(20) , 
  address varchar(20) default ‘北京’ -- 指定默认约束 
);
-- 或者
alter table t_user10 modify address varchar(20) default  ‘北京’;

-- 删除默认索引
-- alter table <表名> modify column <字段名> <类型> default null; 
alter table t_user10 modify column address varchar(20) default null;

2.4.5 零填充约束
  • 1、插入数据时,当该字段的值的长度小于定义的长度时,会在该值的前面补上相应的0
  • 2、zerofill默认为int(10)
  • 3、当使用zerofill 时,默认会自动加unsigned(无符号)属性,使用unsigned属性后,数值范围是原值的2倍,例如,有符号为-128+127,无符号为0256。
create table t_user12 ( 
  id int zerofill , -- 零填充约束
  name varchar(20)   
);

-- 删除0填充约束
alter table t_user12 modify id int;

2.5 数据库基本操作(DQL)–基本查询

select 
  [all|distinct]
  <目标列的表达式1> [别名],
  <目标列的表达式2> [别名]...
from <表名或视图名> [别名],<表名或视图名> [别名]...
[where<条件表达式>]
[group by <列名> 
 [having <条件表达式>]]
[order by <列名> [asc|desc]]
[limit <数字或者列表>];

-- 简化版本
select *| 列名 from 表 where 条件

2.5.1 案例
-- 创建数据库
create database if not exist mydb2;
use mydb2;
-- 创建商品表:
create table product(
    pid int primary key auto_increment, -- 商品编号
    pname varchar(20) not null , -- 商品名字
    price double,  -- 商品价格
    category_id varchar(20) -- 商品所属分类
);

-- 插入数据信息
insert into product values(null,'海尔洗衣机',5000,'c001');
insert into product values(null,'美的冰箱',3000,'c001');
insert into product values(null,'格力空调',5000,'c001');
insert into product values(null,'九阳电饭煲’,200,'c001');

                           insert into product values(null,'啄木鸟衬衣',300,'c002');
insert into product values(null,'恒源祥西裤',800,'c002');
insert into product values(null,'花花公子夹克',440,'c002');
insert into product values(null,'劲霸休闲裤',266,'c002');
insert into product values(null,'海澜之家卫衣',180,'c002');
insert into product values(null,'杰克琼斯运动裤',430,'c002');
 
insert into product values(null,'兰蔻面霜',300,'c003');
insert into product values(null,'雅诗兰黛精华水',200,'c003');
insert into product values(null,'香奈儿香水',350,'c003');
insert into product values(null,'SK-II神仙水',350,'c003');
insert into product values(null,'资生堂粉底液',180,'c003');
 
insert into product values(null,'老北京方便面',56,'c004');
insert into product values(null,'良品铺子海带丝',17,'c004');
insert into product values(null,'三只松鼠坚果',88,null);
-- 1.查询所有的商品.  
select *  from product;
-- 2.查询商品名和商品价格. 
select pname,price from product;
-- 3.别名查询.使用的关键字是as(as可以省略的).  
-- 3.1表别名: 
select * from product as p;
-- 3.2列别名:
select pname as pn from product; 
-- 4.去掉重复值.  
select distinct price from product;
-- 5.查询结果是表达式(运算查询):将所有商品的价格+10元进行显示.
select pname,price+10 from product;
  • 算数运算符
算术运算符 说明
+ 加法运算
- 减法运算
***** 乘法运算
/ DIV 除法运算,返回商
% MOD 求余运算,返回余数
  • 比较运算符
比较运算符 说明
= 等于
< <= 小于和小于等于
> >= 大于和大于等于
<=> 安全的等于,两个操作码均为NULL时,其所得值为1;而当一个操作码为NULL时,其所得值为0
<> **!=** 不等于
IS NULL ISNULL 判断一个值是否为 NULL
IS NOT NULL 判断一个值是否不为 NULL
LEAST 当有两个或多个参数时,返回最小值
GREATEST 当有两个或多个参数时,返回最大值
BETWEEN AND 判断一个值是否落在两个值之间
IN 判断一个值是IN列表中的任意一个值
NOT IN 判断一个值不是IN列表中的任意一个值
LIKE 通配符匹配
REGEXP 正则表达式匹配
  • 逻辑运算符
逻辑运算符 说明
NOT 或者 ! 逻辑非
AND 或者 && 逻辑与
OR 或者 || 逻辑或
XOR 逻辑异或
  • 位运算符
位运算符 说明
| 按位或
& 按位与
^ 按位异或
<< 按位左移
>> 按位右移
~ 按位取反,反转所有比特
2.5.2 基础使用
  • 排序查询
-- 公式
select 
 字段名1,字段名2,……
from 表名
order by 字段名1 [asc|desc],字段名2[asc|desc]……


-- 例子
-- 1.使用价格排序(降序)
select * from product order by price desc;
-- 2.在价格排序(降序)的基础上,以分类排序(降序)
select * from product order by price desc,category_id asc;
-- 3.显示商品的价格(去重复),并排序(降序)
select distinct price from product order by price desc;
  • 聚合查询
聚合函数 作用
count() 统计指定列不为NULL的记录行数;
sum() 计算指定列的数值和,如果指定列类型不是数值类型,那么计算结果为0
max() 计算指定列的最大值,如果指定列是字符串类型,那么使用字符串排序运算;
min() 计算指定列的最小值,如果指定列是字符串类型,那么使用字符串排序运算;
avg() 计算指定列的平均值,如果指定列类型不是数值类型,那么计算结果为0
-- 1 查询商品的总条数
select count(*) from product;
-- 2 查询价格大于200商品的总条数
select count(*) from product where price > 200;
-- 3 查询分类为'c001'的所有商品的总和
select sum(price) from product where category_id = 'c001';
-- 4 查询商品的最大价格
select max(price) from product;
-- 5 查询商品的最小价格
select min(price) from product;
-- 6 查询分类为'c002'所有商品的平均价格
select avg(price) from product where category_id = 'c002';
  • 聚合查询–null值的处理
# 1、count函数对null值的处理
# 如果count函数的参数为星号(*),则统计所有记录的个数。而如果参数为某字段,不统计含null值的记录个数。

# 2、sum和avg函数对null值的处理
# 这两个函数忽略null值的存在,就好象该条记录不存在一样。

# 3、max和min函数对null值的处理
# max和min两个函数同样忽略null值的存在。
  • 分组查询-group by
-- 公式
select 字段1,字段2… from 表名 group by 分组字段 having 分组条件;

-- 例子
-- 1 统计各个分类商品的个数
select category_id ,count(*) from product group by category_id ;
-- 如果要进行分组的话,则SELECT子句之后,只能出现分组的字段和统计函数,其他的字段不能出现:
  • 分组之后的条件筛选-having
  • 分组之后对统计结果进行筛选的话必须使用having,不能使用where
  • where子句用来筛选 FROM 子句中指定的操作所产生的行
  • group by 子句用来分组 WHERE 子句的输出。
  • having 子句用来从分组的结果中筛选行
-- 语法
select 字段1,字段2… from 表名 group by 分组字段 having 分组条件;

-- 例子
-- 2.统计各个分类商品的个数,且只显示个数大于4的信息
select category_id ,count(*) from product group by category_id having count(*) > 1;
2.5.3 分页查询-limit
-- 语法
-- 方式1-显示前n条
select 字段1,字段2... from 表明 limit n
-- 方式2-分页显示
select 字段1,字段2... from 表明 limit m,n
m: 整数,表示从第几条索引开始,计算方式 (当前页-1)*每页显示条数
n: 整数,表示查询多少条数据


---- 基本操作
-- 查询product表的前5条记录 
select * from product limit 5 

-- 从第4条开始显示,显示5条 
select * from product limit 3,5 
2.5.4 INSERT INTO SELECT语句
-- 将一张表的数据导入到另一张表中,可以使用INSERT INTO SELECT语句 。
insert into Table2(field1,field2,…) select value1,value2,… from Table1 或者:
insert into Table2 select * from Table1

2.5.5 正则表达式
模式 描述
^ 匹配输入字符串的开始位置。
$ 匹配输入字符串的结束位置。
. 匹配除 “\n” 之外的任何单个字符。
[…] 字符集合。匹配所包含的任意一个字符。例如, ‘[abc]’ 可以匹配 “plain” 中的 ‘a’。
[^…] 负值字符集合。匹配未包含的任意字符。例如, ‘[^abc]’ 可以匹配 “plain” 中的’p’。
p1|p2|p3 匹配 p1 或 p2 或 p3。例如,‘z|food’ 能匹配 “z” 或 “food”。’(z|f)ood’ 则匹配 “zood” 或 “food”。
  • 格式
模式 描述
***** 匹配前面的子表达式零次或多次。例如,zo* 能匹配 “z” 以及 “zoo”。* 等价于{0,}。
+ 匹配前面的子表达式一次或多次。例如,‘zo+’ 能匹配 “zo” 以及 “zoo”,但不能匹配 “z”。+ 等价于 {1,}。
{n} n 是一个非负整数。匹配确定的 n 次。例如,‘o{2}’ 不能匹配 “Bob” 中的 ‘o’,但是能匹配 “food” 中的两个 o。
{n,m} m 和 n 均为非负整数,其中n <= m。最少匹配 n 次且最多匹配 m 次。
  • 操作
-- ^ 在字符串开始处进行匹配
SELECT  'abc' REGEXP '^a';
 
-- $ 在字符串末尾开始匹配
SELECT  'abc' REGEXP 'a$';
SELECT  'abc' REGEXP 'c$’;

-- . 匹配任意字符
SELECT  'abc' REGEXP '.b';
SELECT  'abc' REGEXP '.c';
SELECT  'abc' REGEXP 'a.';
 
-- [...] 匹配括号内的任意单个字符
SELECT  'abc' REGEXP '[xyz]';
SELECT  'abc' REGEXP '[xaz]';
-- [^...] 注意^符合只有在[]内才是取反的意思,在别的地方都是表示开始处匹配
SELECT  'a' REGEXP '[^abc]';
SELECT  'x' REGEXP '[^abc]';
SELECT  'abc' REGEXP '[^a]';
 
-- a* 匹配0个或多个a,包括空字符串。 可以作为占位符使用.有没有指定字符都可以匹配到数据
 
SELECT 'stab' REGEXP '.ta*b';
SELECT 'stb' REGEXP '.ta*b';
SELECT '' REGEXP 'a*';
 
-- a+  匹配1个或者多个a,但是不包括空字符
SELECT 'stab' REGEXP '.ta+b';
SELECT 'stb' REGEXP '.ta+b';
-- a?  匹配0个或者1个a
SELECT 'stb' REGEXP '.ta?b';
SELECT 'stab' REGEXP '.ta?b';
SELECT 'staab' REGEXP '.ta?b';
 
-- a1|a2  匹配a1或者a2,
SELECT 'a' REGEXP 'a|b';
SELECT 'b' REGEXP 'a|b';
SELECT 'b' REGEXP '^(a|b)';
SELECT 'a' REGEXP '^(a|b)';
SELECT 'c' REGEXP '^(a|b)';
 
-- a{m} 匹配m个a
SELECT 'auuuuc' REGEXP 'au{4}c';
SELECT 'auuuuc' REGEXP 'au{3}c';
-- a{m,n} 匹配m到n个a,包含m和n
SELECT 'auuuuc' REGEXP 'au{3,5}c';
SELECT 'auuuuc' REGEXP 'au{4,5}c';
SELECT 'auuuuc' REGEXP 'au{5,10}c';
 
-- (abc) abc作为一个序列匹配,不用括号括起来都是用单个字符去匹配,如果要把多个字符作为一个整体去匹配就需要用到括号,所以括号适合上面的所有情况。
SELECT 'xababy' REGEXP 'x(abab)y';
SELECT 'xababy' REGEXP 'x(ab)*y';
SELECT 'xababy' REGEXP 'x(ab){1,2}y';

2.6 多表操作

  • 一对一
  • 多表关系

学生和课程

  • 分析:一个学生可以选择很多门课程,一个课程也可以被很多学生选择
  • 原则:多对多关系实现需要借助第三张中间表。中间表至少包含两个字段,将多对多的关系,拆成一对多的关系,中间表至少要
  • 有两个外键,这两个外键分别指向原来的那两张表的主键

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yBFzxdFk-1645196290619)(C:\Users\lms\Desktop\md\mysql\mm.png)]

2.6.1 多表联合查询
  • 数据表
-- 创建部门表
CREATE TABLE IF NOT EXISTS dept3(
    deptno VARCHAR(20) PRIMARY KEY ,  -- 部门号
    NAME VARCHAR(20) -- 部门名字
);

-- 创建员工表
CREATE TABLE IF NOT EXISTS emp3(
    eid VARCHAR(20) PRIMARY KEY , -- 员工编号
    ename VARCHAR(20), -- 员工名字
    age INT,  -- 员工年龄
    dept_id VARCHAR(20)  -- 员工所属部门
);


-- 给dept3表添加数据
INSERT INTO dept3 VALUES('1001','研发部');
INSERT INTO dept3 VALUES('1002','销售部');
INSERT INTO dept3 VALUES('1003','财务部');
INSERT INTO dept3 VALUES('1004','人事部');


-- 给emp表添加数据
INSERT INTO emp3 VALUES('1','乔峰',20, '1001');
INSERT INTO emp3 VALUES('2','段誉',21, '1001');
INSERT INTO emp3 VALUES('3','虚竹',23, '1001');
INSERT INTO emp3 VALUES('4','阿紫',18, '1001');
INSERT INTO emp3 VALUES('5','扫地僧',85, '1002');
INSERT INTO emp3 VALUES('6','李秋水',33, '1002');
INSERT INTO emp3 VALUES('7','鸠摩智',50, '1002'); 
INSERT INTO emp3 VALUES('8','天山童姥',60, '1003');
INSERT INTO emp3 VALUES('9','慕容博',58, '1003');
INSERT INTO emp3 VALUES('10','丁春秋',71, '1005');
  • 多表查询的分类
# 交叉连接查询 [产生笛卡尔积,了解]
    -- 语法:select * from A,B;  

# 内连接查询(使用的关键字 inner join  -- inner可以省略)
    -- 隐式内连接(SQL92标准):select * from A,B where 条件;
    -- 显示内连接(SQL99标准):select * from A inner join B on 条件;

# 外连接查询(使用的关键字 outer join -- outer可以省略)
    -- 左外连接:left outer join(左表全部输出)
       select * from A left outer join B on 条件;
    -- 右外连接:right outer join(右表的全部输出)
       select * from A right outer join B on 条件;
    -- 满外连接: full outer join(相当于两张表的并集)
       select * from A full outer join B on 条件;
# 子查询
       select的嵌套
# 表自关联:
    -- 将一张表当成多张表来用

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k8BGZJeb-1645196290621)(C:\Users\lms\Desktop\md\mysql\select1.png)]

2.6.2 笛卡尔乘积
-- 交叉连接查询返回被连接的两个表所有数据行的笛卡尔积
-- 笛卡尔积可以理解为一张表的每一行去和另外一张表的任意一行进行匹配
-- 假如A表有m行数据,B表有n行数据,则返回m*n行数据
-- 笛卡尔积会产生很多冗余的数据,后期的其他查询可以在该集合的基础上进行条件筛选

-- 格式
select * from 表1,表2,表3….; 
-- 例子:出现了很多脏数据信息
-- 交叉连接查询,会出现笛卡尔乘积的现象
select * from dept3,emp3;
2.6.3 内连接(查询求多张表的交集)
-- 格式
-- 隐式内连接(SQL92标准):select * from A,B where 条件; 
-- 显示内连接(SQL99标准):select * from A inner join B on 条件;

-- 例子
-- 产生笛卡尔积的现象
-- 查询每个员工的部门
SELECT * FROM emp3,dept3;

-- 查询每个员工所属的部门 inner可以直接省略(对于内连接,直接两张表相连即可)
SELECT * FROM emp3 e JOIN dept3 d ON e.`dept_id` = d.`deptno`;
select * from dept3,emp3 where dept3.deptno = emp3.dept_id;
select * from dept3 inner join emp3 on dept3.deptno = emp3.dept_id;

-- 查询研发部门的所属员工
SELECT deptno FROM dept3 WHERE NAME = '研发部';
SELECT * FROM emp3 WHERE `dept_id` = (SELECT deptno FROM dept3 WHERE NAME = '研发部');
-- 
SELECT * FROM emp3 JOIN dept3 ON emp3.dept_id = dept3.deptno AND NAME = '研发部';


-- 查询研发部和销售部的所属员工
SELECT * FROM emp3 WHERE `dept_id` IN (SELECT deptno FROM dept3 WHERE NAME = '研发部' OR NAME = '销售部');
-- 
SELECT * FROM emp3 JOIN dept3 ON emp3.`dept_id` = dept3.deptno AND (NAME = '研发部' OR NAME = '销售部');
-- 
SELECT * FROM emp3 JOIN dept3 ON emp3.`dept_id` = dept3.deptno AND NAME IN ('研发部','销售部');


-- 查询每个部门的员工数,并升序排序
-- 分组查询,只能写分组的字段,但是name和id是一一对应的,所以没有问题
SELECT NAME,dept_id,COUNT(*) num FROM emp3 JOIN dept3 ON `dept_id` = deptno GROUP BY dept_id ORDER BY num ASC;


-- 查询人数大于等于3的部门,并按照人数降序排序
SELECT NAME,deptno,COUNT(*) num
FROM emp3 JOIN dept3
ON emp3.dept_id = dept3.deptno
GROUP BY deptno
HAVING num >= 3
ORDER BY num DESC;
2.6.4 外连接
-- 外连接分为左外连接(left outer join)、右外连接(right outer join),满外连接(full outer join)。
-- 注意:oracle里面有full join,可是在mysql对full join支持的不好。我们可以使用union来达到目的。

-- 左外连接:left outer join
select * from A left outer join B on 条件;
-- 右外连接:right outer join
select * from A right outer join B on 条件;
-- 满外连接: full outer join
select * from A full outer join B on 条件;
-- 外连接
-- 查询哪些部门有员工,哪些部门没有员工(部门表为主表)
SELECT * FROM dept3 LEFT JOIN emp3 ON dept_id = deptno;

-- 查询哪些员工有对应的部门,哪些没有(员工表为主表)
SELECT * FROM dept3 RIGHT JOIN emp3 ON dept_id = deptno;

-- 使用union关键字实现左外连接和右外连接的并集
-- union是将两个查询结果上下拼接,并去重
SELECT * FROM dept3 LEFT JOIN emp3 ON dept_id = deptno
UNION
SELECT * FROM dept3 RIGHT JOIN emp3 ON dept_id = deptno;
2.6.5 子查询
-- 子查询就是指的在一个完整的查询语句之中,嵌套若干个不同功能的小查询,从而一起完成复杂查询的一种编写形式,通俗一点就是包含select嵌套的查询。

# 子查询返回的数据可以分为四种
-- 单行单列:返回的是一个具体列的内容,可以理解为一个单值数据;
-- 单行多列:返回一行数据中多个列的内容;
-- 多行单列:返回多行记录之中同一列的内容,相当于给出了一个操作范围;
-- 多行多列:查询返回的结果是一张临时表
-- 子查询
-- 查询年龄最大的员工信息,显示信息包含员工号、员工名字,员工年龄
SELECT * FROM emp3 WHERE age = (SELECT MAX(age) FROM emp3);

-- 查询年研发部和销售部的员工信息,包含员工号、员工名字
-- 关联查询
SELECT * FROM emp3 JOIN dept3 ON emp3.dept_id = dept3.deptno AND NAME IN ('研发部','销售部');
-- 子查询
SELECT deptno FROM dept3 WHERE NAME = '研发部' OR NAME = '销售部';
SELECT * FROM emp3 WHERE emp3.dept_id IN (SELECT deptno FROM dept3 WHERE NAME = '研发部' OR NAME = '销售部');

-- 查询研发部20岁以下的员工信息,包括员工号、员工名字,部门名字
-- 关联查询
SELECT eid,ename,age,NAME FROM emp3 JOIN dept3 ON emp3.dept_id = dept3.deptno AND (NAME='研发部' AND age < 20);
-- 子查询
SELECT eid,ename,age,NAME FROM emp3 JOIN dept3 ON emp3.dept_id = dept3.deptno
AND dept3.deptno = (SELECT deptno FROM dept3 WHERE NAME='研发部')
HAVING age < 20;
  • 子查询关键字
  • 在子查询中,有一些常用的逻辑关键字,这些关键字可以给我们提供更丰富的查询功能,主要关键字如下:
    • 1.ALL关键字
    • 2.ANY关键字
    • 3.SOME关键字
    • 4.IN关键字
    • 5.EXISTS关键字
2.6.5.1 ALL
select …from …where c > all(查询语句)
--等价于:
select ...from ... where c > result1 and c > result2 and c > result3

-- ALL: 与子查询返回的所有值比较为true 则返回true
-- ALL可以与=、>、>=、<、<=、<>结合是来使用,分别表示等于、大于、大于等于、小于、小于等于、不等于其中的其中的所有数据。
-- ALL表示指定列中的值必须要大于子查询集的每一个值,即必须要大于子查询集的最大值;如果是小于号即小于子查询集的最小值。同理可以推出其它的比较运算符的情况。

-- All关键字
-- 查询年龄大于'1003'部门所有年龄的员工信息
SELECT * FROM emp3 WHERE age > ALL(SELECT age FROM emp3 WHERE dept_id='1003');

-- 查询不属于任何一个部门的员工信息 
SELECT * FROM emp3 WHERE dept_id != ALL(SELECT deptno FROM dept3);
2.6.5.2 any 和 some
select …from …where c > any(查询语句)
--等价于:
select ...from ... where c > result1 or c > result2 or c > result3

-- 特点:
-- ANY:与子查询返回的任何值比较为true 则返回true
-- ANY可以与=、>、>=、<、<=、<>结合是来使用,分别表示等于、大于、大于等于、小于、小于等于、不等于其中的其中的任何一个数据。
-- 表示制定列中的值要大于子查询中的任意一个值,即必须要大于子查询集中的最小值。同理可以推出其它的比较运算符的情况。
-- SOME和ANY的作用一样,SOME可以理解为ANY的别名
-- 查询年龄大于‘1003‘部门任意一个员工年龄的员工信息
SELECT * FROM emp3 WHERE age > ANY(SELECT age FROM emp3 WHERE dept_id = '1003');
2.6.5.3 in
select …from …where c in(查询语句)
-- 等价于:
select ...from ... where c = result1 or c = result2 or c = result3

-- IN关键字,用于判断某个记录的值,是否在指定的集合中
-- 在IN关键字前边加上not可以将条件反过来
-- 查询研发部和销售部的员工信息,包含员工号、员工名字
select eid,ename,t.name from emp3 where dept_id in (select deptno from dept3 where name = '研发部' or name = '销售部') ;
2.6.5.4 exists (推荐使用)
select …from …where exists(查询语句)

-- 该子查询如果“有数据结果”(至少返回一行数据), 则该EXISTS() 的结果为“true”,外层查询执行
-- 该子查询如果“没有数据结果”(没有任何数据返回),则该EXISTS()的结果为“false”,外层查询不执行
-- EXISTS后面的子查询不返回任何实际数据,只返回真或假,当返回真时 where条件成立
-- 注意,EXISTS关键字,比IN关键字的运算效率高,因此,在实际开发中,特别是大数据量时,推荐使用EXISTS关键字

-- exist
-- 查询公司是否有大于60岁的员工,有则输出
SELECT * FROM emp3 WHERE age > 60; 
SELECT * FROM emp3 a WHERE EXISTS (SELECT age FROM emp3 WHERE a.age > 60);

-- 查询有所属部门的员工信息
-- 查询每个员工的时候去部门表判断是否有对应的部门
SELECT * FROM emp3 a WHERE EXISTS(SELECT * FROM dept3 b WHERE a.dept_id = b.deptno);

2.6.6 函数
  • 在MySQL中,为了提高代码重用性和隐藏实现细节,MySQL提供了很多函数。函数可以理解为别人封装好的模板代码。
  • 在MySQL中,函数非常多,主要可以分为以下几类:
  • 聚合函数
  • 数学函数
  • 字符串函数
  • 日期函数
  • 控制流函数
  • 窗口函数

2.6.6.1 聚合函数
  • 在MySQL中,聚合函数主要由:count,sum,min,max,avg,这些聚合函数我们之前都学过,不再重复。这里我们学习另外一个函数:group_concat(),该函数用户实现行的合并
  • group_concat()函数首先根据group by指定的列进行分组,并且用分隔符分隔,将同一个分组中的值连接起来,返回一个字符串结果。
group_concat([distinct] 字段名 [order by 排序字段 asc/desc] [separator '分隔符']) -- 分隔符吧必须放置在最后的位置
-- 说明:
  -- (1)使用distinct可以排除重复值;
  -- (2)如果需要对结果中的值进行排序,可以使用order by子句;
  -- (3)separator是一个字符串值,默认为逗号。
-- 创建数据表
create table emp(
    emp_id int primary key auto_increment comment '编号',
    emp_name char(20) not null default '' comment '姓名',
    salary decimal(10,2) not null default 0 comment '工资',
    department char(20) not null default '' comment '部门'
);
 
insert into emp(emp_name,salary,department) 
values('张晶晶',5000,'财务部'),('王飞飞',5800,'财务部'),('赵刚',6200,'财务部'),('刘小贝',5700,'人事部'),
('王大鹏',6700,'人事部'),('张小斐',5200,'人事部'),('刘云云',7500,'销售部'),('刘云鹏',7200,'销售部'),
('刘云鹏',7800,'销售部');

-- 查询
-- 将所有员工的名字合并成一行 
SELECT GROUP_CONCAT(emp_name) FROM emp;

-- 指定分隔符合并
SELECT GROUP_CONCAT(emp_name SEPARATOR ';') FROM emp;

-- 指定排序方式和分隔符,分隔符的操作必须放置在最后,可以在聚合函数中指定排序的方式
SELECT department, GROUP_CONCAT(emp_name ORDER BY salary DESC SEPARATOR ';') FROM emp GROUP BY department;

2.6.6.2 数学函数
函数名 描述 实例
ABS(x) 返回 x 的绝对值 返回 -1 的绝对值:SELECT ABS(-1) – 返回1
CEIL(x) 返回大于或等于 x 的最小整数 SELECT CEIL(1.5) – 返回2
FLOOR(x) 返回小于或等于 x 的最大整数 小于或等于 1.5 的整数:SELECT FLOOR(1.5) – 返回1
GREATEST(expr1, expr2, expr3, …) 返回列表中的最大值 返回以下数字列表中的最大值:SELECT GREATEST(3, 12, 34, 8, 25); – 34返回以下字符串列表中的最大值:SELECT GREATEST(“Google”, “Runoob”, “Apple”); – Runoob
LEAST(expr1, expr2, expr3, …) 返回列表中的最小值 返回以下数字列表中的最小值:SELECT LEAST(3, 12, 34, 8, 25); – 3返回以下字符串列表中的最小值:SELECT LEAST(“Google”, “Runoob”, “Apple”); – Apple
-- 数学函数
SELECT ABS(-1); -- 1
-- 向上取整
SELECT CEIL(1.2); -- 2
SELECT CEIL(1.0); -- 1
-- 向下取整
SELECT FLOOR(1.9); -- 1
-- 求列表中的最大值
SELECT GREATEST(1,2,3); -- 3
-- 求列表中的最小值
SELECT LEAST(1,2,3); -- 1
函数名 描述 实例
MAX(expression) 返回字段 expression 中的最大值 返回数据表 Products 中字段 Price 的最大值:SELECT MAX(Price) AS LargestPrice FROM Products;
MIN(expression) 返回字段 expression 中的最小值 返回数据表 Products 中字段 Price 的最小值:SELECT MIN(Price) AS MinPrice FROM Products;
MOD(x,y) 返回 x 除以 y 以后的余数 5 除于 2 的余数:SELECT MOD(5,2) – 1
PI() 返回圆周率(3.141593) SELECT PI() --3.141593
POW(x,y) 返回 x 的 y 次方 2 的 3 次方:SELECT POW(2,3) – 8
-- 求最大值
SELECT MAX(salary) FROM emp; 
-- 求最小值
SELECT MIN(salary) FROM emp; 
-- 取余
SELECT MOD(5,2); -- 1
-- pi值
SELECT PI(); -- 3.14159
-- 次方
SELECT POWER(2,3); -- 8
函数名 描述 实例
RAND() 返回 0 到 1 的随机数 SELECT RAND() --0.93099315644334
ROUND(x) 返回离 x 最近的整数(遵循四舍五入) SELECT ROUND(1.23456) --1
ROUND(x,y) 返回指定位数的小数(遵循四舍五入) SELECT ROUND(1.23456,3) –1.235
TRUNCATE(x,y) 返回数值 x 保留到小数点后 y 位的值(与 ROUND 最大的区别是不会进行四舍五入) SELECT TRUNCATE(1.23456,3) – 1.234
-- 获取0-1的随机数
SELECT RAND();
-- 0-100之间的随机数(向上取整)
SELECT CEIL(RAND() * 100);
-- 四舍五入
SELECT ROUND(4.343);
-- 保留指定的小数位数,会四舍五入
SELECT ROUND(4.34543,2);
-- 保留指定为小数位数,不会四舍五入
SELECT TRUNCATE(4.34543,2);
2.6.6.3 字符串函数
函数 描述 实例
CHAR_LENGTH(s) 返回字符串 s 的字符数 返回字符串 RUNOOB 的字符数SELECT CHAR_LENGTH(“RUNOOB”) AS LengthOfString;
CHARACTER_LENGTH(s) 返回字符串 s 的字符数 返回字符串 RUNOOB 的字符数SELECT CHARACTER_LENGTH(“RUNOOB”) AS LengthOfString;
CONCAT(s1,s2…sn) 字符串 s1,s2 等多个字符串合并为一个字符串 合并多个字符串SELECT CONCAT("SQL ", "Runoob ", "Gooogle ", “Facebook”) AS ConcatenatedString;
CONCAT_WS(x, s1,s2…sn) 同 CONCAT(s1,s2,…) 函数,但是每个字符串之间要加上 x,x 可以是分隔符 合并多个字符串,并添加分隔符:SELECT CONCAT_WS("-", “SQL”, “Tutorial”, “is”, “fun!”)AS ConcatenatedString;
FIELD(s,s1,s2…) 返回第一个字符串 s 在字符串列表(s1,s2…)中的位置 返回字符串 c 在列表值中的位置:SELECT FIELD(“c”, “a”, “b”, “c”, “d”, “e”);
-- 返回字符串的长度
SELECT CHAR_LENGTH("zhangsan") len;
SELECT CHARACTER_LENGTH("RUNOOB") AS LengthOfString;
-- 将字符串进行拼接
SELECT CONCAT('hello','mysql');
-- 将字符串进行拼接,并使用指定的字符进行拼接
SELECT CONCAT_WS(',','hello','mysql')
-- 返回字符在指定的字符列表中的位置
SELECT FIELD("c", "a", "b", "c", "d", "e");
函数 描述 实例
LTRIM(s) 去掉字符串 s 开始处的空格 去掉字符串 RUNOOB开始处的空格:SELECT LTRIM(" RUNOOB") AS LeftTrimmedString;-- RUNOOB
MID(s,n,len) 从字符串 s 的 n 位置截取长度为 len 的子字符串,同 SUBSTRING(s,n,len) 从字符串 RUNOOB 中的第 2 个位置截取 3个 字符:SELECT MID(“RUNOOB”, 2, 3) AS ExtractString; – UNO
POSITION(s1 IN s) 从字符串 s 中获取 s1 的开始位置 返回字符串 abc 中 b 的位置:SELECT POSITION(‘b’ in ‘abc’) – 2
REPLACE(s,s1,s2) 将字符串 s2 替代字符串 s 中的字符串 s1 将字符串 abc 中的字符 a 替换为字符 x:SELECT REPLACE(‘abc’,‘a’,‘x’) --xbc
REVERSE(s) 将字符串s的顺序反过来 将字符串 abc 的顺序反过来:SELECT REVERSE(‘abc’) – cba
-- 去掉字符串开始处的空格
SELECT LTRIM('  hello');
-- 从字符串s的指定位置开始截取len长度,等同于subString
SELECT MID('hello world!', 3, 7);
-- 获取s在s1中的位置
SELECT POSITION('a' IN 'hdjshaf');
-- 让s2代替s中的s1
SELECT REPLACE('abc','a','x');
-- 进行字符串逆序
SELECT REVERSE('abc');
函数 描述 实例
RIGHT(s,n) 返回字符串 s 的后 n 个字符 返回字符串 runoob 的后两个字符:SELECT RIGHT(‘runoob’,2) – ob
RTRIM(s) 去掉字符串 s 结尾处的空格 去掉字符串 RUNOOB 的末尾空格:SELECT RTRIM("RUNOOB ") AS RightTrimmedString; – RUNOOB
STRCMP(s1,s2) 比较字符串 s1 和 s2,如果 s1 与 s2 相等返回 0 ,如果 s1>s2 返回 1,如果 s1 比较字符串:SELECT STRCMP(“runoob”, “runoob”); – 0
SUBSTR(s, start, length) 从字符串 s 的 start 位置截取长度为 length 的子字符串 从字符串 RUNOOB 中的第 2 个位置截取 3个 字符:SELECT SUBSTR(“RUNOOB”, 2, 3) AS ExtractString; – UNO
SUBSTRING(s, start, length) 从字符串 s 的 start 位置截取长度为 length 的子字符串 从字符串 RUNOOB 中的第 2 个位置截取 3个 字符:SELECT SUBSTRING(“RUNOOB”, 2, 3) AS ExtractString; – UNO
函数 描述 实例
TRIM(s) 去掉字符串 s 开始和结尾处的空格 去掉字符串 RUNOOB 的首尾空格:SELECT TRIM(’ RUNOOB ') AS TrimmedString;
UCASE(s) 将字符串转换为大写 将字符串 runoob 转换为大写:SELECT UCASE(“runoob”); – RUNOOB
UPPER(s) 将字符串转换为大写 将字符串 runoob 转换为大写:SELECT UPPER(“runoob”); – RUNOOB
LCASE(s)** ** 将字符串 s 的所有字母变成小写字母 字符串 RUNOOB 转换为小写:SELECT LCASE(‘RUNOOB’) – runoob
LOWER(s)** ** 将字符串 s 的所有字母变成小写字母 字符串 RUNOOB 转换为小写:SELECT LOWER(‘RUNOOB’) – runoob
2.6.6.4 日期函数
函数名 描述 实例
UNIX_TIMESTAMP() 返回从1970-01-01 00:00:00到当前毫秒值 select UNIX_TIMESTAMP() -> 1632729059
UNIX_TIMESTAMP(DATE_STRING) 将制定日期转为毫秒值时间戳 SELECT UNIX_TIMESTAMP(‘2011-12-07 13:01:03’);
FROM_UNIXTIME(BIGINT UNIXTIME[, STRING FORMAT]) 将毫秒值时间戳转为指定格式日期 SELECT FROM_UNIXTIME(1598079966,’%Y-%m-%d %H:%i:%s’); (1598079966,’%Y-%m-%d %H:%i:%s’); -> 2020-08-22 15-06-06
CURDATE() 返回当前日期 SELECT CURDATE();-> 2018-09-19
CURRENT_DATE() 返回当前日期 SELECT CURRENT_DATE();-> 2018-09-19
函数名 描述 实例
CURRENT_TIME 返回当前时间 SELECT CURRENT_TIME();-> 19:59:02
CURTIME() 返回当前时间 SELECT CURTIME();-> 19:59:02
CURRENT_TIMESTAMP() 返回当前日期和时间 SELECT CURRENT_TIMESTAMP()-> 2018-09-19 20:57:43
DATE() 从日期或日期时间表达式中提取日期值 SELECT DATE(“2017-06-15”); -> 2017-06-15
DATEDIFF(d1,d2) 计算日期 d1->d2 之间相隔的天数 SELECT DATEDIFF(‘2001-01-01’,‘2001-02-02’)-> -32
函数名 描述 实例
TIMEDIFF(time1, time2) 计算时间差值 SELECT TIMEDIFF(“13:10:11”, “13:10:10”);-> 00:00:01
DATE_FORMAT(d,f) 按表达式 f的要求显示日期 d SELECT DATE_FORMAT(‘2011-11-11 11:11:11’,’%Y-%m-%d %r’)-> 2011-11-11 11:11:11 AM
STR_TO_DATE(string, format_mask) 将字符串转变为日期 SELECT STR_TO_DATE(“August 10 2017”, “%M %d %Y”);-> 2017-08-10
DATE_SUB(date,INTERVAL expr type) 函数从日期减去指定的时间间隔。 Orders 表中 OrderDate 字段减去 2 天:SELECT OrderId,DATE_SUB(OrderDate,INTERVAL 2 DAY) AS OrderPayDateFROM Orders
函数名 描述 实例
ADDDATE/DATE_ADD(d,INTERVAL expr type) 计算起始日期 d 加上一个时间段后的日期,type 值可以是:
MICROSECOND
SECOND
MINUTE
HOUR
DAY
WEEK
MONTH
QUARTER
YEAR
DAY_MINUTE
DAY_HOUR
YEAR_MONTH SELECT DATE_ADD(“2017-06-15”, INTERVAL 10 DAY);
-> 2017-06-25

SELECT DATE_ADD(“2017-06-15 09:34:21”, INTERVAL 15 MINUTE);
-> 2017-06-15 09:49:21

SELECT DATE_ADD(“2017-06-15 09:34:21”, INTERVAL -3 HOUR);
->2017-06-15 06:34:21

SELECT DATE_ADD(“2017-06-15 09:34:21”, INTERVAL -3 HOUR);
->2017-04-15 |

EXTRACT(type FROM d) 从日期 d **中获取指定的值,type指定返回的值。**type可取值MICROSECONDSECONDMINUTE****HOUR SELECT EXTRACT(MINUTE FROM ‘2011-11-11 11:11:11’) -> 11
LAST_DAY(d) 返回给给定日期的那一月份的最后一天 SELECT LAST_DAY(“2017-06-20”);-> 2017-06-30
MAKEDATE(year, day-of-year) 基于给定参数年份 year 和所在年中的天数序号 day-of-year 返回一个日期 SELECT MAKEDATE(2017, 3);-> 2017-01-03
函数名 描述 实例
YEAR(d) 返回年份 SELECT YEAR(“2017-06-15”);-> 2017
MONTH(d) 返回日期d中的月份值,1 到 12 SELECT MONTH(‘2011-11-11 11:11:11’)->11
DAY(d) 返回日期值 d 的日期部分 SELECT DAY(“2017-06-15”); -> 15
HOUR(t) 返回 t 中的小时值 SELECT HOUR(‘1:2:3’)-> 1
MINUTE(t) 返回 t 中的分钟值 SELECT MINUTE(‘1:2:3’)-> 2
SECOND(t) 返回 t 中的秒钟值 SELECT SECOND(‘1:2:3’)-> 3
QUARTER(d) 返回日期d是第几季节,返回 1 到 4 SELECT QUARTER(‘2011-11-11 11:11:11’)-> 4
EXTRACT(type FROM d) 从日期 d 中获取指定的值,type指定返回的值。type可取值为:MICROSECONDSECONDMINUTEHOUR SELECT EXTRACT(MINUTE FROM ‘2011-11-11 11:11:11’) -> 11
LAST_DAY(d) 返回给给定日期的那一月份的最后一天 SELECT LAST_DAY(“2017-06-20”);-> 2017-06-30
MAKEDATE(year, day-of-year) 基于给定参数年份 year 和所在年中的天数序号 day-of-year 返回一个日期 SELECT MAKEDATE(2017, 3);-> 2017-01-03
函数名 描述 实例
YEAR(d) 返回年份 SELECT YEAR(“2017-06-15”);-> 2017
MONTH(d) 返回日期d中的月份值,1 到 12 SELECT MONTH(‘2011-11-11 11:11:11’)->11
DAY(d) 返回日期值 d 的日期部分 SELECT DAY(“2017-06-15”); -> 15
HOUR(t) 返回 t 中的小时值 SELECT HOUR(‘1:2:3’)-> 1
MINUTE(t) 返回 t 中的分钟值 SELECT MINUTE(‘1:2:3’)-> 2
SECOND(t) 返回 t 中的秒钟值 SELECT SECOND(‘1:2:3’)-> 3
QUARTER(d) 返回日期d是第几季节,返回 1 到 4 SELECT QUARTER(‘2011-11-11 11:11:11’)-> 4
函数名 描述 实例
MONTHNAME(d) 返回日期当中的月份名称,如 November SELECT MONTHNAME(‘2011-11-11 11:11:11’)-> November
MONTH(d) 返回日期d中的月份值,1 到 12 SELECT MONTH(‘2011-11-11 11:11:11’)->11
DAYNAME(d) 返回日期 d 是星期几,如 Monday,Tuesday SELECT DAYNAME(‘2011-11-11 11:11:11’)->Friday
DAYOFMONTH(d) 计算日期 d 是本月的第几天 SELECT DAYOFMONTH(‘2011-11-11 11:11:11’)->11
DAYOFWEEK(d) 日期 d 今天是星期几,1 星期日,2 星期一,以此类推 SELECT DAYOFWEEK(‘2011-11-11 11:11:11’)->6
DAYOFYEAR(d) 计算日期 d 是本年的第几天 SELECT DAYOFYEAR(‘2011-11-11 11:11:11’)->315
函数名 描述 实例
WEEK(d) 计算日期 d 是本年的第几个星期,范围是 0 到 53 SELECT WEEK(‘2011-11-11 11:11:11’)-> 45
WEEKDAY(d) 日期 d 是星期几,0 表示星期一,1 表示星期二 SELECT WEEKDAY(“2017-06-15”);-> 3
WEEKOFYEAR(d) 计算日期 d 是本年的第几个星期,范围是 0 到 53 SELECT WEEKOFYEAR(‘2011-11-11 11:11:11’)-> 45
YEARWEEK(date, mode) 返回年份及第几周(0到53),mode 中 0 表示周天,1表示周一,以此类推 SELECT YEARWEEK(“2017-06-15”);-> 201724
NOW() 返回当前日期和时间 SELECT NOW()-> 2018-09-19 20:57:43
2.6.6.5 控制流函数
格式 解释 案例
IF(expr,v1,v2) 如果表达式 expr 成立,返回结果 v1;否则,返回结果 v2。 SELECT IF(1 > 0,‘正确’,‘错误’) ->正确
IFNULL(v1,v2) 如果 v1 的值不为 NULL,则返回 v1,否则返回 v2。 SELECT IFNULL(null,‘Hello Word’)->Hello Word
ISNULL(expression) 判断表达式是否为 NULL SELECT ISNULL(NULL);->1
NULLIF(expr1, expr2) 比较两个字符串,如果字符串 expr1 与 expr2 相等 返回 NULL,否则返回 expr1 SELECT NULLIF(25, 25);->
SELECT IF(5 > 3, 1, 0) STATUS;
-- 会多生成一列status,判断工资的状态
SELECT *,IF(salary > 6500,'高薪','一般') STATUS FROM emp; 
-- 如果v1不为null,则返回v1,否则返回v2
SELECT IFNULL(NULL, 2); -- 2
SELECT IFNULL(1, 2); -- 1
-- 判断v1是否为null
SELECT ISNULL(2); -- 0
SELECT ISNULL(NULL); -- 1
-- 比较两个字符串,如果字符串 expr1 与 expr2 相等 返回 NULL,否则返回 expr1
SELECT NULLIF(1,1); -- null
SELECT NULLIF(1,2); -- 1
格式 解释 操作
CASE expression WHEN condition1 THEN result1 WHEN condition2 THEN result2 WHEN conditionN THEN resultN ELSE result****END CASE 表示函数开始,END 表示函数结束。如果 condition1 成立,则返回 result1, 如果 condition2 成立,则返回 result2,当全部不成立则返回 result,而当有一个成立之后,后面的就不执行了。 select case 100 when 50 then ‘tom’ when 100 then ‘mary’else ‘tim’ end ; select case when 1=2 then ‘tom’ when 2=2 then ‘mary’ else’tim’ end ;
-- case语句,类似于switch语句
SELECT
	CASE 5(表达式)
	WHEN 1 THEN '一般'
	WHEN 2 THEN '良好'
	WHEN 5 THEN '优秀'
	ELSE '较差'
	END AS STATUS;

-- 创建订单表
CREATE TABLE orders(
    oid INT PRIMARY KEY, -- 订单id
    price DOUBLE, -- 订单价格
    payType INT -- 支付类型(1:微信支付 2:支付宝支付 3:银行卡支付 4:其他)
);
INSERT INTO orders VALUES(1,1200,1);
INSERT INTO orders VALUES(2,1000,2);
INSERT INTO orders VALUES(3,200,3);
INSERT INTO orders VALUES(4,3000,1);
INSERT INTO orders VALUES(5,1500,2);

SELECT * FROM orders;
-- case默认也会生成一个新的列
SELECT 
*, 
	CASE payType
	WHEN 1 THEN '微信支付'
	WHEN 2 THEN '支付宝'
	WHEN 3 THEN '银行卡支付'
	ELSE '其他'
	END AS payTypeStr
FROM orders;

2.6.7 窗口函数
# MySQL 8.0 新增窗口函数,窗口函数又被称为开窗函数,与Oracle 窗口函数类似,属于MySQL的一大特点.

# 非聚合窗口函数是相对于聚函数来说的。聚合函数是对一组数据计算后返回单个值(即分组),非聚合函数一次只会处理一行数据。窗口聚合函数在行记录上计算某个字段的结果时,可将窗口范围内的数据输入到聚合函数中,并不改变行数。
  • 分类

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UUzpBYLM-1645196290622)(C:\Users\liumingshan3\AppData\Roaming\Typora\typora-user-images\image-20220105112154455.png)]

  • 另外还有开窗聚合函数: SUM,AVG,MIN,MAX

  • 语法结构

window_function ( expr ) OVER ( 
    PARTITION BY ... 
    ORDER BY ... 
    frame_clause 
)

-- 其中,window_function 是窗口函数的名称;expr 是参数,有些函数不需要参数;OVER子句包含三个选项:
-- 分区(PARTITION BY)
	-- PARTITION BY选项用于将数据行拆分成多个分区(组),它的作用类似于GROUP BY分组。如果省略了 PARTITION BY,所有的数据作为一个组进行计算

-- 排序(ORDER BY)
	-- OVER 子句中的ORDER BY选项用于指定分区内的排序方式,与 ORDER BY 子句的作用类似.

-- 以及窗口大小(frame_clause)。
	-- frame_clause选项用于在当前分区内指定一个计算窗口,也就是一个与当前行相关的数据子集。 
2.6.7.1 序号函数
  • 序号函数有三个:ROW_NUMBER()、RANK()、DENSE_RANK(),可以用来实现分组排序,并添加序号。
row_number()|rank()|dense_rank() over ( 
    partition by ... 
    order by ... 
) 

create table employee( 
    dname varchar(20), -- 部门名 
    eid varchar(20), 
    ename varchar(20), 
    hiredate date, -- 入职日期 
    salary double -- 薪资
); 

insert into employee values('研发部','1001','刘备','2021-11-01',3000);
insert into employee values('研发部','1002','关羽','2021-11-02',5000);
insert into employee values('研发部','1003','张飞','2021-11-03',7000);
insert into employee values('研发部','1004','赵云','2021-11-04',7000);
insert into employee values('研发部','1005','马超','2021-11-05',4000);
insert into employee values('研发部','1006','黄忠','2021-11-06',4000);

insert into employee values('销售部','1007','曹操','2021-11-01',2000);
insert into employee values('销售部','1008','许褚','2021-11-02',3000);
insert into employee values('销售部','1009','典韦','2021-11-03',5000);
insert into employee values('销售部','1010','张辽','2021-11-04',6000);
insert into employee values('销售部','1011','徐晃','2021-11-05',9000);
insert into employee values('销售部','1012','曹洪','2021-11-06',6000);

-- 1.对每个部门的员工按照薪资排序,并给出排名
-- 三个的区别有点大,
SELECT dname,ename,salary,
-- 不会出现并列的1,只会从1-n进行排序
ROW_NUMBER() OVER(PARTITION BY dname ORDER BY salary DESC) AS rn1,
-- 会出现并列的第一,比如有两个1,下一个从3开始
RANK() OVER(PARTITION BY dname ORDER BY salary DESC) AS rn2,
-- 会出现并列的第一,比如有两个1,下一个会从2开始
DENSE_RANK() OVER(PARTITION BY dname ORDER BY salary DESC) AS rn3
FROM employee;


-- 2.求出每个部门薪资排在前三名的员工- 分组求TOPN
SELECT * 
FROM 
(
    SELECT dname,ename,salary,
    DENSE_RANK() OVER(PARTITION BY dname ORDER BY salary DESC) AS rn
    FROM employee
) t
WHERE t.rn <= 3;


-- 3.对所有员工进行全局排序(不分组)
-- 不加partition by表示全局排序
SELECT 
     dname,
     ename,
     salary,
     DENSE_RANK() OVER( ORDER BY salary DESC)  AS rn
FROM employee;
2.6.7.2 开窗聚合函数- SUM,AVG,MIN,MAX
# 在窗口中每条记录动态地应用聚合函数(SUM()、AVG()、MAX()、MIN()、COUNT()),可以动态计算在指定的窗口内的各种聚合函数值。

-- 1.从上到下累加每个部门所有的工资
select  
 dname,
 ename,
 salary,
 sum(salary) over(partition by dname order by hiredate) as pv1 
from employee;


-- 2.如果没有order  by排序语句  默认把分组内的所有数据进行sum操作
SELECT  
 dname,
 ename,
 salary,
 SUM(salary) OVER(PARTITION BY dname) AS pv1 
FROM employee;
2.6.7.3 分布函数- CUME_DIST和PERCENT_RANK
  • CUME_DIST()
-- 用途:分组内小于、等于当前rank值的行数 / 分组内总行数
-- 应用场景:查询小于等于当前薪资(salary)的比例
SELECT  
 dname,
 ename,
 salary,
 CUME_DIST() OVER(ORDER BY salary) AS rn1, -- 没有partition语句 所有的数据位于一组
 CUME_DIST() OVER(PARTITION BY dname ORDER BY salary) AS rn2 
FROM employee;

/*
rn1: 没有partition,所有数据均为1组,总行数为12,
     第一行:小于等于3000的行数为3,因此,3/12=0.25
     第二行:小于等于4000的行数为5,因此,5/12=0.4166666666666667
rn2: 按照部门分组,dname='研发部'的行数为6,
     第一行:研发部小于等于3000的行数为1,因此,1/6=0.16666666666666666
*/
  • PERCENT_RANK()
-- 用途:每行按照公式(rank-1) / (rows-1)进行计算。其中,rank为RANK()函数产生的序号,rows为当前窗口的记录总行数
-- 应用场景:不常用
select 
 dname,
 ename,
 salary,
 rank() over(partition by dname order by salary desc ) as rn,
 percent_rank() over(partition by dname order by salary desc ) as rn2
from employee;
2.6.7.4 前后函数-LAG和LEAD
-- 用途:返回位于当前行的前n行(LAG(expr,n))或后n行(LEAD(expr,n))的expr的值
-- 应用场景:查询前1名同学的成绩和当前同学成绩的差值
-- lag的用法
select 
 dname,
 ename,
 hiredate,
 salary,
 lag(hiredate,1,'2000-01-01') over(partition by dname order by hiredate) as last_1_time,
 lag(hiredate,2) over(partition by dname order by hiredate) as last_2_time 
from employee;
/*
last_1_time: 指定了往上第1行的值,default为'2000-01-01'  
                         第一行,往上1行为null,因此取默认值 '2000-01-01'
                         第二行,往上1行值为第一行值,2021-11-01 
                         第三行,往上1行值为第二行值,2021-11-02 
last_2_time: 指定了往上第2行的值,为指定默认值
                         第一行,往上2行为null
                         第二行,往上2行为null
                         第四行,往上2行为第二行值,2021-11-01 
                         第七行,往上2行为第五行值,2021-11-02 
*/


-- lead的用法
select 
 dname,
 ename,
 hiredate,
 salary,
 lead(hiredate,1,'2000-01-01') over(partition by dname order by hiredate) as last_1_time,
 lead(hiredate,2) over(partition by dname order by hiredate) as last_2_time 
from employee;
2.6.7.5 头尾函数-FIRS和LAST_VALUE
-- 用途:返回第一个(FIRST_VALUE(expr))或最后一个(LAST_VALUE(expr))expr的值
-- 应用场景:截止到当前,按照日期排序查询第1个入职和最后1个入职员工的薪资
-- 注意,  如果不指定ORDER BY,则进行排序混乱,会出现错误的结果
select
  dname,
  ename,
  hiredate,
  salary,
  first_value(salary) over(partition by dname order by hiredate) as first,
  last_value(salary) over(partition by dname order by  hiredate) as last 
from  employee;

2.6.7.6 其他函数-NTH_VALUE(expr, n)、NTILE(n)
-- 用途:返回窗口中第n个expr的值。expr可以是表达式,也可以是列名
-- 应用场景:截止到当前薪资,显示每个员工的薪资中排名第2或者第3的薪资

-- 查询每个部门截止目前薪资排在第二和第三的员工信息
select 
  dname,
  ename,
  hiredate,
  salary,
  nth_value(salary,2) over(partition by dname order by hiredate) as second_score,
  nth_value(salary,3) over(partition by dname order by hiredate) as third_score
from employee


-- 用途:将分区中的有序数据分为n个等级,记录等级数
-- 应用场景:将每个部门员工按照入职日期分成3组
-- 根据入职日期将每个部门的员工分成3组
select 
  dname,
  ename,
  hiredate,
  salary,
ntile(3) over(partition by dname order by  hiredate  ) as rn 
from employee;

-- 取出每个部门的第一组员工
select
*
from
(
    SELECT 
    dname,
    ename,
    hiredate,
    salary,
    NTILE(3) OVER(PARTITION BY dname ORDER BY  hiredate  ) AS rn 
    FROM employee
)t
where t.rn = 1;

3. 索引

  • 索引是存储引擎用来快速查找记录的一种数据结构

3.1 索引的分类

  • 按照实现方式:主要有Hash索引B+Tree索引和BTree索引等

  • 按照功能进行划分:

3.2 单列索引

  • 一个索引只包含单个列,但一个表中可以有多个单列索引;
3.2.1普通索引
  • MySQL中基本索引类型,没有什么限制,允许在定义索引的列中插入重复值和空值,纯粹为了查询数据更快一点。

  • 创建实现

-- 方式1-创建表的时候直接指定
create  table student(
    sid int primary key,
    card_id varchar(20),
    name varchar(20),
    gender varchar(20),
    age int,
    birth date, 
    phone_num varchar(20),
    score double,
    index index_name(name) -- 给name列创建索引
);

-- 方式2-直接创建
-- create index 索引名字 on 数据表(字段名); 
create index index_gender on student(gender); 

-- 方式3-修改表结构(添加索引)
-- alter table 数据表 add index 索引名(字段名)
alter table student add index index_age(age);
3.2.1.1 查询删除索引
-- 1、查看数据库所有索引 
-- show index from table_name; 
show index from student;

-- 2.删除表中的索引
drop index 索引名 on 表名 
-- 或
alter table 表名 drop index 索引名 
3.2.2 唯一索引
  • 唯一索引与前面的普通索引类似,不同的就是:索引列的值必须唯一,但允许有空值。如果是组合索引,则列值的组合必须唯一。它有以下几种创建方式:
-- 方式1-创建表的时候直接指定
create  table student2(
    sid int primary key,
    card_id varchar(20),
    name varchar(20),
    gender varchar(20),
    age int,
    birth date, 
    phone_num varchar(20),
    score double,
    unique index_card_id(card_id) -- 给card_id列创建索引
);
-- 方式2-直接创建
-- create unique index 索引名 on 表名(列名) 
create unique index index_card_id on student2(card_id);

-- 方式3-修改表结构(添加索引)
-- alter table 表名 add unique [索引名] (列名)
alter table student2 add unique index_phone_num(phone_num)
3.2.3 主键索引
  • 每张表一般都会有自己的主键,当我们在创建表时,MySQL会自动在主键列上建立一个索引,这就是主键索引。主键是具有唯一性并且不允许为NULL,所以他是一种特殊的唯一索引。

3.3 组合索引

  • 组合索引也叫复合索引,指的是我们在建立索引的时候使用多个字段,例如同时使用身份证和手机号建立索引,同样的可以建立为普通索引或者是唯一索引。
  • 复合索引的使用复合最左原则。
-- 创建组合索引的格式
create index indexname on table_name(column1(length),column2(length)); 
-- 创建索引的基本语法-- 普通索引
-- create index indexname on table_name(column1(length),column2(length)); 
create index index_phone_name on student(phone_num,name);
-- 操作-删除索引
 drop index index_phone_name on student; 
-- 创建索引的基本语法-- 唯一索引
create  unique index index_phone_name on student(phone_num,name);
select * from student where name = '张三';   # 不符合最左原则
select * from student where phone_num = '15100046637'; 
select * from student where phone_num = '15100046637' and name = '张三'; 
select * from student where name = '张三' and phone_num = '15100046637'; 
/* 
  三条sql只有 2 、 3、4能使用的到索引idx_phone_name,因为条件里面必须包含索引前面的字段  才能够进行匹配。
  而3和4相比where条件的顺序不一样,为什么4可以用到索引呢?是因为mysql本身就有一层sql优化,他会根据sql来识别出来该用哪个索引,我们可以理解为3和4在mysql眼中是等价的。 
*/

3.4 全文索引

- 全文索引的关键字是fulltext

- 全文索引主要用来查找文本中的关键字,而不是直接与索引中的值相比较,它更像是一个搜索引擎,基于相似度的查询,而不是简单的where语句的参数匹配。

- 用 like + % 就可以实现模糊匹配了,为什么还要全文索引?like + % 在文本比较少时是合适的,但是对于大量的文本数据检索,是不可想象的。全文索引在大量的数据面前,能比 like + % 快 N 倍,速度不是一个数量级,但是全文索引可能存在精度问题。
- 全文索引的版本、存储引擎、数据类型的支持情况:
- MySQL 5.6 以前的版本,只有 MyISAM 存储引擎支持全文索引;
- MySQL 5.6 及以后的版本,MyISAM 和 InnoDB 存储引擎均支持全文索引;
- 只有字段的数据类型为 char、varchar、text 及其系列才可以建全文索引;
- 在数据量较大时候,现将数据放入一个没有全局索引的表中,然后再用create index创建fulltext索引,要比先为一张表建立fulltext然后再将数据写入的速度快很多;
- 测试或使用全文索引时,要先看一下自己的 MySQL 版本、存储引擎和数据类型是否支持全文索引。
-- MySQL 中的全文索引,有两个变量,最小搜索长度和最大搜索长度,对于长度小于最小搜索长度和大于最大搜索长度的词语,都不会被索引。通俗点就是说,想对一个词语使用全文索引搜索,那么这个词语的长度必须在以上两个变量的区间内。这两个的默认值可以使用以下命令查看:
SHOW VARIABLES LIKE '%ft%';

-- 可以看到mysql5.6之前的版本的全文索引的长度介于 4 -- 84 之间
-- mysql5.6以后的使用的是长度介于 3 - 84 之间,超出或者小于这个长度的区间都会无法被进行全文索引。
  • 参数解释
# 参数名称 默认值 最小值 最大值 作用
1 ft_min_word_len 4 1 3600 MyISAM 引擎表全文索引包含的最小词长度
2 ft_query_expansion_limit 20 0 1000 MyISAM引擎表使用 with query expansion 进行全文搜索的最大匹配数
3 innodb_ft_min_token_size 3 0 16 InnoDB 引擎表全文索引包含的最小词长度
4 innodb_ft_max_token_size 84 10 84 InnoDB 引擎表全文索引包含的最大词长度
-- 创建全文的方式
-- 创建表的时候添加全文索引
create table t_article (
     id int primary key auto_increment ,
     title varchar(255) ,
     content varchar(1000) ,
     writing_date date -- , 
     -- fulltext (content) -- 创建全文检索 方式1,创建的时候直接进行指定即可
);

-- 方式2,3
-- 修改表结构添加全文索引
alter table t_article add fulltext index_content(content)
 
-- 直接添加全文索引
create fulltext index index_content on t_article(content);
  • 使用全文索引
-- 使用全文索引
-- 和常用的模糊匹配使用 like + % 不同,全文索引有自己的语法格式,使用 match 和 against 关键字,格式:(放在where之后)
match (col1,col2,...) against(expr [search_modifier])

-- 例子
select * from t_article where match(content) against('yo’); -- 没有结果 单词数需要大于等于3 
select * from t_article where match(content) against('you'); -- 有结果

3.5 空间索引

- MySQL在5.7之后的版本支持了空间索引,而且支持OpenGIS几何数据模型
- 空间索引是对空间数据类型的字段建立的索引,MYSQL中的空间数据类型有4种,分别是GEOMETRY、POINT、LINESTRING、POLYGON。
- MYSQL使用SPATIAL关键字进行扩展,使得能够用于创建正规索引类型的语法创建空间索引。
- 创建空间索引的列,必须将其声明为NOT NULL。
类型 含义 说明
Geometry 空间数据 任何一种空间类型
Point 坐标值
LineString 线 有一系列点连接而成
Polygon 多边形 由多条线组成
create table shop_info (
    id  int  primary key auto_increment comment 'id',
    shop_name varchar(64) not null comment '门店名称',
    geom_point geometry not null comment '经纬度’,
    spatial key geom_index(geom_point)  # spatial用于指定关键字
);

3.6 索引的原理(算法)

  • hash
hash
  • 二叉树
tree
  • 平衡二叉树
batree
  • B-Tree,B+Tree
btree
  • myisam
myisam
  • innodb
innodn

3.7 索引的优缺点

3.7.1 索引的优点
- 大大加快数据的查询速度

- 使用分组和排序进行数据查询时,可以显著减少查询时分组和排序的时间

- 创建唯一索引,能够保证数据库表中每一行数据的唯一性

- 在实现数据的参考完整性方面,可以加速表和表之间的连接
3.7.2 索引的缺点
- 创建索引和维护索引需要消耗时间,并且随着数据量的增加,时间也会增加

- 索引需要占据磁盘空间

- 对数据表中的数据进行增加,修改,删除时,索引也要动态的维护,降低了维护的速度
3.7.3 创建索引的原则
- 更新频繁的列不应设置索引

- 数据量小的表不要使用索引(毕竟总共2页的文档,还要目录吗?)

- 重复数据多的字段不应设为索引(比如性别,只有男和女,一般来说:重复的数据超过百分之15就不该建索引)

- 首先应该考虑对where 和 order by 涉及的列上建立索引

4.存储引擎

4.1 InnoDB 和 MyISAM的区别

engine

4.2 存储引擎相关操作

-- 查询当前数据库支持的存储引擎:
show engines;
 
-- 查看当前的默认存储引擎:
show variables like ‘%storage_engine%’;

-- 查看某个表用了什么引擎(在显示结果里参数engine后面的就表示该表当前用的存储引擎): 
show create table student; 
 
-- 创建新表时指定存储引擎:
create table(...) engine=MyISAM;
 
-- 修改数据库引擎
alter table student engine = INNODB;
alter table student engine = MyISAM;
- 修改MySQL默认存储引擎方法
1. 关闭mysql服务 
2. 找到mysql安装目录下的my.ini文件: 
3. 找到default-storage-engine=INNODB 改为目标引擎,
   如:default-storage-engine=MYISAM 
4. 启动mysql服务

5. 事务

5.1 事务操作

- 1、开启事务:Start Transaction

- 任何一条DML语句(insert、update、delete)执行,标志事务的开启
- 命令:BEGIN 或 START TRANSACTION 

- 2、提交事务:Commit Transaction
- 成功的结束,将所有的DML语句操作历史记录和底层硬盘数据来一次同步
- 命令:COMMIT

- 3、回滚事务:Rollback Transaction
- 失败的结束,将所有的DML语句操作历史记录全部清空
- 命令:ROLLBACK 
-- 设置事务的操作
set autocommit=0  -- 禁止自动提交 
set autocommit=1  -- 开启自动提交 


-- 设置MySQL的事务为手动提交(关闭自动提交)
-- 查看事务的状态
select @@autocommit;
set autocommit = 0;
 
-- 模拟账户转账
-- 开启事务 
begin;
update account set money = money - 200 where name = 'zhangsan';
update account set money = money + 200 where name = 'lisi';
-- 提交事务
commit;
  
-- 如果转账中的任何一条出现问题,则回滚事务
rollback;

5.2 事务的特性

image-20211220100824140

5.3 事务的隔离级别

5.3.1 概念
  • Isolate,顾名思义就是将事务与另一个事务隔离开,为什么要隔离呢?如果一个事务正在操作的数据被另一个事务修改或删除了,最后的执行结果可能无法达到预期。如果没有隔离性还会导致其他问题。
image-20211220101531000
1. 读未提交(Read uncommitted)
   -- 一个事务可以读取另一个未提交事务的数据,最低级别,任何情况都无法保证,会造成脏读。
    
2. 读已提交(Read committed)
   -- 一个事务要等另一个事务提交后才能读取数据,可避免脏读的发生,会造成不可重复读。

3. 可重复读(Repeatable read)
   -- 就是在开始读取数据(事务开启)时,不再允许修改操作,可避免脏读、不可重复读的发生,但是会造成幻读。这是mysql默认使用的隔离级别。

4. 序列化(Serializable)
   -- 是最高的事务隔离级别,在该级别下,事务串行化顺序执行,可以避免脏读、不可重复读与幻读。但是这种事务隔离级别效率低下,比较耗数据库性能,一般不使用。
image-20211220102434144
5.3.2 隔离界别的操作
-- 查看隔离级别 
show variables like '%isolation%’; 

-- 设置隔离级别
/*
set session transaction isolation level 级别字符串
级别字符串:read uncommitted、read committed、repeatable read、serializable
*/
-- 设置读未提交:read uncommitted
-- 会引起脏读,A事务会读取到B事务中没有提交的数据。
set session transaction isolation level read uncommitted;
 
-- 设置读已提交:read committed
-- 会引起不可重复读,A事务在没有提交事务之前,看到的数据不一致
set session transaction isolation level read committed;
 
-- 设置可重复读:repeatable read
-- 会引起可幻读,A事务在提交之前和提交之后,看到的数据不一致
set session transaction isolation level repeatable read;
 
-- 设置序列化:serializable
-- 该隔离级别比较安全,但是效率低,A事务在提交之前和提交之后,看到的数据不一致
set session transaction isolation level serializable;

6. 锁机制

  • 锁是计算机协调多个进程或线程并发访问某一资源的机制(避免争抢)。

  • 在数据库中,除传统的计算资源(如 CPU、RAM、I/O 等)的争用以外,数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。从这个角度来说,锁对数据库而言显得尤其重要,也更加复杂。

  • 从对数据操作的粒度分
- 1)表锁:操作时,会锁定整个表。

- 2)行锁:操作时,会锁定当前操作行。
  • 从对数据操作的类型分
- 1) 读锁(共享锁):针对同一份数据,多个读操作可以同时进行而不会互相影响。

- 2) 写锁(排它锁):当前操作没有完成之前,它会阻断其他写锁和读锁。
  • 不同存储引擎的锁机制
image-20211220141151917
  • MySQL锁的特性可大致归纳如下 :

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2AuaYt0o-1645196290623)(C:\Users\liumingshan3\AppData\Roaming\Typora\typora-user-images\image-20211220141240887.png)]

- 从上述特点可见,很难笼统地说哪种锁更好,只能就具体应用的特点来说哪种锁更合适!仅从锁的角度来说:表级锁更适合于以查询为主,只有少量按索引条件更新数据的应用,如Web 应用;

- 而行级锁则更适合于有大量按索引条件并发更新少量不同数据,同时又有并查询的应用,如一些在线事务处理(OLTP)系统。

6.1 MyISAM

  • 该存储引擎只支持表锁

  • 如何加表锁?

- MyISAM 在执行查询语句(SELECT)前,会自动给涉及的所有表加读锁,在执行更新操作(UPDATE、DELETE、INSERT 等)前,会自动给涉及的表加写锁,这个过程并不需要用户干预,因此,用户一般不需要直接用 LOCK TABLE 命令给 MyISAM 表显式加锁。
-- 显示添加锁
-- 加读锁:只能进行查询,不可以及逆行增删改的操作,可以添加多个读锁
-- 一旦给某个表添加了读锁之后,没解锁之前,不能再读其他的表
lock table table_name[表名] read; 

-- 加写锁:只能添加一个写锁
lock table table_name[表名] write;
6.1.1 表锁特点
- 1) 对MyISAM 表的读操作,不会阻塞其他用户对同一表的读请求,但会阻塞对同一表的写请求(不支持insert,update,delete的操作);

- 2) 对MyISAM 表的写操作,则会阻塞其他用户对同一表的读和写操作;

- 简而言之,就是读锁会阻塞写,但是不会阻塞读。而写锁,则既会阻塞读,又会阻塞写。
- 此外,MyISAM 的读写锁调度是写优先,这也是MyISAM不适合做写为主的表的存储引擎的原因。因为写锁后,其他线程不能做任何操作,大量的更新会使查询很难得到锁,从而造成永远阻塞。
-- 给表添加读锁
LOCK TABLE ACCOUNT READ;
SELECT * FROM ACCOUNT;
-- 解锁
UNLOCK TABLES;
-- 如果为解锁,更新操作将会失败
UPDATE ACCOUNT SET money = 100 WHERE id=1;
SELECT * FROM book;

-- 给表添加写锁
LOCK TABLE ACCOUNT WRITE;

-- 可以查询
SELECT * FROM ACCOUNT;

-- 添加写锁
LOCK TABLE book WRITE;
UNLOCK TABLES;

6.2 InnoDB

  • 支持行锁

  • 行锁的特点

- 行锁特点:偏向InnoDB存储引擎,开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。

- InnoDB 与 MyISAM 的最大不同有两点:一是 支持事务;二是 采用了行级锁。
  • 行锁的模式
InnoDB  实现了以下两种类型的行锁。

- 共享锁(S):又称为读锁,简称S锁,共享锁就是多个事务对于同一数据可以共享一把锁,都能访问到数据,但是只能读不能修改。

- 排他锁(X):又称为写锁,简称X锁,排他锁就是不能与其他锁并存,如一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁,包括共享锁和排他锁,但是获取排他锁的事务是可以对数据就行读取和修改。

- 对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及数据集加排他锁(X);
- 对于普通SELECT语句,InnoDB不会加任何锁;
  • 可以通过以下语句显示给记录集加共享锁或排他锁
-- 共享锁(S):
SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE 

-- 排他锁(X) :
SELECT * FROM table_name WHERE ... FOR UPDATE

7. 日记

  • 日记分类
- 错误日志
- 二进制日志
- 查询日志
- 慢查询日志
  • 查看mysql的日记信息
- 使用该语句可以查看到日记文件存放的位置
show variables like 'log_error%';

7.1 错误日记

- 错误日志是 MySQL 中最重要的日志之一,它记录了当 mysqld 启动和停止时,以及服务器在运行过程中发生任何严重错误时的相关信息。当数据库出现任何故障导致无法正常使用时,可以首先查看此日志。

- 该日志是默认开启的,默认存放目录为 mysql 的数据目录, 默认的日志文件名为 hostname.err(hostname是主机名)。

7.2 二进制日记 – binlog

- 二进制日志(BINLOG)记录了所有的 DDL(数据定义语言)语句和 DML(数据操纵语言)语句,但是不包括数据查询语句。此日志对于灾难时的数据恢复起着极其重要的作用,MySQL的主从复制,就是通过该binlog实现的。

- 二进制日志,MySQL8.0默认已经开启,低版本的MySQL的需要通过配置文件开启,并配置MySQL日志的格式。

- Windows系统:my.ini    Linux系统:my.cnf 
# 配置开启binlog日志, 日志的文件前缀为 mysqlbin -----> 生成的文件名如 : mysqlbin.000001,mysqlbin.000002
log_bin=mysqlbin

# 配置二进制日志的格式
binlog_format=STATEMENT
  • 日记格式的3中分类

    # STATEMENT
    	该日志格式在日志文件中记录的都是SQL语句(statement),每一条对数据进行修改的SQL都会记录在日志文件中,通过Mysql提供的mysqlbinlog工具,可以清晰的查看到每条语句的文本。主从复制的时候,实际就是:从库(slave)会将日志解析为原文本,并在从库重新执行一次。
    
    # ROW
    	该日志格式在日志文件中记录的是每一行的数据变更,而不是记录SQL语句。比如,执行SQL语句 : update tb_book set status='1' , 如果是STATEMENT 日志格式,在日志中会记录一行SQL文件; 如果是ROW,由于是对全表进行更新,也就是每一行记录都会发生变更,ROW 格式的日志中会记录每一行的数据变更。
    
    # MIXED
    	混合了STATEMENT 和 ROW两种格式。
    
  • 操作

-- 查看mysql是否开启了binlog日记
SHOW VARIABLES LIKE 'log_bin'

-- 查看mysql的binlog日记的格式
SHOW VARIABLES LIKE 'binlog_format'

-- 查看所有的日记
SHOW BINLOG EVENTS;

-- 查看最新的binlog日记文件
SHOW MASTER STATUS;

-- 查看指定的binlog日记文件信息
SHOW BINLOG EVENTS IN 'ZB-PF34PJPN-bin.000010';

-- 查看指定binlog中的从指定位置开始的日记文件
SHOW BINLOG EVENTS IN 'ZB-PF34PJPN-bin.000010' FROM 448;

-- 查看指定binlog中的从指定位置开始的日记文件,查询几条数据
SHOW BINLOG EVENTS IN 'ZB-PF34PJPN-bin.000010' FROM 448 LIMIT 4;

-- 查看指定binlog中的从指定位置开始的日记文件,跳过前几条,查询几条数据
SHOW BINLOG EVENTS IN 'ZB-PF34PJPN-bin.000010' FROM 448 LIMIT 4, 3;

-- 清空所有的 binlog 日志文件
reset master

7.3 查询日记

# 查询日志中记录了客户端的所有操作语句,而二进制日志不包含查询数据的SQL语句。

# 默认情况下, 查询日志是未开启的。如果需要开启查询日志,可以设置以下配置:
	# 该选项用来开启查询日志,选值 : 0 或者 1 ; 0 代表关闭, 1 代表开启 
	general_log=1

	# 设置日志的文件名,如果没有指定,默认的文件名为 host_name.log 
	general_log_file=file_name
-- 查看MySQL是否开启了查询日志
SHOW VARIABLES LIKE 'general_log';

-- 开启查询日志
SET GLOBAL general_log=1;

7.4 慢日记查询

# 慢查询日志记录了所有执行时间超过参数 long_query_time 设置值并且扫描记录数不小于 min_examined_row_limit 的所有的SQL语句的日志。long_query_time 默认为 10 秒,最小为 0, 精度可以到微秒。
# 该参数用来控制慢查询日志是否开启, 可取值: 1 和 0 , 1 代表开启, 0 代表关闭
slow_query_log=1

# 该参数用来指定慢查询日志的文件名
slow_query_log_file=slow_query.log

# 该选项用来配置查询的时间限制, 超过这个时间将认为值慢查询, 将需要进行日志记录, 默认10s
long_query_time=10

-- 查询语句
-- 慢日记查询
-- 查看慢日记的相关信息
SHOW VARIABLES LIKE 'slow_query_log%'

-- 开启慢日记查询
SET slow_query_log = 1;

-- 查看慢日记查询的超时时间
SHOW VARIABLES LIKE 'long_query_time%';

SELECT SLEEP(10);

8. MySQL的优化

  • 概念
# 在应用的的开发过程中,由于初期数据量小,开发人员写 SQL 语句时更重视功能上的实现,但是当应用系统正式上线后,随着生产数据量的急剧增长,很多 SQL 语句开始逐渐显露出性能问题,对生产的影响也越来越大,此时这些有问题的 SQL 语句就成为整个系统性能的瓶颈,因此我们必须要对它们进行优化.
  • 优化的方式
# 从设计上优化

# 从查询上优化

# 从索引上优化

# 从存储上优化

8.1 查看MYSQL的执行效率

# MySQL 客户端连接成功后,通过 show [session|global] status 命令可以查看服务器状态信息。通过查看状态信息可以查看对当前数据库的主要操作类型。
-- 下面的命令显示了当前 session 中所有统计参数的值(7个下划线)
 -- 查看当前会话统计结果(也就是查询当前会话对sql数据库的操作汇总)
show session status like 'Com_______'; 

-- 查看自数据库上次启动至今的操作统计结果
show global  status  like 'Com_______'; 

-- 查看针对Innodb引擎的操作统计结果
show status like 'Innodb_rows_%’;       
image-20211221171212066

8.2 定位低效率执行SQL

# 慢查询日志 : 通过慢查询日志定位那些执行效率较低的 SQL 语句。
- show variables like '%slow_query_log%';

# show processlist:该命令可以查看当前MySQL正在进行的线程,包括线程的状态、是否锁表等,可以实时地查看 SQL 的执行情况,同时对一些锁表操作进行优化。
- show processlist;
-- 查看慢日志配置信息 
show variables like '%slow_query_log%’; 

-- 开启慢日志查询(可以使用上述的命令查询慢日记是否已经开启)
set global slow_query_log=1; 

-- 查看慢日志记录SQL的最低阈值时间(默认是 >= 10s 的sql语句将会别记录到慢日记当中,前提得先开启) 
show variables like 'long_query_time%’; 

-- 修改慢日志记录SQL的最低阈值时间 
set global long_query_time=4;
  • 使用 show processlist 进行查询

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Rss7Q0iS-1645196290624)(C:\Users\liumingshan3\AppData\Roaming\Typora\typora-user-images\image-20211221172523596.png)]

- 1) id列,用户登录mysql时,系统分配的"connection_id",可以使用函数connection_id()查看

- 2) user列,显示当前用户。如果不是root,这个命令就只显示用户权限范围的sql语句

- 3) host列,显示这个语句是从哪个ip的哪个端口上发的,可以用来跟踪出现问题语句的用户

- 4) db列,显示这个进程目前连接的是哪个数据库

- 5) command列,显示当前连接的执行的命令,一般取值为休眠(sleep),查询(query),连接(connect)等

- 6) time列,显示这个状态持续的时间,单位是秒

- 7) state列,显示使用当前连接的sql语句的状态,很重要的列。state描述的是语句执行中的某一个状态。一个sql语句,以查询为例,可能需要经过copying to tmp table、sorting result、sending data等状态才可以完成

- 8) info列,显示这个sql语句,是判断问题语句的一个重要依据
  • 那么使用上述的两个方式查询到执行效率比较低的SQL语句之后,那为什么它执行效率低,就可以使用Explain关键字进行分析SQL语句执行效率低的原因。

8.3 explain 分析执行计划

# 通过以上步骤查询到效率低的 SQL 语句后,那为什么效率比较低呢? 这时就可以通过 EXPLAIN 命令获取 MySQL是如何执行 SELECT 语句的信息,包括在 SELECT 语句执行过程中表如何连接和连接的顺序,从而得到MYSQL执行效率低下的原因,知道了原因,我们就可以对症下药,针对出现的问题进行修改MYSQL语句,从而提高MYSQL的执行效率。

  • 有索引

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9Vcgq3zp-1645196290624)(C:\Users\liumingshan3\AppData\Roaming\Typora\typora-user-images\image-20211221175219616.png)]

  • 无索引

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vv6OkffT-1645196290625)(C:\Users\liumingshan3\AppData\Roaming\Typora\typora-user-images\image-20211221175408386.png)]

  • 字段解析

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-upyvdgPG-1645196290625)(C:\Users\liumingshan3\AppData\Roaming\Typora\typora-user-images\image-20211221175431291.png)]

8.3.1 Explain分析执行计划 **-- **Explain 之 id
  • id 字段是 select查询的序列号,是一组数字,表示的是查询中执行select子句或者是操作表的顺序。
  • id 情况有三种:
# 1、id 相同表示加载表的顺序是从上到下
SELECT * FROM t_user u, t_role r, t_user_role ur WHERE u.id = ur.userid AND r.id = ur.roleid;
  • 结果显示

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ro1Sebaw-1645196290626)(C:\Users\liumingshan3\AppData\Roaming\Typora\typora-user-images\image-20211221180940936.png)]

# 2、id 不同id值越大,优先级越高,越先被执行。 
# 查询zhangsan拥有的角色
EXPLAIN SELECT * FROM t_role WHERE id IN (
	SELECT roleid FROM t_user_role WHERE userid=(
		SELECT id FROM t_user WHERE username = 'zhangsan' 
	)
)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DPh3ZfhA-1645196290626)(C:\Users\liumingshan3\AppData\Roaming\Typora\typora-user-images\image-20211221181914514.png)]

# 3、id 有相同,也有不同,同时存在。id相同的可以认为是一组,从上往下顺序执行;在所有的组中,id的值越大,优先级越高,越先执行。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AEyMI52I-1645196290627)(C:\Users\liumingshan3\AppData\Roaming\Typora\typora-user-images\image-20211221181914514.png)]

8.3.2 Explain分析执行计划-Explain之 select_type
  • select的可能取值

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WCcPHS79-1645196290627)(C:\Users\liumingshan3\AppData\Roaming\Typora\typora-user-images\image-20211221182626414.png)]

# 1、简单的select查询,查询中不包含子查询或者union
EXPLAIN SELECT * FROM t_user;

# 2、查询中包含任何复杂的子查询,最外层查询标识为该标志
# 3、在select或者where列表中包含了子查询
# t_user_role 标识为primary(主查询),t_user表示为subQuery(子查询)
# id 数值越大,越优先被加载
EXPLAIN SELECT roleid FROM t_user_role WHERE userid=(
		SELECT id FROM t_user WHERE username = 'zhangsan' )

# 4、derived 在from中包含子查询,理解为临时表
EXPLAIN SELECT * FROM (SELECT * FROM t_user LIMIT 2)t;

# 5、union,若第二个select出现在union之后
EXPLAIN SELECT * FROM t_user WHERE id = 1 UNION SELECT * FROM t_user WHERE id = 2;
8.3.3 Explain析执行计划-Explain type
  • type 显示的是访问类型,是较为重要的一个指标,可取值为:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YLJwyZwQ-1645196290628)(D:\liumingshan3\Desktop\image-20211227151006742.png)]

  • 结果值最好到最坏 以此system > const > eq_ref > ref > range > index > ALL
# 1.不访问任何表,任何索引,直接返回结果
EXPLAIN SELECT NOW();

# 2.全表扫描,此时id上无索引
EXPLAIN SELECT * FROM t_user;

# 3.system 系统表,数据库启动的时候,数据库系统表已经加载在内存中

# 4.const 命中主键索引或者唯一索引,被链接的部分是一个常量(const)
EXPLAIN SELECT * FROM t_user WHERE id = 1;
EXPLAIN SELECT * FROM t_user WHERE username = 'zhangsan';

# 创建唯一索引
CREATE UNIQUE INDEX unique_index_username ON t_user(username);
# 查询所有的索引
SHOW INDEX FROM t_user;
# 删除唯一索引
DROP INDEX unique_index_username ON t_user;

# 5.ref:非唯一索引扫描(普通索引),左右表匹配会出现多行,很eq_ref相反
CREATE INDEX index_username ON t_user(username);
EXPLAIN SELECT * FROM t_user WHERE username='zhangsan';

# 6.eq_ref:左表有主键,而且左表和右表的每一行刚好匹配,
# 如果数据不是一一对应,那么结果就会是ALL
CREATE TABLE user2(
	id INT,
	NAME VARCHAR(20)
);
INSERT INTO user2 VALUES(1,'zhangsan'),(2,'lisi'),(3,'wangwu');
CREATE TABLE user2_ex(
	id INT,
	age INT
);
INSERT INTO user2_ex VALUES(1,20),(2,19),(3,20);

EXPLAIN SELECT * FROM user2 a, user2_ex b WHERE a.id = b.id;
# 添加主键
ALTER TABLE user2 ADD PRIMARY KEY(id);

# 7.range 范围查询
EXPLAIN SELECT * FROM user2 WHERE id > 2;

# 8.index:把索引列的全部数据进行扫描
EXPLAIN SELECT id FROM user2;
8.3.4 Explain分析执行计划-其他指标字段
  • Explain 之 table

    • 显示这一步所访问数据库中表名称有时不是真实的表名字,可能是简称,
  • explain 之 rows

    • 扫描行的数量。
  • Explain 之 key

    • possible_keys : 显示可能应用在这张表的索引, 一个或多个。

    • key : 实际使用的索引, 如果为NULL, 则没有使用索引。

    • key_len : 表示索引中使用的字节数, 该值为索引字段最大可能长度,并非实际使用长度,在不损失精确性的前提下, 长度越短越好 。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tBoI94Hc-1645196290628)(C:\Users\liumingshan3\AppData\Roaming\Typora\typora-user-images\image-20211227155127062.png)]

  • Explain之 extra

    • 其他的额外的执行计划信息,在该列展示 。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1FPPlhNz-1645196290629)(C:\Users\liumingshan3\AppData\Roaming\Typora\typora-user-images\image-20211227155211465.png)]

SHOW INDEX FROM t_user;
DROP INDEX index_username ON t_user;
EXPLAIN SELECT * FROM t_user ORDER BY username;
EXPLAIN SELECT username, COUNT(*) FROM t_user GROUP BY username;
EXPLAIN SELECT id, COUNT(*) FROM t_user GROUP BY id;

8.4 show profile分析 - SQL

  • Mysql从5.0.37版本开始增加了对 show profilesshow profile 语句的支持。show profiles 能够在做SQL优化时帮助我们了解时间都耗费到哪里去了。。

  • 通过 have_profiling 参数,能够看到当前MySQL是否支持profile:

    # 查看当前的数据库是否支持profile
    SELECT @@have_profiling;
    # 开启profiling开关
    SET profiling=1;
    
  • 通过profile,我们能够更清楚地了解SQL执行的过程。首先,我们可以执行一系列的操作

    USE shiro;
    SELECT COUNT(*) FROM t_user;
    SELECT * FROM t_user;
    SELECT * FROM t_user WHERE id < 2;
    
    # 显示具体的某条查询信息的执行流程
    SHOW PROFILE;
    # 查看每条语句执行的详细时间
    SHOW PROFILES;
    
    # 通过show  profile for  query  query_id 语句可以查看到该SQL执行过程中每个线程的状态和消耗的时间:
    # 查询具体的语句执行的时间
    SHOW PROFILE FOR QUERY 8;
    
    image-20211227171839768
image-20211227172327382
# 在获取到最消耗时间的线程状态后,MySQL支持进一步选择all、cpu、block io 、context switch、page faults等明细类型类查看MySQL在使用什么资源上耗费了过高的时间。例如,选择查看CPU的耗费时间  :
# 查看cpu的耗费时间
SHOW PROFILE CPU FOR QUERY 8;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eJ0Q5w1P-1645196290629)(C:\Users\liumingshan3\AppData\Roaming\Typora\typora-user-images\image-20211227172552708.png)]

8.5 trace分析优化器执行计划

  • MySQL5.6提供了对SQL的跟踪trace, 通过trace文件能够进一步了解为什么优化器选择A计划, 而不是选择B计划

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bJe8T3h5-1645196290629)(C:\Users\liumingshan3\AppData\Roaming\Typora\typora-user-images\image-20211227173453303.png)]

  • 打开trace , 设置格式为 JSON,并设置trace最大能够使用的内存大小,避免解析过程中因为默认内存过小而不能够完整展示。

    SET optimizer_trace="enabled=on",end_markers_in_json=on; 
    set optimizer_trace_max_mem_size=1000000;
    
    # 执行sql语句
    select * from t_user where id < 2;
    
    # 最后, 检查information_schema.optimizer_trace就可以知道MySQL是如何执行SQL的 :
    select * from information_schema.optimizer_trace \G;
    

8.6 使用索引优化

  • 索引是数据库优化最常用也是最重要的手段之一, 通过索引通常可以帮助用户解决大多数的MySQL的性能优化问题。

  • 数据准备

create table `tb_seller` (
    `sellerid` varchar (100),
    `name` varchar (100),
    `nickname` varchar (50),
    `password` varchar (60),
    `status` varchar (1),
    `address` varchar (100),
    `createtime` datetime,
    primary key(`sellerid`)
); 

-- 插入数据信息
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('alibaba','京东','京东小店','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('baidu','百度科技有限公司','百度小店','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('huawei','华为科技有限公司','华为小店','e10adc3949ba59abbe56e057f20f883e','0','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('itcast','传智播客教育科技有限公司','传智播客','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('itheima','黑马程序员','黑马程序员','e10adc3949ba59abbe56e057f20f883e','0','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('luoji','罗技科技有限公司','罗技小店','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00');

-- 创建组合索引 
create index idx_seller_name_sta_addr on tb_seller(name,status,address);

-- 分析:
-- 全值匹配和字段匹配成功即可,和字段的顺序无关
explain select * from tb_seller where name='小米科技' and status='1' and address='北京市';
8.6.1 最左前缀法则
-- 最左前缀法则
-- 如果索引了多列,要遵守最左前缀法则,指的是查询从索引的最左前列开始,并且不跳过索引中的列
EXPLAIN SELECT * FROM tb_seller WHERE NAME='小米'; -- 403
-- 交换位置没有问题,只要符合最左前缀原则即可
EXPLAIN SELECT * FROM tb_seller WHERE NAME='小米' AND STATUS='1'; -- 410
EXPLAIN SELECT * FROM tb_seller WHERE STATUS='1' AND NAME='小米'; -- 410

-- 违反了最左前缀法则,索引失效
EXPLAIN SELECT * FROM tb_seller WHERE STATUS='1'; 

-- 如果符合最左前缀法则,但是出现了跳跃某一列,只有最左列的索引生效
-- 下列的语句只用到了name这个索引,因为address中间还有一个status,违反了原则,只有最左边的生效
EXPLAIN SELECT * FROM tb_seller WHERE NAME='小米' AND address='北京市'; -- 403
8.6.2 范围查询中的索引失效
-- 范围查询右边的列,不能使用索引
-- 根据钱买你的两个字段 name,status查询走的是索引列,但是最后一个条件address没有用到索引
EXPLAIN SELECT * FROM tb_seller WHERE NAME='小米' AND STATUS > '1' AND address='北京市'; -- 410

-- 不要在索引列上进行运算操作,索引将会失效
EXPLAIN SELECT * FROM tb_seller WHERE SUBSTRING(NAME,3,2)='科技'; -- null

-- 字符串不加单引号,造成索引失效(只有第一个字段的索引生效)
EXPLAIN SELECT * FROM tb_seller WHERE NAME='小米' AND STATUS = 1; -- 403
8.6.3 尽量使用覆盖索引
-- 尽量使用覆盖索引(就是创建了索引的列),减少使用select *
-- 效率低,因为查询需要从原表及磁盘中进行读取数据信息
EXPLAIN SELECT * FROM tb_seller WHERE NAME='京东' AND address = '北京市'; -- 效率低

-- 效率高,直接从索引树中就可以查询到所有的数据(要查询的字段为索引字段)
EXPLAIN SELECT NAME FROM tb_seller WHERE NAME='京东' AND address = '北京市';
EXPLAIN SELECT NAME, STATUS,address FROM tb_seller WHERE NAME='京东' AND address = '北京市';
EXPLAIN SELECT NAME,address FROM tb_seller WHERE NAME='京东' AND address = '北京市';

-- 效率低,password不属于索引列中的字段,仍然需要去原表中进行查询
EXPLAIN SELECT NAME, STATUS,address,PASSWORD FROM tb_seller WHERE NAME='京东' AND address = '北京市';



-- 尽量使用覆盖索引(之访问索引的查询,索引列完全包含要查询的列,减少select * 的使用)
-- 如果查询列,超出了索引列,也会降低性能
/*
Extra:
   using index: 使用覆盖索引的时候会出现
   using where:在查找使用索引的情况下,需要回表去查询需要的数据
   using index condition:查找使用了索引,但是需要回表查询数据
   using index,using where:查找使用了索引,但是需要的数据都能在索引列中获取,所以不需要回表查询数据
*/

8.6.4 用 or 分隔开的条件,那么涉及到的索引都不会被用到
-- 用 or 分隔开的条件,那么涉及到的索引都不会被用到
EXPLAIN SELECT * FROM tb_seller WHERE NAME='京东' OR address = '北京市';
EXPLAIN SELECT * FROM tb_seller WHERE NAME='京东' OR createtime = '2088-01-01 12:00:00';
8.6.5 % 开头的模糊查询
-- 以 % 开头的like模糊查询,索引失效
EXPLAIN SELECT * FROM tb_seller WHERE NAME LIKE '%京东';
-- % 结尾,会使用索引
EXPLAIN SELECT * FROM tb_seller WHERE NAME LIKE '京东%';
-- 索引依旧失效
EXPLAIN SELECT * FROM tb_seller WHERE NAME LIKE '%京东%';

-- 弥补不足,不直接使用 *,直接使用索引列
EXPLAIN SELECT NAME,STATUS FROM tb_seller WHERE NAME LIKE '%京东';
EXPLAIN SELECT NAME,STATUS FROM tb_seller WHERE NAME LIKE '%京东%';
8.6.6 其他的情况
-- 1.如果mysql评估使用索引比全表更慢,则不使用索引
-- 这种情况是由数据本身的特点来决定的
CREATE INDEX index_address ON tb_seller(address);
EXPLAIN SELECT * FROM tb_seller WHERE address='北京市'; # 北京市,数据较多,全表查询,不使用索引
EXPLAIN SELECT * FROM tb_seller WHERE address='西安市'; # 西安市,数据较少,使用索引查询

--  2、is  NULL , is NOT NULL  有时有效,有时索引失效。
create index index_address on tb_seller(nickname);
explain select * from tb_seller where nickname is NULL;  -- 索引有效,为null的数据较少
explain select * from tb_seller where nickname is not NULL; -- 索引无效,数非空数据较多


-- 3.in 走索引,not in 索引失效
-- 普通索引
EXPLAIN SELECT * FROM tb_seller WHERE nickname IN ('京东小店', '传智播客');
EXPLAIN SELECT * FROM tb_seller WHERE nickname NOT IN ('京东小店', '传智播客');
-- 主键索引(都会使用索引)
EXPLAIN SELECT * FROM tb_seller WHERE sellerid IN ('jd', 'huawei');
EXPLAIN SELECT * FROM tb_seller WHERE sellerid NOT IN ('jd', 'huawei');

-- 4. 单列索引和复合索引,尽量使用符合索引
SHOW INDEX FROM tb_seller;
CREATE INDEX idx_seller_name_sta_addr ON tb_seller(NAME,STATUS,address);
/*
 等价于(这几个索引):
	name
	name + status 
	name + status + address
*/
-- 如果一张表有多个单列索引,即使where中都使用了这些索引,则只有一个最优的索引会生效
DROP INDEX idx_seller_name_sta_addr ON tb_seller;
DROP INDEX index_address ON tb_seller;

CREATE INDEX index_name ON tb_seller(NAME);
CREATE INDEX index_status ON tb_seller(STATUS);
CREATE INDEX index_address ON tb_seller(address);

EXPLAIN SELECT * FROM tb_seller WHERE NAME='京东' AND STATUS='1' AND address='北京';
EXPLAIN SELECT * FROM tb_seller WHERE STATUS='1' AND address='北京';

8.7 SQL优化

8.7.1 大批量插入数据
  • 当使用load 命令导入数据的时候,适当的设置可以提高导入的效率。对于 InnoDB 类型的表,有以下几种方式可以提高导入的效率:
8.7.1.1 主键顺序插入
# 因为InnoDB类型的表是按照主键的顺序保存的,所以将导入的数据按照主键的顺序排列,可以有效的提高导入数据的效率。如果InnoDB表没有主键,那么系统会自动默认创建一个内部列作为主键,所以如果可以给表创建一个主键,将可以利用这点,来提高导入数据的效率。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k4pOt4mK-1645196290630)(C:\Users\liumingshan3\AppData\Roaming\Typora\typora-user-images\image-20211228152559436.png)]

-- 1、首先,检查一个全局系统变量 'local_infile' 的状态, 如果得到如下显示 Value=OFF,则说明这是不可用的
show global variables like 'local_infile';
 
-- 2、修改local_infile值为on,开启local_infile
set global local_infile=1;
 
-- 3、加载数据 
/*
脚本文件介绍 :
    sql1.log  ----> 主键有序
    sql2.log  ----> 主键无序
*/
load data local infile 'D:\\sql_data\\sql1.log' into table tb_user fields terminated by ',' lines terminated by '\n';

8.7.1.2 关闭唯一性校验
# 在导入数据前执行 SET UNIQUE_CHECKS=0,关闭唯一性校验,在导入结束后执行 SET UNIQUE_CHECKS=1,恢复唯一性校验,可以提高导入的效率。
-- 关闭唯一性校验
SET UNIQUE_CHECKS=0;
 
truncate table tb_user;
load data local infile 'D:\\sql_data\\sql1.log' into table tb_user fields terminated by ',' lines terminated by '\n';

-- 开启唯一性校验
SET UNIQUE_CHECKS=1;
8.7.1.3 优化insert语句
  • 当进行数据的insert操作的时候,可以考虑采用以下几种优化方案:
-- 如果需要同时对一张表插入很多行数据时,应该尽量使用多个值表的insert语句,这种方式将大大的缩减客户端与数据库之间的连接、关闭等消耗。使得效率比分开执行的单个insert语句快。

-- 原始方式为:
insert into tb_test values(1,'Tom');
insert into tb_test values(2,'Cat');
insert into tb_test values(3,'Jerry');
 
-- 优化后的方案为 :  
insert into tb_test values(1,'Tom'),(2,'Cat'),(3,'Jerry');

-- 方式2:
-- 在事务中进行数据插入。
begin;
insert into tb_test values(1,'Tom');
insert into tb_test values(2,'Cat');
insert into tb_test values(3,'Jerry');
commit;

-- 方式3:
-- 数据有序插入
insert into tb_test values(4,'Tim');
insert into tb_test values(1,'Tom');
insert into tb_test values(3,'Jerry');
insert into tb_test values(5,'Rose');
insert into tb_test values(2,'Cat');
 
 
-- 优化后
insert into tb_test values(1,'Tom');
insert into tb_test values(2,'Cat');
insert into tb_test values(3,'Jerry');
insert into tb_test values(4,'Tim');
insert into tb_test values(5,'Rose');
8.7.2 优化Order by 语句
# 两种排序方式:
# 第一种是通过对返回数据进行排序,也就是通常说的 filesort 排序,所有不是通过索引直接返回排序结果的排序都叫 FileSort 排序。

# 第二种通过有序索引顺序扫描直接返回有序数据,这种情况即为 using index,不需要额外排序,操作效率高。
# fileSort的优化:

- 通过创建合适的索引,能够减少 Filesort 的出现,但是在某些情况下,条件限制不能让Filesort消失,那就需要加快 Filesort的排序操作。对于Filesort , MySQL 有两种排序算法:

- 1) 两次扫描算法 :MySQL4.1 之前,使用该方式排序。首先根据条件取出排序字段和行指针信息,然后在排序区 sort buffer 中排序,如果sort buffer不够,则在临时表 temporary table 中存储排序结果。完成排序之后,再根据行指针回表读取记录,该操作可能会导致大量随机I/O操作。

- 2)一次扫描算法:一次性取出满足条件的所有字段,然后在排序区 sort buffer 中排序后直接输出结果集。排序时内存开销较大,但是排序效率比两次扫描算法要高。


- MySQL 通过比较系统变量 max_length_for_sort_data 的大小和Query语句取出的字段总大小, 来判定是否那种排序算法,如果max_length_for_sort_data 更大,那么使用第二种优化之后的算法;否则使用第一种。

- 可以适当提高 sort_buffer_size  和 max_length_for_sort_data  系统变量,来增大排序区的大小,提高排序的效率。
# order by 语句的优化
CREATE TABLE emp (
    id INT(11) NOT NULL AUTO_INCREMENT,
    NAME VARCHAR(100) NOT NULL,
    age INT(3) NOT NULL,
    salary INT(11) DEFAULT NULL,
    PRIMARY KEY (id)
);

INSERT INTO emp (id, NAME, age, salary) VALUES('1','Tom','25','2300');
INSERT INTO emp (id, NAME, age, salary) VALUES('2','Jerry','30','3500');
INSERT INTO emp (id, NAME, age, salary) VALUES('3','Luci','25','2800');
INSERT INTO emp (id, NAME, age, salary) VALUES('4','Jay','36','3500');
INSERT INTO emp (id, NAME, age, salary) VALUES('5','Tom2','21','2200');
INSERT INTO emp (id, NAME, age, salary) VALUES('6','Jerry2','31','3300');
INSERT INTO emp (id, NAME, age, salary) VALUES('7','Luci2','26','2700');
INSERT INTO emp (id, NAME, age, salary) VALUES('8','Jay2','33','3500');
INSERT INTO emp (id, NAME, age, salary) VALUES('9','Tom3','23','2400');
INSERT INTO emp (id, NAME, age, salary) VALUES('10','Jerry3','32','3100');
INSERT INTO emp (id, NAME, age, salary) VALUES('11','Luci3','26','2900');
INSERT INTO emp (id, NAME, age, salary) VALUES('12','Jay3','37','4500');

CREATE INDEX idx_emp_age_salary ON emp(age,salary);

# 排序,order by
# 尽量不要写 * 
EXPLAIN SELECT * FROM emp ORDER BY age; -- Using filesort
EXPLAIN SELECT * FROM emp ORDER BY age, salary; -- Using filesort

# 尽量查询的时候写上索引字段
EXPLAIN SELECT id FROM emp ORDER BY age; -- Using index
EXPLAIN SELECT id,age FROM emp ORDER BY age; -- Using index
EXPLAIN SELECT id,age,salary,NAME FROM emp ORDER BY age; -- Using filesort

-- order by 后边的多个字段要求尽量排序方式相同
EXPLAIN SELECT id,age FROM emp ORDER BY age ASC, salary DESC; -- Using index; Using filesort
EXPLAIN SELECT id,age FROM emp ORDER BY age DESC, salary DESC; -- Backward index scan; Using index

-- order by 后边的多个排序字段顺序尽量和组合索引字段顺序一直
EXPLAIN SELECT id,age FROM emp ORDER BY salary, age; -- Using index; Using filesort

-- 查看数据信息
SHOW VARIABLES LIKE 'max_length_for_sort_data';  -- 4096
SHOW VARIABLES LIKE 'sort_buffer_size';  -- 262144
8.7.3 优化 group by
# GROUP BY实际上也同样会进行排序操作,而且与ORDER BY相比,GROUP BY主要只是多了排序之后的分组操作。当然,如果在分组的时候还使用了其他的一些聚合函数,那么还需要一些聚合函数的计算。所以,在GROUP BY 的实现过程中,与 ORDER BY 一样也可以利用到索引。

# 如果查询包含 group by 但是用户想要避免排序结果的消耗, 则可以执行order by null 禁止排序。如下 :
DROP INDEX idx_emp_age_salary ON emp; 

-- 如果使用的是索引列进行分组,通过索引列进行查询,效率会提高
-- using index
EXPLAIN SELECT age,COUNT(*) FROM emp GROUP BY age;

-- Using temporary
EXPLAIN SELECT age,COUNT(*) FROM emp GROUP BY age ORDER BY NULL; 

CREATE INDEX idx_emp_age_salary ON emp(age,salary);
8.7.4 优化子查询
# 使用子查询可以一次性的完成很多逻辑上需要多个步骤才能完成的SQL操作,同时也可以避免事务或者表锁死,并且写起来也很容易。但是,有些情况下,子查询是可以被更高效的连接(JOIN)替代。
explain select * from user where uid in (select uid from user_role ); 

explain select * from user u , user_role ur where u.uid = ur.uid;

-- system>const>eq_ref>ref>range>index>ALL
-- 连接(Join)查询之所以更有效率一些 ,是因为MySQL不需要在内存中创建临时表来完成这个逻辑上需要两个步骤的查询工作。
8.7.5 优化limit查询
# 一般分页查询时,通过创建覆盖索引能够比较好地提高性能。一个常见又非常头疼的问题就是 limit 900000,10  ,此时需要MySQL排序前900010 记录,仅仅返回900000 - 900010 的记录,其他记录丢弃,查询排序的代价非常大 。

# 优化思路1: 在索引上完成排序分页操作,最后根据主键关联回原表查询所需要的其他列内容。
# 优化思路2: 该方案适用于主键自增的表,可以把Limit 查询转换成某个位置的查询。

INSERT INTO emp (id, NAME, age, salary) VALUES('1','Tom','25','2300');
INSERT INTO emp (id, NAME, age, salary) VALUES('2','Jerry','30','3500');
INSERT INTO emp (id, NAME, age, salary) VALUES('3','Luci','25','2800');
INSERT INTO emp (id, NAME, age, salary) VALUES('4','Jay','36','3500');
INSERT INTO emp (id, NAME, age, salary) VALUES('5','Tom2','21','2200');
INSERT INTO emp (id, NAME, age, salary) VALUES('6','Jerry2','31','3300');
INSERT INTO emp (id, NAME, age, salary) VALUES('7','Luci2','26','2700');
INSERT INTO emp (id, NAME, age, salary) VALUES('8','Jay2','33','3500');
INSERT INTO emp (id, NAME, age, salary) VALUES('9','Tom3','23','2400');
INSERT INTO emp (id, NAME, age, salary) VALUES('10','Jerry3','32','3100');
INSERT INTO emp (id, NAME, age, salary) VALUES('11','Luci3','26','2900');
INSERT INTO emp (id, NAME, age, salary) VALUES('12','Jay3','37','4500');

CREATE INDEX idx_emp_age_salary ON emp(age,salary);

# 排序,order by
# 尽量不要写 * 
EXPLAIN SELECT * FROM emp ORDER BY age; -- Using filesort
EXPLAIN SELECT * FROM emp ORDER BY age, salary; -- Using filesort

# 尽量查询的时候写上索引字段
EXPLAIN SELECT id FROM emp ORDER BY age; -- Using index
EXPLAIN SELECT id,age FROM emp ORDER BY age; -- Using index
EXPLAIN SELECT id,age,salary,NAME FROM emp ORDER BY age; -- Using filesort

-- order by 后边的多个字段要求尽量排序方式相同
EXPLAIN SELECT id,age FROM emp ORDER BY age ASC, salary DESC; -- Using index; Using filesort
EXPLAIN SELECT id,age FROM emp ORDER BY age DESC, salary DESC; -- Backward index scan; Using index

-- order by 后边的多个排序字段顺序尽量和组合索引字段顺序一直
EXPLAIN SELECT id,age FROM emp ORDER BY salary, age; -- Using index; Using filesort

-- 查看数据信息
SHOW VARIABLES LIKE 'max_length_for_sort_data';  -- 4096
SHOW VARIABLES LIKE 'sort_buffer_size';  -- 262144
8.7.3 优化 group by
# GROUP BY实际上也同样会进行排序操作,而且与ORDER BY相比,GROUP BY主要只是多了排序之后的分组操作。当然,如果在分组的时候还使用了其他的一些聚合函数,那么还需要一些聚合函数的计算。所以,在GROUP BY 的实现过程中,与 ORDER BY 一样也可以利用到索引。

# 如果查询包含 group by 但是用户想要避免排序结果的消耗, 则可以执行order by null 禁止排序。如下 :
DROP INDEX idx_emp_age_salary ON emp; 

-- 如果使用的是索引列进行分组,通过索引列进行查询,效率会提高
-- using index
EXPLAIN SELECT age,COUNT(*) FROM emp GROUP BY age;

-- Using temporary
EXPLAIN SELECT age,COUNT(*) FROM emp GROUP BY age ORDER BY NULL; 

CREATE INDEX idx_emp_age_salary ON emp(age,salary);
8.7.4 优化子查询
# 使用子查询可以一次性的完成很多逻辑上需要多个步骤才能完成的SQL操作,同时也可以避免事务或者表锁死,并且写起来也很容易。但是,有些情况下,子查询是可以被更高效的连接(JOIN)替代。
explain select * from user where uid in (select uid from user_role ); 

explain select * from user u , user_role ur where u.uid = ur.uid;

-- system>const>eq_ref>ref>range>index>ALL
-- 连接(Join)查询之所以更有效率一些 ,是因为MySQL不需要在内存中创建临时表来完成这个逻辑上需要两个步骤的查询工作。
8.7.5 优化limit查询
# 一般分页查询时,通过创建覆盖索引能够比较好地提高性能。一个常见又非常头疼的问题就是 limit 900000,10  ,此时需要MySQL排序前900010 记录,仅仅返回900000 - 900010 的记录,其他记录丢弃,查询排序的代价非常大 。

# 优化思路1: 在索引上完成排序分页操作,最后根据主键关联回原表查询所需要的其他列内容。
# 优化思路2: 该方案适用于主键自增的表,可以把Limit 查询转换成某个位置的查询。

你可能感兴趣的:(笔记,mysql,数据库,database)