MySQL高级丶

文件

  • frm文件:存放表结构
  • myd文件:存放表数据
  • myi文件:存放表索引
  • 二进制日志文件log-bin
  • 错误日志log-error
  • 查询日志log

逻辑架构

分层可拔插(存储引擎也是可插拔的)

  1. 连接层
  2. 服务层
  3. 引擎层
  4. 存储层

存储引擎

show engines;
show variables like '%storage_engine%';
对比项 MyISAM InnoDB
主外键 不支持 支持
事务 不支持 支持
行表锁 表锁,即使操作一行记录也会锁住整个表,不适合高并发的操作 行锁,操作时只锁某一行,不对其它行有影响,适合高并发的操作
缓存 只缓存索引,不缓存绅士数据 不仅缓存索引,还要缓存真实数据,对内存要求较高,而且内存大小对性能有决定性的影响
表空间
关注点 性能 事务
默认安装 Y Y

索引优化

索引是高效获取数据的数据结构。排序&查询

# 显示索引
show index from tbl_emp;
# 删除索引
alter table  table_name drop index index_name
# 删除主键索引
alter table  table_name drop primary key
#######################
# 四种索引
#######################
# 1.普通索引
alter table table_name add index index_username (username)
# 2.唯一索引 (字段不允许重复,可有多个NULL)
alter table  table_name add unique index_username (username)
# 3.主键索引 (字段不允许重复,不允许NULL)
alter table table_name add primary key  (username)
# 4.全文索引
alter table  table_name add fulltext index_username (username)

索引分类:单值索引、唯一索引、复合索引

索引结构:BTree索引、Hash索引、full-text索引、R-Tree索引

需要建索引的情况:

  • 主键自动建立唯一索引
  • 频繁作为查询条件的字段应该创建索引
  • 查询中与其他表关联的字段,外键关系建立索引
  • 单键索引/组合索引的选择问题(高并发下倾向创建组合索引)
  • 查询中的排序字段,排序字段若通过索引去访问将大大提高排序速度(order by)
  • 查询中统计或者分组字段

不要建索引的情况:

  • 表记录太少(三百万)
  • 经常增删改的表/频繁更新的字段不适合创建索引
  • where条件里用不到的字段不要创建索引
  • 数据重复且分布平均的字段(索引选择性越接近于1(比如性别字段的另一个极端),效率越高)

性能分析

  • Mysql Query Optimizer查询优化器
  • MySQL常见瓶颈
    • CPU:CPU饱和一般发生在数据装入内存或从磁盘上读取数据的时候
    • IO:磁盘I/O瓶颈发生在装入数据远大于内存容量的时候
    • 服务器硬件性能瓶颈:top、free、iostat和vmstat查看系统的性能状态
  • Explain执行计划
    • 表的读取顺序(id)
    • 数据读取操作的操作类型(select_type)
    • 哪些索引可以使用(possible_keys)
    • 哪些索引被实际使用(key)
    • 表之间的引用(ref)
    • 每张表有多少行被优化器查询

Explain执行计划字段解释

  • id:select查询的序列号,包含一组数字,表示查询中执行select子句或操作表的顺序
    • id相同,(table)执行顺序由上到下
    • id不同,如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行
    • id同时存在相同与不同的情况,相同的部分,可以认为是一组,从上往下顺序执行;在所有组中,id值越大,优先级越高,越先执行
  • select_type:
    • SIMPLE:简单的select查询,查询中不包含子查询或者UNION
    • PRIMARY:查询中若包含任何复杂的子部分,最外层查询就被标记为PRIMARY
    • SUBQUERY:在select或where列表中包含了子查询
    • DERIVED:在from列表中包含的子查询标记为DERIVED(衍生),MySQL会递归查询这些子查询,把结果放在临时表里
    • UNION:若第二个select出现在union之后,则标记为UNION;若union包含在from子句的子查询中,外层select将被标记为DERIVED
    • UNION RESULT:从UNION表获取结果的select
  • table:显示这一行的数据是关于哪张表的
  • type:访问类型,最好到最差:system>const>eq_ref>ref>range>index>ALL
    • system:表只有一行记录(等于系统表),这是const类型的特例,平时不会出现
    • const:表示通过索引一次就找到了,const用于比较primary key或者unique索引。因为只匹配一行数据,所以很快。如将主键置于where列表中,MySQL就能将该查询转换为一个常量
    • eq_ref:唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见于主键或唯一索引扫描
    • ref:非唯一性索引扫描,返回匹配某个单独值的所有行,本质上也是一种索引访问,它返回所有匹配某个单独值的行,然而,它可能会找到多个符合条件的行,所以它应该属于查找和扫描的混合体
    • range:只检索给定范围的行,使用一个索引来选择行。key 列显示使用了哪个索引,一般是在where语句中出现between、<、>、in等查询,这种范围性索引扫描比全表扫描要好。
    • index:遍历整个索引,比全表扫描要好点
    • ALL:最差,全表扫描,考虑建索引
  • possible_keys:显示可能应用在这张表中的索引,查询涉及到的字段上若存在索引,则将索引列出(不一定被查询实际使用)
  • key:实际使用的索引(查询中若使用了覆盖索引,则该索引仅出现在key列表中)
  • key_len:表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度,在不损失精度的情况下,长度越短越好。显示的值为索引字段的最大可能长度,并非实际使用长度,即key_len是根据表定义计算而得,不是通过表内检索出的
  • ref:显示索引的哪一列被使用了,如果可能,是一个常数,哪些列或常量被用于查找索引列上的值。
  • rows:根据表统计信息及索引选用情况,大致估算出找到所需的记录所需要读取的函数
  • Extra:剩下的执行情况就写在这个字段里边(十分重要的额外信息)
    • using filesort:文件排序,九死一生!mysql会对数据使用一个外部的索引排序,而不是按照表内的索引数据进行读取。MySQL中无法利用索引完成的排序操作称为”文件排序“
    • using temporary:新建了内部的临时表,十死无生。使用了临时表保存中间结果,MySQL在对查询结果排序时使用临时表,常见于order by和group by。(复合索引的个数和顺序要严格一致)
    • using index:好信号!表示相应的select操作中使用了覆盖索引,避免访问了表的数据行,效率不错。如果同时出现using where,表明索引被用来执行索引键值的查找;如果没有同时出现using where,表明索引用来读取数据而非执行查找动作。覆盖索引:查询的列与所建的复合索引吻合。
    • using where
    • using join buffer
    • impossible where
    • select table optimized away
    • distinct

索引分析案例

Explain分析执行顺序

image-20201129095802553

索引单表优化案例

CREATE TABLE IF NOT EXISTS `article`(
`id` INT(10) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
`author_id` INT (10) UNSIGNED NOT NULL,
`category_id` INT(10) UNSIGNED NOT NULL , 
`views` INT(10) UNSIGNED NOT NULL , 
`comments` INT(10) UNSIGNED NOT NULL,
`title` VARBINARY(255) NOT NULL,
`content` TEXT NOT NULL
);

insert into `article`(author_id,category_id,views,comments,title,content) values
(1,1,1,1,'1','1'),
(2,2,2,2,'2','2'),
(1,1,3,3,'3','3');

查询category_id为1且comments大于1的情况下views最多的autho_id

image-20201129103236720

image-20201129103322551

索引两表优化案例

CREATE TABLE IF NOT EXISTS `class`(
`id` INT(10) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
`card` INT (10) UNSIGNED NOT NULL
);
CREATE TABLE IF NOT EXISTS `book`(
`bookid` INT(10) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
`card` INT (10) UNSIGNED NOT NULL
);

# 插入数据略(见文末)

左连接,索引建在右表;右连接,索引建在左表;左右位置可以调换

select * from book left join class on book.card=class.card
explain select * from book left join class on book.card=class.card
alter table `class` add index Y(`card`)
drop index Y on `class`

索引三表优化案例

CREATE TABLE IF NOT EXISTS `phone`(
`phoneid` INT(10) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
`card` INT (10) UNSIGNED NOT NULL
)ENGINE = INNODB;

# 插入数据略(见文末)

这种情况怎么处理

explain select * from class left join book on class.card=book.card left join phone on class.card=phone.card;
alter table `book` add index Y(`card`)
alter table `phone` add index Z(`card`)

结论:

  • 尽可能减少Join语句中NestedLoop的循环总次数(永远用小的结果集驱动大的结果集小表驱动大表)
  • 优先优化NestedLoop的内层循环
  • 保证join语句中被驱动表上join条件字段已经被索引
  • 当无法保证被驱动表的join条件字段被索引且内存资源充足的前提下,不要太吝惜JoinBuffer的设置

索引失效

CREATE TABLE staffs(
id INT PRIMARY KEY AUTO_INCREMENT,
`name` VARCHAR(24)NOT NULL DEFAULT'' COMMENT'姓名',
`age` INT NOT NULL DEFAULT 0 COMMENT'年龄',
`pos` VARCHAR(20) NOT NULL DEFAULT'' COMMENT'职位',
`add_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT'入职时间'
)CHARSET utf8 COMMENT'员工记录表';

insert into staffs(NAME,age,pos,add_time) values('z3',22,'manager',NOW());
insert into staffs(NAME,age,pos,add_time) values('July',23,'dev',NOW());
insert into staffs(NAME,age,pos,add_time) values('2000',23,'dev',NOW());

alter table `staffs` add index idx_nameAgePos(`name`,age,pos);

CREATE TABLE tbl_user(
`id` INT(11) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(20) DEFAULT NULL,
`age`INT(11) DEFAULT NULL,
`email` VARCHAR(20) DEFAULT NULL,
PRIMARY KEY(`id`)
)ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

insert into tbl_user(NAME,age,email) values('1aa1',21,'b@163.com');
insert into tbl_user(NAME,age,email) values('2aa2',222,'a@163.com');
insert into tbl_user(NAME,age,email) values('3aa3',265,'c@163.com');
insert into tbl_user(NAME,age,email) values('4aa4',21,'d@163.com');

索引失效的情况

  1. 全值匹配我最爱:索引中的列和where中的条件刚好重合
  2. 最佳左前缀法则:根据复合索引,从左到右写where条件,最左边的不能少(带头大哥不能死,中间兄弟不能断)
  3. 不在索引列上做任何操作(计算,函数,自动或手动类型转换),会导致索引失效而转向全表扫描
  4. 不能使用索引中范围条件右边的列:and b>0用了范围条件了,b后面的c索引就失效了
  5. 尽量使用覆盖索引(只访问索引的查询(索引列和查询列一致)),减少select*
  6. mysql在使用不等于(!=或者<>)的时候无法使用索引会导致全表扫描
  7. is null,is not null也无法使用索引
  8. like以通配符%开头,MySQL索引会失效而变成全表扫描(想解决用覆盖索引)
  9. 字符串不加单引号索引失效
  10. 少用or,用它来连接时索引会失效

定值(常量)、范围(后会失效)、排序,一般order by是给个范围,group by基本都需要进行排序,会产生临时表

一般性建议

  • 对于单键索引,尽量选择针对当前索引query过滤性更好的索引
  • 在选择组合索引时,当前query中过滤性最好的字段在索引字段顺序中,位置越靠前越好
  • 在选择组合索引时,尽量选择可能包含当前query中的where子句中更多字段的索引
  • 尽可能通过分析系统信息和调整query的写法来达到选择合适索引的目的

慢SQL处理流程

  1. 慢查询的开启并捕获
  2. explain + 慢SQL分析
  3. Show Profile查询SQL在mysql服务器里面的执行细节和生命周期情况
  4. sql数据库服务器的参数调优

查询截取分析

查询优化

1、永远小表驱动大表

image-20201129204806302

2、order by关键字优化(为排序使用索引)

  • 尽量使用index方式排序,避免使用filesort方式排序

  • filesort有两种算法:单路排序 和 双路排序

    • 双路排序:老版本的mysql,两次扫描磁盘
    • 单路排序:有风险
  • orderby时select*是大忌

  • 数据库服务器参数设置sort_buffer_size/max_length_for_sort_data

image-20201129212715874

3、group by关键字优化

  • 实质是先排序后进行分组,遵照索引建的最佳左前缀
  • 当无法使用索引列,增大sort_buffer_size/max_length_for_sort_data
  • where高于having,能写在where中的不要写在having中

慢查询日志

# 查看开启状况
show variables like '%slow_query_log%';
# 查看当前多少秒算慢sql
show variables like '%long_query_time%';
# 开启
set global slow_query_log=1;
# 设置慢的时间
set global long_query_time=3;
# 记录日志
select sleep(4);
# 另外mysqldumpslow工具

批量数据脚本

往表里插入一千万条数据

# 函数及存储过程(见文末)

show variables like '%log_bin_trust_function_creators%';
set global log_bin_trust_function_creators=1;

delimiter ;# 恢复设置
select * from dept;
call insert_dept(100,10);
select * from emp;
call insert_emp(100001,500000);

Show Profile

是mysql提供可以用来分析当前会话中语句执行的资源消耗情况,可以用于sql的调优测量

# 默认是关闭,使用前需要开启
show variables like 'profiling';
set profiling=on;
show profiles;
show profile cpu,block io for query 3;
# 出现以下4项必须优化
# converting HEAP to MyISAM
# Creating tmp table
# Copying to tmp on disk
# locked

全局查询日志

永远不要在生产环境开启此功能

配置启用

# 在MySQL的my.cnf中,设置如下
# 开启
general_log=1
# 记录日志文件的路径
general_log_file-/path/logfile
# 输出格式
log_output-FILE

编码启用

set global general_log=1;
set global log_output='TABLE';
select * from mysql.general_log;

MySQL锁机制

读锁(共享锁)/写锁(排它锁)/表锁(偏读)/行锁(偏写)/页锁

  • 表锁MyISAM
# 简而言之:读锁会阻塞写,但是不会阻塞读;而写锁则会把读和写都阻塞。
select * from mylock;
lock table mylock read ,book write;
show open tables;
unlock tables;
lock table mylock read;
select * from mylock;
# 当前session不能操作,其他session会阻塞等待,直到unlock tables;
update mylock set name='a2' where id=1;
select * from book;
lock table mylock write;
update mylock set name='a3' where id=1;
show status like 'table%';# 反映锁竞争的情况
  • 行锁InnoDB
# 开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高;(InnoDB与MyISAM最大的不同:一是支持事务,二是采用了行级锁)
CREATE INDEX test_innodb_a_ind ON test_innodb_lock(a);
CREATE INDEX test_innodb_lock_b_ind ON test_innodb_lock(b);
SET autocommit=0;# 不自动提交事务,commit之前就会自动锁住操作的行
select * from test_innodb_lock;
update test_innodb_lock set b='4001' where a=4;
COMMIT;
  • 无索引行锁升级为表锁(varchar不加单引号索引失效进而行锁升级为表锁)

  • 间隙锁危害(id不连续时,范围操作时,可能影响其他session执行)

  • 如何锁定一行

SET autocommit=0;
begin;
select * from test_innodb_loc where a=8 for update;//锁定行
commit;
  • 查看行锁情况
show status like 'innodb_row_lock%';
  • 行锁相关优化建议
    • 尽可能让所有数据检索都通过索引来完成,避免无索引行锁升级为表锁
    • 合理涉及索引,尽可能缩小锁的范围
    • 尽可能使用较少检索条件,避免间隙锁
    • 尽量控制事务大小,减少锁定资源量和时间长度
    • 尽可能低级别事务隔离

主从复制

slave会从master读取binlog来进行数据同步

  1. master将改变记录到二进制日志(binary log),这些记录过程叫做二进制日志事件binary log events
  2. slave将master的binary log events拷贝到它的中继日志(relay log)
  3. slave重做中继日志中的事件,将改变应用到自己的数据库中,MySQL复制是异步的且串行化的

配置文件进行配置主从复制(授权;show master/slave status;stop slave;)

课程|SQL|

posted @ 2020-11-30 01:53  夜雨秋池  阅读(89)  评论(0编辑  收藏  举报