Mysql中有很多术语,要先浅浅了解一下,便于我们理解下面的东西
Mysql自带了四个数据库(information_schema、mysql、performance_schema、sys)
information_schema
虚拟库,不占用磁盘空间,存储元数据
换句话说,它保存着关于MySQL服务器所维护的所有其他数据库的信息,它其中所维护的表指的是视图,而不是基本表
以下是该库里的表
解释几张比较重要的表:
show tables from schemaname
(schemaname替换成需要查询的数据库名称)的结果来自于这张表show columns from schemaname.tablename
的结果取自这张表show index from schemaname.tablename
的结果取自此表SHOW CHARACTER SET
show COLLATION
显示的前两个显示字段mysql
mysql的核心数据库,主要负责存储数据库用户、权限设置、关键字等mysql自己需要使用的控制和管理信息 (常用的,我们可以在mysql.user表中修改root用户的密码)
performance_schema
主要用于收集数据库服务器性能参数。并且库里表的存储引擎均为PERFORMANCE_SCHEMA,而用户是不能创建存储引擎为 PERFORMANCE_SCHEMA 的表。
sys
sys库中所有的数据源均来自performance_schema,目的是把performance_schema的复杂度降低,让DBA能更好的阅读这个库里的内容,从而快速了解数据库的运行情况
mysql的每个数据库都对应存放在一个与数据库同名的文件夹中,MySQL数据库文件包括MySQL所建数据库文件和MySQL所用存储引擎创建的数据库文件
其余文件我们根据存储引擎的不同分开来说:
mysql中有一个存储引擎的概念,针对不同的需求可以选择最优的存储引擎
特点 | MyISAM | InnoDB | MEMORY | MERGE | NDB |
---|---|---|---|---|---|
存储限制 | 有 | 64TB | 有 | 没有 | 有 |
事务安全 | 支持 | ||||
锁机制 | 表级锁 | 行级锁 | 表级锁 | 表级锁 | 行级锁 |
B树索引 | 支持 | 支持 | 支持 | 支持 | 支持 |
哈希索引 | 支持 | 支持 | |||
全文索引 | 支持 | ||||
集群索引 | 支持 | ||||
数据缓存 | 支持 | 支持 | 支持 | ||
索引缓存 | 支持 | 支持 | 支持 | 支持 | 支持 |
数据可压缩 | 支持 | ||||
空间使用 | 低 | 高 | N/A | 低 | 低 |
内存使用 | 低 | 高 | 中等 | 低 | 高 |
批量插入的速度 | 高 | 低 | 高 | 高 | 高 |
支持外键 | 支持 |
重点理解MyISAM、InnoDB,其余的只要了解一下即可
start transaction
开启事务-- Table "article_0" DDL
CREATE TABLE `article_1` (
`id` bigint(20) NOT NULL,
`subject` varchar(200) NOT NULL,
`content` text NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-- Table "article_1" DDL
CREATE TABLE `article_2` (
`id` bigint(20) NOT NULL,
`subject` varchar(200) NOT NULL,
`content` text NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
然后建立一个总表
-- Table "article_total" DDL
CREATE TABLE `article_total` (
`id` bigint(20) NOT NULL,
`subject` varchar(200) NOT NULL,
`content` text NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MRG_MyISAM DEFAULT CHARSET=utf8 UNION=(`article_1`,`article_2`);
那么article_total表的内容就包含了article_1,article_2的内容
当执行 select * from article_total
表的结果就是各个表数据合并的内容
其实就是一句话:合适的才是最好的
除非我们需要InnoDB不具备的一些特性,并且没有其他办法代替,否则都应该优先考虑InnoDB,又或者,不需要InnoDB的特性,并且其他引擎更加合适当前的情况,例如多读少些,对数据恢复的要求不高,反而对存储空间要求较高
尤其需要注意的是:不要低估数据崩溃后恢复数据的重要性,MyISAM将数据写入内存中,然后等待操作系统定期将数据刷回磁盘
以下给出各引擎较为适合被选择的场景:
MyISAM:如果应用是以读操作和插入操作为主,只有很少的更新和删除操作,并且对事务的完整性、并发性要求不是很高,那么就请选择MyISAM吧,MyISAM是在Web、数据仓储和其他应用环境下最常使用的存储引擎之一
InnoDB(默认引擎) :用于事务处理应用程序,支持外键。如果应用对事务的完整性有较高要求,在并发条件下要求数据的一致性,数据操作除了插入和查询外,还有很多更新、删除操作,那么InnoDB是较为合适的选择。
InnoDB存储引擎除了可以有效降低由于删除和更新导致的锁定,还可以确保事务的完整提交和回滚,对于类似计费系统或财务系统等对数据准确性要求比较高的系统,InnoDB都是合适的选择。因为其采用事务日志,在系统崩溃后易于完成数据恢复
MEMORY:将所有的数据都保存在内存中,且支持HASH索引,在需要快速定位记录和其他类似数据的环境下,可提供极快的访问。
MEMORY的缺陷是对表的大小有限制,太大的表无法缓存在内存中,并且不支持BLOB、TEXT类型,而且使用的是表级锁,并发性能很低,现在很少使用,一般作为中间表来保存中间数据
MERGE:用于将一系列等同的MyISAM表以逻辑方式组合在一起,并作为一个对象引用它们。MERGE表的优点在于可以突破单个MyISAM表大小的限制,并且通过将不同的表分布在多个磁盘上,可以有效地改善MERGE表的访问效率。这对于诸如数据仓储等VLDB环境十分适合
首先我们要了解为什么需要优化SQL
举个例子:一个项目的上线初期,由于业务数据量相对较少,一些SQL的执行效率对程序运行效率的影响不太明显,而开发和运维人员也无法准确判断SQL对程序的运行效率的影响有多大,但随着时间的积累,业务数据量的增多,SQL的执行效率对程序的运行效率的影响逐渐增大,那么此时我们就要对SQL进行优化来改善程序运行慢的问题
show [session|global] status
命令可以查询到服务器状态信息show [session|global] status
可以根据需要加上的参数session 或 global 来显示session级(当前连接)的统计结果和global级(自数据库上次启动至今)的统计结果。如果不写,默认使用session查看全局的统计结果:
查看当前会话的统计结果
也可以根据引擎来查看(如下Innodb的行的影响结果)
主要可以通过以下方式定位执行效率低的sql语句
show processlist
:这个东西类似于Windows系统中的任务管理器,可以查看mysql当前在进行的线程,包括线程的状态,是否缩表等,也可以实时地查看sql的执行情况,同时对一些锁表操作进行优化名称 | 描述 |
---|---|
id | 当用户登录MySQL的时候系统会自动分配一个连接ID(可以理解为会话id),跨域通过 select connection_id();查看 |
user | 显示当前用户 |
host | 客户端Ip地址 |
db | 显示当前这个客户端连接的哪个数据库 |
command | 当前指定的命令:Sleep、Query、connect |
time | 显示当前这个命令状态持续的时间 |
state | 显示当前SQL的状态:coping to tmp table、sorting resulet、sending data等 |
info | 显示正在执行的SQL |
接下来我们准备一份拥有百万级数据的表
DROP TABLE IF EXISTS `system_user`;
CREATE TABLE `system_user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_uuid` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`user_name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`user_password` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`user_phone` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`user_address` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`user_status` smallint(2) NOT NULL,
`user_sex` smallint(2) NOT NULL,
`create_time` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
通过上述步骤可以查询到低效率的SQL语句,然后通过explain执行查询计划即可查看SQL的执行计划
执行计划包含的信息
1、分析出表的读取顺序
2、数据读取操作的操作类型
3、哪些索引可以使用
4、哪些索引被实际使用
5、表之间的引用
6、每张表有多少行被优化器查询
执行计划详解
以上述百万条数据的system_user表为例
我们分别使用执行计划,查看查询有索引字段的语句和无索引字段的语句的区别
有索引(id):
无索引字段(user_uuid)
然后我们创建一个数据量少一点的表方便查看执行计划:
DROP TABLE IF EXISTS `tb_dept`;
CREATE TABLE `tb_dept` (
`id` int(11) NOT NULL,
`dname` varchar(32) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
insert into `tb_dept`(`id`,`dname`) values (1,'开发'),(2,'测试'),(3,'运维');
DROP TABLE IF EXISTS `tb_emp`;
CREATE TABLE `tb_emp` (
`id` int(11) NOT NULL,
`ename` varchar(32) DEFAULT NULL,
`eage` int(11) DEFAULT NULL,
`eaddr` varchar(100) DEFAULT NULL,
`did` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `fk_did` (`did`),
CONSTRAINT `fk_did` FOREIGN KEY (`did`) REFERENCES `tb_dept` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
insert into `tb_emp`(`id`,`ename`,`eage`,`eaddr`,`did`) values (1,'K1',20,'南京',1),(2,'K2',21,'南京',1),(3,'C1',22,'南京',2),(4,'C2',21,'南京',2),(5,'Y1',23,'南京',3),(6,'Y2',24,'南京',3),(7,'Y3',24,'南京',3);
ID列
例如我们执行如下查询计划,获得了ID列有相同值,这说明ID列不是唯一标识,而是显示执行顺序的列:
执行下述执行计划,获得ID列值不同:
SELECT_TYPE列
table和type列
key、prossible_key、rows和extra列
partitions和filtered列
mysql8后加的两个列
explain关键字主要用于定性分析索引的使用情况,以及SQL语句的优劣,但是是无法得知SQL语句的实际执行情况的
而show profile命令可以做到定量分析 SQL语句的执行情况,即使用者可以明确知道一条SQL到底执行了多久
需要注意的是show profile从5.6.7开始不推荐使用,并且在以后的版本中会被删除,改用Performance Schema
SHOW PROFILE [type [, type] ... ]
[FOR QUERY query_id]
[LIMIT row_count [OFFSET offset]]
type: {
ALL
| BLOCK IO
| CONTEXT SWITCHES
| CPU
| IPC
| MEMORY
| PAGE FAULTS
| SOURCE
| SWAPS
}
参数说明:
type | 说明 |
---|---|
ALL | 显示所有性能信息 |
BLOCK IO | 显示块io的次数 |
CONTEXT SWITCHES | 显示上下文切换的次数,包括主动和被动 |
CPU | 显示系统和用户CPU使用时间 |
IPC | 显示发送和接收消息的次数 |
MEMORY | 暂未实现 |
PAGE FAULTS | 显示页面错误的数量 |
SOURCE | 显示源代码中的函数名称以及该函数所在文件的名称和行号 |
SWAPS | 显示swap的次数 |
关于Performance Schema的使用,开启这项功能的时候需要去配置文件进行开启,暂不在这里赘述了,以后用到去看。
从MySQL5.6版本开始,optimizer_trace可支持把MySQL查询执行计划树打印出来,对DBA进行深入分析SQL执行计划,计算成本都非常有用,打印的内部信息比较全面,默认是关闭的,功能支持动态开关,因为对性能有20%左右影响,所以建议只在分析问题的时候,临时开启
show variables like 'optimizer_trace';
set session optimizer_trace="enabled=on",end_markers_in_json=on;
select * from tb_emp;
select * from information_schema.optimizer_trace;
合适的索引可以帮助我们提升查询效率
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('jack',22,'manager',now());
insert into staffs(name,age,pos,add_time) values('tom',23,'dev',now());
insert into staffs(name,age,pos,add_time) values('ketty',23,'dev',now());
select * from staffs;
alter table staffs add index idx_staffs_nameAgePos(name,age,pos);
可以看到我们最后创建了一个复合索引
两条比较重要的准则来避免索引失效
全值匹配:匹配的时候用全值
最佳左前缀法则:如果索引了多列,要遵循最左前缀法则,指的是查询从索引的最左前列开始并且不跳过索引中的列(简单来说是带头大哥不能挂,中间兄弟不能断)
explain select * from staffs where name='jack' and age = 22 and pos = 'manager';
最左前缀法则
当我们三个索引列都作为查询条件时,可知我们用到了复合索引
但当我们取消对name的匹配时,就会发现查询时没有用到索引
而当我们保留name字段,但取消age的条件时,发现虽然用到了索引,但键长发生了变化
那么当我们仅保留name字段去查询时,可以看到虽然也是用到了复合索引,但键长和上一个查询一致,这说明上一个查询和这个查询用到的索引一致,也就是只有一个字段的索引生效
所以,带头大哥不能挂,中间兄弟不能断的含义就是最左的索引必须成为查询条件,否则不会使用索引,中间的字段不能不被使用,否则也只会使用最左边字段的索引,其索引列不生效
不在索引列上做任何操作(如计算、函数、(自动/手动)类型转换),这些操作会导致索引失效而转向全表扫描
自动类型转换,指的是如果某个字段是字符串,记得加单引号,否则查询不会使用索引
可以看到如果索引字段使用函数,查询时将不会使用索引
存储引擎不能使用索引范围条件右边的列
即复合索引虽然有3个字段,但由于age用了范围查询,所以右边的列pos上的索引会失效
尽量使用覆盖索引(即只访问索引的查询(索引列和查询列一致)),减少使用select *
当时有select *时
当查询列和索引列一致时
说明搜索和匹配都使用到了索引
is null,is not null有时索引失效,大多数情况都会失效
失效的情况
我们新增一列地址addr字段,并在该字段上创建索引
mysql评估使用索引反而会慢则不使用索引
mysql会去评估使全表扫描和使用索引时的速度,如果发现全表扫描反而会比使用索引快的时候,就会放弃使用索引,这也是上面is null和is not null有时生效,有时不生效的症结所在
实际上,我们也可以通过使用force index(ide_addr)强制让它使用索引
in使用索引,not in不使用索引(mysql8似乎not in也可以使用索引)
in使用了主键索引
not in (mysql8使用到了索引)
单列索引和复合索引的选择
尽量使用复合索引,少使用单列索引,单列索引在多条件使用的时候,只会选择最优的那个索引,而不会使用全部的索引
可以通过执行以下指令查看所有的使用情况,但仅作为参考,而不能作为优化的依据
show status like 'Handler_read%';
show global status like 'Handler_read%';
对于存储引擎是InnoDB的优化
结论: 尽量避免使用额外的排序,尽量使用using index排序,因为索引本身是有序的,而在升降序的时候尽量保持一致,要么都用升序,要么都用降序
两种排序:
filesort: 对返回结果排序,不通过索引进行排序
using index:通过索引顺序扫描返回有序数据,效率较高
filesort文件排序
filesort优化
1、加大max_length_for_sort_data参数:在MySQl中决定用何种排序算法是通过参数max_length_for_sort_data来决定的。当所有返回字段的最大长度小于这个参数值,MySQL就会选择单路排序
所以如果有充足的内存让MySQL存放需要返回的非排序字段,就可以加大这个参数的值来让MySQL选用单路排序
2、去掉不必要的返回字段
当内存不是很富裕的时候,不能简单地通过强行加大上面的参数来强迫MySQL选用单路排序。因为这可能造成MySQL不得不将数据分成很多段,然后进行排序,反而得不偿失
这个时候就需要去掉不必要的返回字段,让返回结果长度尽可能地适应max_length_for_sort_data参数的限制
3、增大sort_buffer_size参数
增大这个参数不是为了让MySQL选择单路排序算法,而是为了让MySQL尽量减少在排序过程中对需要排序的数据进行分段,因为分段会造成MySQL不得不使用临时表来进行交换排序