MySQL体系结构、优化与锁

2020-12-30  本文已影响0人  攻城老狮

一 MySQL体系结构

1.1 体系结构图

image-20201224184601732.png

1.2 Server的各组成部分

二 存储引擎

2.1 各种存储引擎的特性

image-20201224185826128.png

2.2 查看存储引擎的相关命令

# 查看支持的存储引擎
show engines;
# 查看mysql默认的存储引擎
show variables like '%storage_engine%';

2.3 InnoDB

.frm文件:保存表结构
.ibd文件:保存索引和数据

2.4 MyISAM

.frm文件:保存表结构
.MYD文件:存储数据
.MYI文件:存储索引

2.5 存储引擎选择建议

三 数据库的优化措施

3.0 配置测试环境的库表

CREATE DATABASE demo01;
USE demo01;

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`)
)engine=innodb default charset=utf8mb4;

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');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`,
`address`, `createtime`) values('oppo','OPPO科技有限公司','OPPO官方旗舰
店','e10adc3949ba59abbe56e057f20f883e','0','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`,
`address`, `createtime`) values('ourpalm','掌趣科技股份有限公司','掌趣小
店','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`,
`address`, `createtime`) values('qiandu','千度科技','千度小
店','e10adc3949ba59abbe56e057f20f883e','2','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`,
`address`, `createtime`) values('sina','新浪科技有限公司','新浪官方旗舰
店','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`,
`address`, `createtime`) values('xiaomi','小米科技','小米官方旗舰
店','e10adc3949ba59abbe56e057f20f883e','1','西安市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`,
`address`, `createtime`) values('yijia','宜家家居','宜家家居旗舰
店','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00');

3.1 分析SQL的工具

  1. 查看SQL执行频率
# 查看每个xxx语句执行的次数
show status like 'Com_______'; # 例如:Com_select 执行select的操作次数
# 查看InnoDB存储引擎的相关操作执行次数
show status like 'Innodb_rows_%'; # 例如:Inoodb_rows_read 执行select返回的行数
  1. 定位低效SQL
# 查看当前MySQL在进行的线程
show processlist;
  1. explain分析执行计划
explain select * from tb_seller where id = 1;
image-20201224191715049.png
  1. profiling分析语句执行时间
# 查看profiling的开启情况
select @@profiling;
# 开启
set profiling=1;

# 执行一些操作
show databases;
show tables;
select count(*) from tb_seller;

# 查看sql执行耗时
show profiles;

3.2 索引

create index idx_seller_name_sta_addr on tb_seller(name,status,address);
  1. 全值匹配,对索引所有列指定具体值
explain select * from tb_seller where name='小米科技' and status='1' and address='北京市';
image-20201224205659065.png
  1. 最左前缀法则——查询从索引的最左前列开始,并且不跳过索引中的列
# 跳过了中间的一个索引列,导致只使用了name的索引
EXPLAIN SELECT * FROM tb_seller WHERE NAME='小米科技' AND  address='北京市';
image-20201224210053884.png
# 缺失最左侧索引,整个sql不走索引
EXPLAIN SELECT * FROM tb_seller WHERE  STATUS='1' AND address='北京市';
image-20201224210153068.png
  1. 范围查询右边的列,不走索引
# address没走索引
EXPLAIN SELECT * FROM tb_seller WHERE NAME='小米科技' AND STATUS>'1' AND address='北京市';
image-20201224210351598.png
  1. 不要在索引列上进行运算操作, 索引将失效
# 在name索引列做运算操作,索引失效
EXPLAIN SELECT * FROM tb_seller WHERE SUBSTRING(NAME,3,2)='科技';
image-20201224210551035.png
  1. 字符串不加单引号,造成索引失效
# status是字符串类型,但没加单引号
EXPLAIN SELECT * FROM tb_seller WHERE NAME='小米科技' AND STATUS=1;
image-20201224210731488.png
  1. 尽量使用覆盖索引,避免select *
# 只查询有索引的列效率更高,会在extra中显示使用索引,若多加password没有索引的这个字段则效率降低
EXPLAIN SELECT NAME,STATUS,address FROM tb_seller WHERE NAME='小米科技' AND STATUS='1' AND address='北京市';
image-20201224211017450.png
  1. 用or分割开的条件,不走索引
# 用OR连接两个索引条件,也不会走索引
EXPLAIN SELECT * FROM tb_seller WHERE NAME='黑马程序员' OR STATUS = '1';
image-20201224211459020.png
  1. 如果MySQL评估使用索引比全表更慢,则不使用索引
CREATE INDEX idx_seller_addr ON tb_seller(address);
EXPLAIN SELECT * FROM tb_seller WHERE address='西安市'; # 走索引(数据稀缺)
EXPLAIN SELECT * FROM tb_seller WHERE address='北京市'; # 不走索引(数据大量存在)
  1. 单列索引和复合索引——尽量使用复合索引,而少使用单列索引
# 创建复合索引
create index idx_name_sta_address on tb_seller(name, status, address);
就相当于创建了三个索引 :
name
name + status
name + status + address

# 创建单列索引
create index idx_seller_name on tb_seller(name);
create index idx_seller_status on tb_seller(status);
create index idx_seller_address on tb_seller(address);
数据库会选择一个最优的索引(辨识度最高索引)来使用,并不会使用全部索引。而复合索引可能会使用到多个字段的索引故效率更高。

3.3 SQL优化

  1. 大批量插入数据
set unique_check=0;
# 插入数据
set unique_check=1;
set autocommit=0;
# 插入数据
set autocommit=1;
  1. 优化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');
start transaction;
insert into tb_test values(1,'Tom');
insert into tb_test values(2,'Cat');
insert into tb_test values(3,'Jerry');
commit;
  1. 优化order by语句
EXPLAIN SELECT * FROM tb_seller ORDER BY NAME DESC; # using filesort 效率低
EXPLAIN SELECT NAME FROM tb_seller ORDER BY NAME DESC; # using index 效率高
  1. 优化group by语句
# 默认group by 之后会进行 order by的操作 故性能较低
EXPLAIN SELECT COUNT(*) FROM tb_seller GROUP BY address;
image-20201224223025841.png
# 去除order by的性能影响
EXPLAIN SELECT COUNT(*) FROM tb_seller GROUP BY address ORDER BY NULL;
image-20201224223109126.png
# 创建索引后,效率提升 变为 using index
CREATE INDEX idx_seller_addr ON tb_seller(address);
EXPLAIN SELECT COUNT(*) FROM tb_seller GROUP BY address ORDER BY NULL;
image-20201224223338319.png
  1. 优化嵌套查询——连接(Join)查询之所以更有效率一些 ,是因为MySQL不需要在内存中创建临时表来完成这个逻辑上需要两个步骤的查询工作
  2. 优化分页查询—— 一般分页查询时,通过创建覆盖索引能够比较好地提高性能。一个常见又非常头疼的问题就是 limit 2000000,10 , 此时需要MySQL排序前2000010 记录,仅仅返回2000000 - 2000010 的记录,其他记录丢弃,查询排序的代价非常大 。

3.4 查询缓存优化

  1. 请求流程
1. 客户端发送一条查询给服务器;
2. 服务器先会检查查询缓存,如果命中了缓存,则立即返回存储在缓存中的结果。否则进入下一阶段;
3. 服务器端进行SQL解析、预处理,再由优化器生成对应的执行计划;
4. MySQL根据优化器生成的执行计划,调用存储引擎的API来执行查询;
5. 将结果返回给客户端。
  1. 查询缓存的配置
# 若未开启或者缓存大小为0,则需要在my.cnf中配置相关参数,并重启mysql服务
# 查看当前的MySQL数据库是否支持查询缓存
SHOW VARIABLES LIKE 'have_query_cache';
# 查看当前MySQL是否开启了查询缓存
SHOW VARIABLES LIKE 'query_cache_type';
# 查看查询缓存的占用大小
SHOW VARIABLES LIKE 'query_cache_size';
# 查看查询缓存的状态变量:
SHOW STATUS LIKE 'Qcache%';
  1. 查询缓存失效的情况
SQL1 : select count(*) from tb_seller;
SQL2 : Select count(*) from tb_seller;
SELECT user();
SELECT database();
select 'A';
 select * from information_schema.engines;

3.5 内存管理与优化

  1. 内存优化原则

四 应用层面优化

4.1 使用连接池

建立连接的代价是比较昂贵的,因为我们频繁的创建关闭连接,是比较耗费资源的,需要建立数据库连接池,以提高访问的性能。

4.2 减少对MySQL的访问

  1. 业务层上减少对数据进行重复检索

编写业务时,需要能够理清对数据库的访问逻辑。能够一次连接就获取到结果的,就不用两次连接,这样可以大大减少对数据库无用的重复请求。

  1. 增加cache层

可以在应用中增加 缓存 层来达到减轻数据库负担的目的。缓存层有很多种,也有很多实现方式, 只要能达到降低数据库的负担又能满足应用需求就可以。可用选择使用框架(Mybatis, Hibernate)提供的一级缓存/二级缓存,或者使用redis数据库来缓存数据。

4.3 负载均衡

  1. 利用MySQL复制分流查询

通过MySQL的主从复制,实现读写分离,使增删改操作走主节点,查询操作走从节点,从而可以降低单台服务器的读写压力。

  1. 采用分布式数据库架构

分布式数据库架构适合大数据量、负载高的情况,它有良好的拓展性和高可用性。通过在多台服务器之间分布数据,可以实现在多台服务器之间的负载均衡,提高访问效率。

五 MySQL锁机制

5.1 并发参数调整

1.max_connections:控制连接到MySQL数据库的最大数量,默认为151
2.back_log:控制积压的请求栈大小,若连接数达到max_connections则新请求会被存入栈中。若等待的连接数量超过back_log则不被授予连接。默认50+(max_connections/5) = 80
3.table_open_cache:控制所有SQL语句执行线程可以打开表缓存的数量,需要根据最大连接数和涉及到的表的最大数量来设定 max_connections*N
4.thread_cache_size:控制缓存客户服务线程的数量
5.innodb_lock_wait_timeout:设置Innodb事务等待行锁的时间,默认为50ms

5.2 锁分类

从对数据操作的颗粒度分:

从对数据操作的类型分:

5.3 MyISAM表锁

  1. 环境准备
CREATE TABLE `tb_book` (
`id` INT(11) auto_increment,
`name` VARCHAR(50) DEFAULT NULL,
`publish_time` DATE DEFAULT NULL,
`status` CHAR(1) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=myisam DEFAULT CHARSET=utf8 ;
INSERT INTO tb_book (id, name, publish_time, status) VALUES(NULL,'java编程思想','2088-08-01','1');
INSERT INTO tb_book (id, name, publish_time, status) VALUES(NULL,'solr编程思想','2088-08-08','0');
CREATE TABLE `tb_user` (
`id` INT(11) auto_increment,
`name` VARCHAR(50) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=myisam DEFAULT CHARSET=utf8 ;
INSERT INTO tb_user (id, name) VALUES(NULL,'令狐冲');
INSERT INTO tb_user (id, name) VALUES(NULL,'田伯光');

  1. 读锁案例

客户端1:

# 获取tb_book 表的读锁
lock table tb_book read;
# 执行查询操作
select * from tb_book; # 正常执行

客户端2:

# 执行查询操作
select * from tb_book; # 正常执行

客户端1:

# 查询未锁定的表
select * from tb_user; # 无法操作

ERROR 1100 (HY000): Table 'tb_user' was not locked with LOCK TABLES

客户端2:

# 查询未锁定的表
select * from tb_user; # 正常执行

客户端1:

# 执行插入操作
insert into tb_book values(null,'mysql','2020-12-28','1');

ERROR 1099 (HY000): Table 'tb_book' was locked with a READ lock and can't be updated

客户端2:

# 执行插入操作
insert into tb_book values(null,'mysql','2020-12-28','1'); # 阻塞,等待客户端1释放读锁

客户端1:

# 释放读锁
unlock tables; # 客户端1释放读锁后,客户端2成功插入数据
  1. 写锁案例

客户端1:

# 获取tb_book表的写锁
lock table tb_book write;
# 执行查询操作
select * from tb_book; # 正常执行
# 执行更新操作
update tb_book set name='java' where id=1; # 正常执行

客户端2:

# 执行查询操作
select * from tb_book; # 阻塞

客户端1:

# 释放写锁
unlock tables; # 客户端1释放写锁后,客户端2成功查询数据

总结:读锁会阻塞写,但是不会阻塞读。而写锁,则既会阻塞读,又会阻塞写

5.4 InnoDB行锁

  1. 环境准备
create table test_innodb_lock(
id int(11),
name varchar(16),
sex varchar(1)
)engine = innodb default charset=utf8;
insert into test_innodb_lock values(1,'100','1');
insert into test_innodb_lock values(3,'3','1');
insert into test_innodb_lock values(4,'400','0');
insert into test_innodb_lock values(5,'500','1');
insert into test_innodb_lock values(6,'600','0');
insert into test_innodb_lock values(7,'700','0');
insert into test_innodb_lock values(8,'800','1');
insert into test_innodb_lock values(9,'900','1');
insert into test_innodb_lock values(1,'200','0');

create index idx_test_innodb_lock_id on test_innodb_lock(id);
create index idx_test_innodb_lock_name on test_innodb_lock(name);
  1. 行锁案例

客户端1,2:

# 关闭自动提交
set autocommit=0;
# 查询数据
select * from test_innodb_lock; # 正常查询全部数据
# 查询id=3的数据
select * from test_inoodb_lock where id=3; # 正常执行
# 更新id=3的数据,相同行的数据
update test_innodb_lock set name='test' where id=3; # 客户端1执行成功,客户端2阻塞
# 客户端1提交事务
commit; # 客户端2的更新也执行成功

# 若更新不同行,由于是行锁不会发生阻塞现象

客户端1,2:

# 删除索引
drop index idx_test_innodb_lock_name on test_innodb_lock;
# 查看是否删除成功
show index from test_innodb_lock\G
# 关闭自动提交
set autocommit=0;
# 更新name=400的数据
# 客户端1
update test_innodb_lock set sex='2' where name='400'; # 由于已经删除name的索引,降为表锁
# 客户端2
update test_innodb_lock set sex='2' where id=9; # 操作其他行被阻塞,降级为表锁
# 客户端1提交事务
commit; # 客户端2的更新也执行成功
  1. 间隙锁

当我们用范围条件,而不是使用相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据进行加锁; 对于键值在条件范围内但并不存在的记录,叫做 "间隙(GAP)" , InnoDB也会对这个 "间隙" 加锁,这种锁机制就是所谓的 间隙锁(Next-Key锁) 。

六 MySQL常用工具及日志

6.1 mysql

mysql [options] [database]
参数 :
-u, 指定用户名
-p, 指定密码
-h, 指定服务器IP或
-P, 指定连接端口
-e, 执行SQL语句并退出

# 示例
mysql -u root demo01 -e "select * from tb_book" -p

6.2 错误日志

它记录了当 mysqld 启动和停止时,以及服务器在运行过程中发生任何严重错误时的相关信息

# 查看日志位置
show variables like 'log_error%';
# 查看日志内容
tail -f 位置

6.3 二进制日志

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

  1. 开启二进制日志并重新启动mysql服务
#配置开启binlog日志
server-id=1
log_bin=mysqlbin
log-bin-index=mysqlbin.index
  1. 插入一条数据
insert into tb_book values(null,'Lucene','2088-05-01','0');
  1. 进入日志存储的目录,并查看日志文件
cd /var/lib/mysql
mysqlbinlog --no-defaults  mysqlbin
  1. 删除日志
Reset Master
上一篇 下一篇

猜你喜欢

热点阅读