mysql优化 个人笔记 (mysql 进阶索引 ) 非礼勿扰 -m15

一、 limit

1. 测试案例
-- 创建表 ![在这里插入图片描述](https://img-blog.csdnimg.cn/cover1/248667912046641316.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,image_MjAyMDA3MTUxNjIxMDEzOC5wbmc=,size_16,color_FFFFFF,t_70,image/resize,m_lfit,w_962)

DROP TABLE IF EXISTS `rental`;
CREATE TABLE `rental` (
  `rental_id` int(11) NOT NULL AUTO_INCREMENT,
  `rental_date` datetime NOT NULL,
  `inventory_id` mediumint(8) unsigned NOT NULL,
  `customer_id` smallint(5) unsigned NOT NULL,
  `return_date` datetime DEFAULT NULL,
  `staff_id` tinyint(3) unsigned NOT NULL,
  `last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`rental_id`)
)  

-- 查询语句 
select * from rental limit 1000000,20

-- 可替代语句
select * from rental a join (select rental_id from rental limit 1000000,20 ) b on a.rental_id = b.rental_id 

-- 主键自增 有序 也可以这样 
select * from rental a where a.rental_id >= (select rental_id from rental limit 1000000,1) limit 20 

-- 有人会想到用IN   看下图图图图图  我用的这个版本不支持其他没测试不知道了
select * from rental a where a.rental_id in (select rental_id from rental limit 1000000,20)  


-- 前三个哪个好一点呢 这个需要实际场景中 根据自己的实际数据进行测试 和预测 



-- 看过一篇博客 说是百万级别内 limit 1000000,20 比较好
-- 千万级别及以上 后边2中比较好  
-- sql优化 还是那句话  没有绝对  只有测试结果 一定要根据自己的场景进行测试 合适自己的鞋 只有自己试了才知道  合适自己的女朋友只有自己处了才知道 合适自己的路 只有自己走了才知道

mysql优化 个人笔记 (mysql 进阶索引 ) 非礼勿扰 -m15_第1张图片
mysql优化 个人笔记 (mysql 进阶索引 ) 非礼勿扰 -m15_第2张图片

2. 尽量使用覆盖索引 1中的join 就是用了主键优化查询了

二、 优化UNION

  1. 除非确实需要服务器消除重复的行,否则一定要使用union all,没有all关键字的话,mysql在查询的时候会给临时表加上distinct关键字 ,这个操作比较耗性能

  2. 行转列的时候 可能会用到union

Mysql行转列 测试

--创建表 
CREATE TABLE `score_table` (
	`stuname` VARCHAR (255) NULL,
	`classname` VARCHAR (255) NULL,
	`score` FLOAT NULL
);

-- 插入数据
INSERT INTO `score_table` VALUES ('黄渤', '唱歌', '60');
INSERT INTO `score_table` VALUES ('邓紫棋', '唱歌', '90');
INSERT INTO `score_table` VALUES ('孙漂亮', '唱歌', '50');
INSERT INTO `score_table` VALUES ('黄渤', '演技', '80');
INSERT INTO `score_table` VALUES ('邓紫棋', '演技', '70');
INSERT INTO `score_table` VALUES ('孙漂亮', '演技', '90');

mysql优化 个人笔记 (mysql 进阶索引 ) 非礼勿扰 -m15_第3张图片
我想要的结果是:姓名 , 演技成绩, 唱歌成绩

 -- 方式1   max 
select 
stuname, 
max(case classname when '演技' then score else 0 end) 演技分数,
max(case classname when '唱歌' then score else 0 end) 唱歌分数
from score_table
group by stuname 

-- 方式1.5  if
select 
stuname, 
sum(if( classname='演技' ,score,0) ) 演技分数,
sum(if( classname='唱歌' ,score,0)) 唱歌分数
from score_table
group by stuname 

-- 方式2  sum 
select 
stuname, 
sum(case classname when '演技' then score else 0 end) 演技分数,
sum(case classname when '唱歌' then score else 0 end) 唱歌分数
from score_table
group by stuname 


-- 方式3  子查询 
select 
distinct stuname ,
(select score from score_table b where b.stuname = a.stuname and b.classname='演技') 演技分数, 
(select score from score_table b where b.stuname = a.stuname and b.classname='唱歌') 唱歌分数 
from score_table a

-- 方式4 可以mysql使用存储过程 或者函数来处理 

在这里插入图片描述

-- 方式5 
SELECT stuname,GROUP_CONCAT(classname,':',score) AS 成绩 FROM score_table
GROUP BY stuname

mysql优化 个人笔记 (mysql 进阶索引 ) 非礼勿扰 -m15_第4张图片

-- 方式6 
select a.stuname,a.score as '演技分数',b.score  as '唱歌分数' from 
(select stuname , score from score_table where classname ='演技') a
left JOIN 
(select stuname , score from score_table where classname ='唱歌') b 
on a.stuname = b.stuname 



-- 方式7 
select a.stuname ,sum(a.演技分数) 演技分数, sum(a.唱歌分数)唱歌分数 
from (
select stuname ,score 演技分数 , 0 唱歌分数 from score_table where classname ='演技' 
union all 
select stuname ,0 演技分数 , score 唱歌分数 from score_table where classname ='唱歌' 
) a
group by a.stuname 

mysql优化 个人笔记 (mysql 进阶索引 ) 非礼勿扰 -m15_第5张图片

三、推荐使用用户自定义变量

1. 自定义变量的使用
set @one :=1
select @one --结果 1
select @one:=@one+1 --结果 2  

-- 可以加sql语句 
set @min_rental_id :=(select min(rental_id) from rental)

select @min_rental_id  --结果 1
-- 可以加函数 
set @last_week:=current_date-interval 1 week;

select  @last_week  --  2020-07-16
 select @isEnd:=10 -- 结果10 并且或赋值给@isEnd
 select @isEnd -- 结果 10 
2. 自定义变量的限制
  1. 当前会话有效 无法使用缓存

  2. 不能在使用常量或者标识符的地方官使用自定义变量,例如表名 limit子句

  3. 用户自定义变量在一个连接内有效 所以不能作为连接之间的通信数据使用 一个session有效

  4. 不能显示的生命自定义变量类型

  5. mysql优化器 在某些场景下 可能会把自定义变量优化掉,可能不会按照自己想的意愿进行

  6. 赋值符号 := 的优先级非常低 所以在使用赋值表达式的时候应该明确的使用括号
    select a.*,(@aa:=1) from actor a;

  7. 使用未定义变量 不会产生语法错误
    select @bbbbbbbbbbb; – 没有错误

  8. 有人说mybatis不支持 自定义变量 我测试了一个版本 简单的可以支持 复杂的没测试

pom mybatis版本 :
mysql优化 个人笔记 (mysql 进阶索引 ) 非礼勿扰 -m15_第6张图片

接口+xml:
在这里插入图片描述
mysql优化 个人笔记 (mysql 进阶索引 ) 非礼勿扰 -m15_第7张图片
测试类:
mysql优化 个人笔记 (mysql 进阶索引 ) 非礼勿扰 -m15_第8张图片
输出结果:
在这里插入图片描述

3.自定义变量的案例
  1. 排序
set @rownum:=0;

select city,@rownum:=@rownum+1 as   rownum from city limit 10 


-- 案例2 
-- 这是个错误的案例 
set @rownum=0;
select actor_id,count(*) cnt,@rownum:=@rownum+1	 from film_actor group by actor_id order by cnt limit 10 

-- 分组的时候 要把分组放在子查询 
set @rownum=0;
select * , @rownum:=@rownum+1	as num from 
(
select actor_id,count(*) cnt from film_actor
 group by actor_id order by cnt limit 10 
) a

mysql优化 个人笔记 (mysql 进阶索引 ) 非礼勿扰 -m15_第9张图片
mysql优化 个人笔记 (mysql 进阶索引 ) 非礼勿扰 -m15_第10张图片
2. 避免重复查询刚刚更新的数据

-- 创建表 
CREATE TABLE `test` (
`id`  int NOT NULL ,
`t_date`  datetime NULL ,
`name`  varchar(255) NULL ,
PRIMARY KEY (`id`)
)
-- 插入数据 
INSERT INTO `sakila`.`test` (`id`, `t_date`, `name`) VALUES ('1', '2020-07-25 10:07:01', '测试');

--  跟新id为1的数据的时间 并再次查询
update `sakila`.`test` set t_date = now() where id=1
select t_date from `sakila`.`test` where id=1

-- 第二种方式
update `sakila`.`test` set t_date = now() where id=1 and @now:=now()
select @now ;
  1. 确定取值的顺序问题 在赋值和取变量的时候可能不在一个阶段
-- 还是上边test表
INSERT INTO `sakila`.`test` (`id`, `t_date`, `name`) VALUES ('2', '2020-07-25 10:26:00', '测试2');
INSERT INTO `sakila`.`test` (`id`, `t_date`, `name`) VALUES ('3', '2020-07-25 10:26:11', '测试3');

-- 1
set @rownum:=0;
select name,@rownum:=@rownum+1 from test  where @rownum<=1
可能是先走where 判断第一条数据@rownum<=1   正确
走select字段 @rownum=@rownum+1
再走where 再判断第二条数据 @rownum<=1   正确
@rownum=@rownum+1
再走where 再判断第二条数据 @rownum<=1-- 2
set @rownum:=0;
select name,@rownum:=@rownum+1 from test  where @rownum<=1 order by t_date
讲道理是先过滤再排序 这里好像是先排序后过滤了 所以所有数据都查出来了
-- 3
set @rownum:=0;
select name,@rownum from test  where (@rownum:=@rownum+1)<=1

这个也是先走where 但是where的时候执行了@rownum:=@rownum+1 
相比1中的在select字段中+1 就少了一条数据 
-- 

mysql优化 个人笔记 (mysql 进阶索引 ) 非礼勿扰 -m15_第11张图片
mysql优化 个人笔记 (mysql 进阶索引 ) 非礼勿扰 -m15_第12张图片

mysql优化 个人笔记 (mysql 进阶索引 ) 非礼勿扰 -m15_第13张图片

-------------------!分区表!

https://www.mysql.com/
– 分区表相关介绍
https://dev.mysql.com/doc/refman/8.0/en/partitioning.html
InnoDB 每个表默认2个文件 x.frm(表结构元数据) x.ibd(索引+数据)

MyISAM 默认3个文件 x.frm x.MYD(数据) x.MYI(索引)

**总结:**可以按照自己定义的规则,把一个表分成多个文件存储带"#",分而治之

检索的时候 IO 量就少了

1.应用场景
1. 表很大无法全放内存,或者只在表的最后部分有热点数据,其他部分都是历史数据
2. 分区表数据更容易进行维护 
	批量删除大量数据的时候,可以清除指定分区
	对一个独立的分区进行优化、检查、修复等操作
3. 分区表的数据可以分布在不同的物理设备上 从而更高效的利用物理设备
4. 可以使用分区表,避免某些特殊瓶颈
	innodb单个索引 互斥访问,innodb加锁是给索引加锁的,如果分区了 效率就高了
	ext3文件系统inode锁竞争 ls -li 展示第一列就是inode  分区的话就相当于是1.7hash的segment了
5. 可以备份和恢复 独立分区
2.分区表的限制
1. 早期mysql中 分区表表达式必须是整数,或者返回整数的表达式
	mysql5.5中  某些场景可以使用列来分区
2. 一个表最多只能有1024个分区, 5.7支持8196个分区  -- 这个与linux文件系统相关的 unlimited 

3. 分区表的分区表达式中使用的所有列都必须是表可能具有的每个唯一键的一部分,
换句话说,表上的每个唯一键都必须使用表的分区表达式中的每一列
官网地址:
https://dev.mysql.com/doc/refman/8.0/en/partitioning-limitations-partitioning-keys-unique-keys.html
 这个需要翻来覆去的看官网的几个错误的和正确的例子
我是这样理解的 不知道对不对:
分区表达式中有的列 表的唯一键和主键都必须包含
举例子
分区表达式有列col_a  col_b 
那么unique字段和主键 必须包含col_a col_b 这俩字段  
也就是说 分区表达式的字段是unique的子集 也是主键的子集  见下图



4. 分区表无法使用外键约束 

mysql优化 个人笔记 (mysql 进阶索引 ) 非礼勿扰 -m15_第14张图片

3.分区表简单使用

6种分区类型:
范围分区、列表分区、列分区、hash分区、KEY分区、子分区
https://dev.mysql.com/doc/refman/8.0/en/partitioning-types.html
mysql优化 个人笔记 (mysql 进阶索引 ) 非礼勿扰 -m15_第15张图片
秘钥

CREATE TABLE employees (
    id INT NOT NULL,
    fname VARCHAR(30),
    lname VARCHAR(30),
    hired DATE NOT NULL DEFAULT '1970-01-01',
    separated DATE NOT NULL DEFAULT '9999-12-31',
    job_code INT NOT NULL,
    store_id INT NOT NULL
)
PARTITION BY RANGE (store_id) (
    PARTITION p0 VALUES LESS THAN (6), -- store_id小于6 放这个分区
    PARTITION p1 VALUES LESS THAN (11),-- store_id大于6,小于11 放这个分区
    PARTITION p2 VALUES LESS THAN (16),-- store_id大于11,小于16 放这个分区
    PARTITION p3 VALUES LESS THAN MAXVALUE-- store_id 大于16,小于INT最大值 放这个分区
);

-- 还可以根据日期分区  各种高级应用场景


-- 修改的时候 最好不要修改分区键 

-- 有人说了,我们表刚开始没有建立分区,现在有数据了已经,还能再建立分区吗,
  当然可以!
  https://dev.mysql.com/doc/refman/8.0/en/partitioning-maintenance.html

	ALTER TABLE t1 REBUILD PARTITION p0, p1;

4. 分区表原理

分区表是由多个底层表实现的,这个底层表也是由句柄对象标识,我们可以直接访问各个分区,存储引擎管理分区各个底层表和管理普通表一样(所有底层表都必须使用相同的存储引擎),分区表的索引只是在各个底层表上各自加上一个完全相同的索引。
从存储引擎的角度来开,底层表和普通表没有任何不同,存储引擎也无须知道这是一个普通表还是一个分区表
感觉有点儿像是1.7hashmap的segment桶
分区表的增删改查操作逻辑:

  1. select 查询
    当查询一个分区表的时候,分区层先打开并锁住所有的底层表,优化器判断是否可以过滤部分分区,然后再调用对应的存储引擎接口访问各个分区的数据

  2. insert 操作
    当写入一条数据的时候,分区层先打开并锁住所有的底层表,然后确定哪个分区接受这条记录,然后将记录写入对应的底层报表

  3. update 操作
    更新一条记录时,分区层先打开并锁住所有的底层表,mysql先确定需要更新的记录在哪个分区,然后取出数据进行更新,再判断更新后数据应该再哪个分区,最后对底层表进行写入操作,并对源数据所在的底层进行删除操作

  4. delete 操作
    当删除一条记录时,分区层先打开并锁住所有的底层表,mysql确定要删除的记录在哪个分区,最后对相应的底层表进行删除操作

     有些操作支持过滤,例如删除一条记录时,Mysql需要先找到这条记录,如果where条件恰好和分区表达式匹配,就可以将所有不包含这条记录的分区都过滤掉,对于update同样有效
     对于insert操作 本身就是只命中一个分区,其他分区都会被过滤掉。
        
      虽然每个操作都会先锁住所有的底层表,但是这并不是说分区表在处理的过程中会锁住全表,**如果存储引擎能够自己实现行级锁,例如InnoDB 则会在分区层释放对应的表锁**    
    

5. 分区表类型

https://dev.mysql.com/doc/refman/8.0/en/partitioning-types.html
范围分区、列表分区、列分区、hash分区、秘钥分区、子分区
mysql优化 个人笔记 (mysql 进阶索引 ) 非礼勿扰 -m15_第16张图片

  1. 范围分区
    https://dev.mysql.com/doc/refman/8.0/en/partitioning-range.html
-- 普通范围分区 
CREATE TABLE employees (
    id INT NOT NULL,
    fname VARCHAR(30),
    lname VARCHAR(30),
    hired DATE NOT NULL DEFAULT '1970-01-01',
    separated DATE NOT NULL DEFAULT '9999-12-31',
    job_code INT NOT NULL,
    store_id INT NOT NULL
)
PARTITION BY RANGE (job_code) (
    PARTITION p0 VALUES LESS THAN (100),
    PARTITION p1 VALUES LESS THAN (1000),
    PARTITION p2 VALUES LESS THAN (10000)
);

-- 包含最大值MAXVALUE 包含函数YEAR    范围分区
CREATE TABLE employees (
    id INT NOT NULL,
    fname VARCHAR(30),
    lname VARCHAR(30),
    hired DATE NOT NULL DEFAULT '1970-01-01',
    separated DATE NOT NULL DEFAULT '9999-12-31',
    job_code INT,
    store_id INT
)
PARTITION BY RANGE ( YEAR(separated) ) (
    PARTITION p0 VALUES LESS THAN (1991),
    PARTITION p1 VALUES LESS THAN (1996),
    PARTITION p2 VALUES LESS THAN (2001),
    PARTITION p3 VALUES LESS THAN MAXVALUE
);

-- 使用UNIX_TIMESTAMP 范围分区 
CREATE TABLE quarterly_report_status (
    report_id INT NOT NULL,
    report_status VARCHAR(20) NOT NULL,
    report_updated TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
)
PARTITION BY RANGE ( UNIX_TIMESTAMP(report_updated) ) (
    PARTITION p0 VALUES LESS THAN ( UNIX_TIMESTAMP('2008-01-01 00:00:00') ),
    PARTITION p1 VALUES LESS THAN ( UNIX_TIMESTAMP('2008-04-01 00:00:00') ),
    PARTITION p2 VALUES LESS THAN ( UNIX_TIMESTAMP('2008-07-01 00:00:00') ),
    PARTITION p3 VALUES LESS THAN ( UNIX_TIMESTAMP('2008-10-01 00:00:00') ),
    PARTITION p4 VALUES LESS THAN ( UNIX_TIMESTAMP('2009-01-01 00:00:00') ),
    PARTITION p5 VALUES LESS THAN ( UNIX_TIMESTAMP('2009-04-01 00:00:00') ),
    PARTITION p6 VALUES LESS THAN ( UNIX_TIMESTAMP('2009-07-01 00:00:00') ),
    PARTITION p7 VALUES LESS THAN ( UNIX_TIMESTAMP('2009-10-01 00:00:00') ),
    PARTITION p8 VALUES LESS THAN ( UNIX_TIMESTAMP('2010-01-01 00:00:00') ),
    PARTITION p9 VALUES LESS THAN (MAXVALUE)
);

 
  1. 列表分区
    https://dev.mysql.com/doc/refman/8.0/en/partitioning-list.html
    类比sql中的in条件 java总的List#contains
CREATE TABLE employees (
    id INT NOT NULL,
    fname VARCHAR(30),
    lname VARCHAR(30),
    hired DATE NOT NULL DEFAULT '1970-01-01',
    separated DATE NOT NULL DEFAULT '9999-12-31',
    job_code INT,
    store_id INT
)
PARTITION BY LIST(store_id) (
    PARTITION pNorth VALUES IN (3,5,6,9,17),
    PARTITION pEast VALUES IN (1,2,10,11,19,20),
    PARTITION pWest VALUES IN (4,12,13,14,18),
    PARTITION pCentral VALUES IN (7,8,15,16)
);
  1. 列分区
    https://dev.mysql.com/doc/refman/8.0/en/partitioning-columns.html

1+2 + 无类型限制 = 3
范围分区+列表分区+无类型限制 = 列分区

 CREATE TABLE rcx (
         a INT,
         b INT,
        c CHAR(3),
        d INT
    )
     PARTITION BY RANGE COLUMNS(a,d,c) (
		 PARTITION p0 VALUES LESS THAN (5,10,'ggg'),
		 PARTITION p1 VALUES LESS THAN (10,20,'mmm'),
		 PARTITION p2 VALUES LESS THAN (15,30,'sss'),
		 PARTITION p3 VALUES LESS THAN (MAXVALUE,MAXVALUE,MAXVALUE)
      ); 
  1. hash分区
    https://dev.mysql.com/doc/refman/8.0/en/partitioning-hash.html
-- 根据store_id取hash值 再模上4  跟HashMap很像  
CREATE TABLE employees (
    id INT NOT NULL,
    fname VARCHAR(30),
    lname VARCHAR(30),
    hired DATE NOT NULL DEFAULT '1970-01-01',
    separated DATE NOT NULL DEFAULT '9999-12-31',
    job_code INT,
    store_id INT
)
PARTITION BY HASH(store_id)
PARTITIONS 4;
  1. KEY分区
    https://dev.mysql.com/doc/refman/8.0/en/partitioning-key.html
    就是hash的一个变种 使用主键+唯一键
-- 不指定列 
CREATE TABLE k1 (
    id INT NOT NULL PRIMARY KEY,
    name VARCHAR(20)
)
PARTITION BY KEY()
PARTITIONS 2;

-- 指定列
CREATE TABLE tm1 (
    s1 CHAR(32) PRIMARY KEY
)
PARTITION BY KEY(s1)
PARTITIONS 10;
  1. 子分区
    https://dev.mysql.com/doc/refman/8.0/en/partitioning-subpartitions.html
CREATE TABLE ts (id INT, purchased DATE)
    PARTITION BY RANGE( YEAR(purchased) )
    SUBPARTITION BY HASH( TO_DAYS(purchased) )
    SUBPARTITIONS 2 (
        PARTITION p0 VALUES LESS THAN (1990),
        PARTITION p1 VALUES LESS THAN (2000),
        PARTITION p2 VALUES LESS THAN MAXVALUE
    );

就是在分区的基础上 再进行分区

这是一般分区格式:
mysql优化 个人笔记 (mysql 进阶索引 ) 非礼勿扰 -m15_第17张图片

这是子分区格式:
mysql优化 个人笔记 (mysql 进阶索引 ) 非礼勿扰 -m15_第18张图片

6. 如何使用分区表

  1. 全量扫描数据 不要任何索引
    使用简单的分区方式存放表,不需要任何索引,根据分区规则大致定位需要的数据位置,通过使用where条件,将数据限制在几个分区范围内。这种策略适合正常方式访问大量数据

  2. 索引数据 并分离热点
    比如说最近一个月是热数据 之前月份是历史数据
    能够使用索引 也能使用缓存

7. 分区表注意事项

  1. null值可能对分区无效
  2. 分区列和索引不匹配 可能会导致查询无法进行分区过滤
  3. 选择分区的时候 成本可能很高 粒度越小可能成本越高
  4. 打开并锁住所有底层表的成本可能会很高
  5. 维护分区表成本可能很高

你可能感兴趣的:(mysql)