MYSQL
性能监控
profile
# 显示性能信息官网 https://dev.mysql.com/doc/refman/8.0/en/show-profile.html
# mysql5版本使用
# set profiling=on;
# 展示最近一条语句运行的时间情况
show profile;
starting 0.000062
Executing hook on transaction 0.000005
starting 0.000008
checking permissions 0.000007
Opening tables 0.000044
init 0.000007
System lock 0.000009
optimizing 0.000005
statistics 0.000014
preparing 0.000019
executing 0.000045
end 0.000004
query end 0.000004
waiting for handler commit 0.000008
closing tables 0.000007
freeing items 0.000039
cleaning up 0.000011
# 展示运行的语句耗时时间
show profiles;
18 0.00114475 SELECT QUERY_ID, SUM(DURATION) AS SUM_DURATION FROM INFORMATION_SCHEMA.PROFILING GROUP BY QUERY_ID
19 0.00117775 SELECT STATE AS `Status`, ROUND(SUM(DURATION),7) AS `Duration`, CONCAT(ROUND(SUM(DURATION)/0.001126*100,3), '') AS `Percentage` FROM INFORMATION_SCHEMA.PROFILING WHERE QUERY_ID=4 GROUP BY SEQ, STATE ORDER BY SEQ
20 0.000149 SET PROFILING = 1
21 0.00112325 SHOW STATUS
22 0.00113575 SHOW STATUS
# 展示指定id的语句的运行时间情况
show profile for query id;
performance schema
# mysql8之后用performance_schema
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |
+--------------------+
4 rows in set (0.00 sec)
# 查看performance_schema
mysql> SHOW VARIABLES LIKE 'performance_schema';
+--------------------+-------+
| Variable_name | Value |
+--------------------+-------+
| performance_schema | ON |
+--------------------+-------+
1 row in set (0.01 sec)
# 这是只读的,必须在配置文件中重新修改
mysql> set performance_schema=off;
ERROR 1238 (HY000): Variable 'performance_schema' is a read only variable
# 查看所有数据库
mysql> show databases
-> ;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |
| test |
+--------------------+
5 rows in set (0.00 sec)
# 选择数据库
mysql> select test;
ERROR 1054 (42S22): Unknown column 'test' in 'field list'
mysql> use test;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Database changed
# 展示创建表的结构
mysql> show create table test1;
+-------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table | Create Table |
+-------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| test1 | CREATE TABLE `test1` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 |
+-------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
# 展示所有的表
mysql> show tables;
+----------------+
| Tables_in_test |
+----------------+
| test1 |
+----------------+
1 row in set (0.00 sec)
/* performance_schema监控表 */
--打开等待事件的采集器配置项开关,需要修改setup_instruments配置表中对应的采集器配置项
UPDATE setup_instruments SET ENABLED = 'YES', TIMED = 'YES'where name like 'wait%';
--打开等待事件的保存表配置开关,修改setup_consumers配置表中对应的配置项
UPDATE setup_consumers SET ENABLED = 'YES'where name like '%wait%';
-- 查看运行的线程
--当配置完成之后可以查看当前server正在做什么,可以通过查询events_waits_current表来得知,该表中每个线程只包含一行数据,用于显示每个线程的最新监视事件
-- \G是按一种格式来查看
select * from events_waits_current\G
*************************** 1. row ***************************
THREAD_ID: 13
EVENT_ID: 355
END_EVENT_ID: 355
EVENT_NAME: wait/synch/mutex/innodb/buf_dblwr_mutex
SOURCE:
TIMER_START: 2721873704860310
TIMER_END: 2721873704970420
TIMER_WAIT: 110110
SPINS: NULL
OBJECT_SCHEMA: NULL
OBJECT_NAME: NULL
INDEX_NAME: NULL
OBJECT_TYPE: NULL
OBJECT_INSTANCE_BEGIN: 94703558963944
NESTING_EVENT_ID: NULL
NESTING_EVENT_TYPE: NULL
OPERATION: lock
NUMBER_OF_BYTES: NULL
FLAGS: NULL
.....下面太多不列举了
/*属性说明:
id:事件来自哪个线程,事件编号是多少
event_name:表示检测到的具体的内容
source:表示这个检测代码在哪个源文件中以及行号
timer_start:表示该事件的开始时间
timer_end:表示该事件的结束时间
timer_wait:表示该事件总的花费时间
注意:_current表中每个线程只保留一条记录,一旦线程完成工作,该表中不会再记录该线程的事件信息
*/
/*
_history表中记录每个线程应该执行完成的事件信息,但每个线程的事件信息只会记录10条,再多就会被覆盖,*_history_long表中记录所有线程的事件信息,但总记录数量是10000,超过就会被覆盖掉
*/
select thread_id,event_id,event_name,timer_wait from events_waits_history order by thread_id limit 21;
/*
summary表提供所有事件的汇总信息,该组中的表以不同的方式汇总事件数据(如:按用户,按主机,按线程等等)。例如:要查看哪些instruments占用最多的时间,可以通过对events_waits_summary_global_by_event_name表的COUNT_STAR或SUM_TIMER_WAIT列进行查询(这两列是对事件的记录数执行COUNT(*)、事件记录的TIMER_WAIT列执行SUM(TIMER_WAIT)统计而来)
*/
SELECT EVENT_NAME,COUNT_STAR FROM events_waits_summary_global_by_event_name ORDER BY COUNT_STAR DESC LIMIT 10;
/*
instance表记录了哪些类型的对象会被检测。这些对象在被server使用时,在该表中将会产生一条事件记录,例如,file_instances表列出了文件I/O操作及其关联文件名
*/
select * from file_instances limit 20;
processlist
# show processlist;实时监控线程连接的信息
mysql> show processlist;
+----+------+---------------------+--------------------+---------+------+----------+------------------+
| Id | User | Host | db | Command | Time | State | Info |
+----+------+---------------------+--------------------+---------+------+----------+------------------+
| 4 | root | 58.213.111.46:21462 | performance_schema | Sleep | 1221 | | NULL |
| 6 | root | localhost | performance_schema | Query | 0 | starting | show processlist |
| 7 | root | 58.213.111.46:1221 | performance_schema | Sleep | 1221 | | NULL |
+----+------+---------------------+--------------------+---------+------+----------+------------------+
3 rows in set (0.00 sec)
schema与数据类型优化
1.数据类型的优化
更小的更好
应该尽量使用可以正确存储数据的最小数据类型,更小的数据类型通常更快,因为它们占用更少的磁盘、内存和CPU缓存,并且处理时需要的CPU周期更少,但是要确保没有低估需要存储的值的范围,如果无法确认哪个数据类型,就选择你认为不会超过范围的最小类型
案例:
设计两张表,设计不同的数据类型,查看表的容量
简单就好
简单数据类型的操作通常需要更少的CPU周期,例如,
1、整型比字符操作代价更低,因为字符集和校对规则是字符比较比整型比较更复杂,
2、使用mysql自建类型而不是字符串来存储日期和时间
3、用整型存储IP地址
案例:
创建两张相同的表,改变日期的数据类型,查看SQL语句执行的速度
# 一般使用无符号的整数来存储ip地址,255.255.255.255-->4294967295,但是可读性差,因此要权衡
select INET_ATON('192.168.2.127') 将ip地址转化为int类型存储
select INET_NTOA(3232236159) 将int重新转化为ip地址
数据库int类型:有符号位 -2的31次方 ~ 2的31次方减1,无符号位 0 ~ 2的32次方减1( 2147483647 和 4294967295)
尽量避免null
如果查询中包含可为NULL的列,对mysql来说很难优化,因为可为null的列使得索引、索引统计和值比较都更加复杂,坦白来说,通常情况下null的列改为not null带来的性能提升比较小,所有没有必要将所有的表的schema进行修改,但是应该尽量避免设计成可为null的列
数据库中的null是不等于NULL的,查询的时候无法查询
整形类型
可以使用的几种整数类型:TINYINT,SMALLINT,MEDIUMINT,INT,BIGINT分别使用8,16,24,32,64位存储空间。使用(1,2,3,4,8个字节存储)
尽量使用满足需求的最小数据类型
2.字符串类型
varchar(n)类型,可变长度,里面的n代表的是字符,最多存多少字符
一个行定义长度不能超过65535个字节,不包括text、blob等类型
根据字符集,字符类型若为gbk,每个字符占用2个字节,最大长度不能超过32766,字符类型若为utf8,每个字符最多占用3个字节,最大长度不能超过21845,若超过这个限制,则会自动将varchar类型转为mediumtext或longtext
# 原则
1.使用最小的符合需求的长度
2.varchar(n) n小于等于255使用额外一个字节保存长度,n>255使用额外两个字节保存长度
3.varchar(5)与varchar(255)保存同样的内容,硬盘存储空间相同,但内存空间占用不同,是指定的大小 。
4、varchar在mysql5.6之前变更长度,或者从255一下变更到255以上时时,都会导致锁表。
# 应用场景
1、存储长度波动较大的数据,如:文章,有的会很短有的会很长
2、字符串很少更新的场景,每次更新后都会重算并使用额外存储空间保存长度
3、适合保存多字节字符,如:汉字,特殊字符等
# varchar小于255时有额外一个字节保留长度
char类型,定长
1、最大长度:255
2、会自动删除末尾的空格
3、检索效率、写效率 会比varchar高,以空间换时间
# 应用场景
1、存储长度波动不大的数据,如:md5摘要
2、存储短字符串、经常更新的字符串
3.blob和text
blob和text类型
MySQL 把每个 BLOB 和 TEXT 值当作一个独立的对象处理。
两者都是为了存储很大数据而设计的字符串类型,分别采用二进制和字符方式存储。
4.datetime和timestamp
datetime
1.占用5个字节
2.与时区无关,数据库底层时区配置,对datetime无效
3.可保存到秒
4.可保存时间范围大
5.不要使用字符串存储日期类型,占用空间大,损失日期类型函数的便捷性
timestamp
1.占用4个字节
2.时间范围:1970-01-01到2038-01-19
3.精确到毫秒
4.采用整形存储
5.依赖数据库设置的时区
6.自动更新timestamp列的值
date
1.占用的字节数比使用字符串、datetime、int存储要少,使用date类型只需要3个字节
2.使用date类型还可以利用日期时间函数进行日期之间的计算
3.date类型用于保存1000-01-01到9999-12-31之间的日期
5.枚举代替字符串类型
有时可以使用枚举类代替常用的字符串类型,mysql存储枚举类型会非常紧凑,会根据列表值的数据压缩到一个或两个字节中,mysql在内部会将每个值在列表中的位置保存为整数,并且在表的.frm文件中保存“数字-字符串”映射关系的查找表
create table enum_test(e enum('fish','apple','dog') not null);
insert into enum_test(e) values('fish'),('dog'),('apple');
select e+0 from enum_test;
6.特殊类型
人们经常使用varchar(15)来存储ip地址,然而,它的本质是32位无符号整数不是字符串,可以使用INET_ATON()和INET_NTOA函数在这两种表示方法之间转换
案例:
select inet_aton('1.1.1.1')
select inet_ntoa(16843009)
字符编码设置utf8m64
存储引擎
MYISAM | INNODB | |
---|---|---|
索引类型 | 非聚簇索引 | 聚簇索引 |
支持事务 | 否 | 是 |
支持表锁 | 是 | 是 |
支持行锁 | 否 | 是 |
支持外键 | 否 | 是 |
支持全文索引 | 是 | 5.6以后支持 |
适合操作类型 | 大量select | 大量insert、update、delete |
查询的列是否是索引列,是的话就是行锁,否则的话就是表锁。
MYISAM锁:共享读锁,独占写锁。占有写锁的时候,其他线程都不能进行读和写。加读锁之后,只能进行加锁表的读,其他操作不允许,包括读其他表。
INNODB:共享锁和排他锁。如果没有索引,就会将行锁改为表锁 lock in share mode
commit提交
适当拆分
当我们的表中存在类似于 TEXT 或者是很大的 VARCHAR类型的大字段的时候,如果我们大部分访问这张表的时候都不需要这个字段,我们就该义无反顾的将其拆分到另外的独立表中,以减少常用数据所占用的存储空间。这样做的一个明显好处就是每个数据块中可以存储的数据条数可以大大增加,既减少物理 IO 次数,也能大大提高内存中的缓存命中率。
垂直切分:按照业务存到不同的数据库中
水平切分:按照数据量比如1-1000存到一个表里,1001-2000存到另一个表里
explain执行计划
explain
在企业的应用场景中,为了知道优化SQL语句的执行,需要查看SQL语句的具体执行过程,以加快SQL语句的执行效率。
可以使用explain+SQL语句来模拟优化器执行SQL查询语句,从而知道mysql是如何处理sql语句的。
官网地址: https://dev.mysql.com/doc/refman/5.5/en/explain-output.html
1、执行计划中包含的信息
Column | Meaning |
---|---|
id | The SELECT identifier |
select_type | The SELECT type |
table | The table for the output row |
partitions | The matching partitions |
type | The join type |
possible_keys | The possible indexes to choose |
key | The index actually chosen |
key_len | The length of the chosen key |
ref | The columns compared to the index |
rows | Estimate of rows to be examined |
filtered | Percentage of rows filtered by table condition |
extra | Additional information |
id
select查询的序列号,包含一组数字,表示查询中执行select子句或者操作表的顺序
id号分为三种情况:
1、如果id相同,那么执行顺序从上到下
explain select * from emp e join dept d on e.deptno = d.deptno join salgrade sg on e.sal between sg.losal and sg.hisal;
2、如果id不同,如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行
explain select * from emp e where e.deptno in (select d.deptno from dept d where d.dname = 'SALES');
3、id相同和不同的,同时存在:相同的可以认为是一组,从上往下顺序执行,在所有组中,id值越大,优先级越高,越先执行
explain select * from emp e join dept d on e.deptno = d.deptno join salgrade sg on e.sal between sg.losal and sg.hisal where e.deptno in (select d.deptno from dept d where d.dname = 'SALES');
select_type
主要用来分辨查询的类型,是普通查询还是联合查询还是子查询
select_type Value |
Meaning |
---|---|
SIMPLE | Simple SELECT (not using UNION or subqueries) |
PRIMARY | Outermost SELECT |
UNION | Second or later SELECT statement in a UNION |
DEPENDENT UNION | Second or later SELECT statement in a UNION, dependent on outer query |
UNION RESULT | Result of a UNION. |
SUBQUERY | First SELECT in subquery |
DEPENDENT SUBQUERY | First SELECT in subquery, dependent on outer query |
DERIVED | Derived table |
UNCACHEABLE SUBQUERY | A subquery for which the result cannot be cached and must be re-evaluated for each row of the outer query |
UNCACHEABLE UNION | The second or later select in a UNION that belongs to an uncacheable subquery (see UNCACHEABLE SUBQUERY) |
--sample:简单的查询,不包含子查询和union
explain select * from emp;
--primary:查询中若包含任何复杂的子查询,最外层查询则被标记为Primary
explain select staname,ename supname from (select ename staname,mgr from emp) t join emp on t.mgr=emp.empno ;
--union:若第二个select出现在union之后,则被标记为union
explain select * from emp where deptno = 10 union select * from emp where sal >2000;
--dependent union:跟union类似,此处的depentent表示union或union all联合而成的结果会受外部表影响
explain select * from emp e where e.empno in ( select empno from emp where deptno = 10 union select empno from emp where sal >2000)
--union result:从union表获取结果的select
explain select * from emp where deptno = 10 union select * from emp where sal >2000;
--subquery:在select或者where列表中包含子查询
explain select * from emp where sal > (select avg(sal) from emp) ;
--dependent subquery:subquery的子查询要受到外部表查询的影响
explain select * from emp e where e.deptno in (select distinct deptno from dept);
--DERIVED: from子句中出现的子查询,也叫做派生类,
explain select staname,ename supname from (select ename staname,mgr from emp) t join emp on t.mgr=emp.empno ;
--UNCACHEABLE SUBQUERY:表示使用子查询的结果不能被缓存
explain select * from emp where empno = (select empno from emp where deptno=@@sort_buffer_size);
--uncacheable union:表示union的查询结果不能被缓存:sql语句未验证
table
对应行正在访问哪一个表,表名或者别名,可能是临时表或者union合并结果集
1、如果是具体的表名,则表明从实际的物理表中获取数据,当然也可以是表的别名
2、表名是derivedN的形式,表示使用了id为N的查询产生的衍生表
3、当有union result的时候,表名是union n1,n2等的形式,n1,n2表示参与union的id
type
type显示的是访问类型,访问类型表示我是以何种方式去访问我们的数据,最容易想的是全表扫描,直接暴力的遍历一张表去寻找需要的数据,效率非常低下,访问的类型有很多,效率从最好到最坏依次是:
system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL
一般情况下,得保证查询至少达到range级别,最好能达到ref
--all:全表扫描,一般情况下出现这样的sql语句而且数据量比较大的话那么就需要进行优化。
explain select * from emp;
--index:全索引扫描这个比all的效率要好,主要有两种情况,一种是当前的查询时覆盖索引,即我们需要的数据在索引中就可以索取,或者是使用了索引进行排序,这样就避免数据的重排序
explain select empno from emp;
--range:表示利用索引查询的时候限制了范围,在指定范围内进行查询,这样避免了index的全索引扫描,适用的操作符: =, <>, >, >=, <, <=, IS NULL, BETWEEN, LIKE, or IN()
explain select * from emp where empno between 7000 and 7500;
--index_subquery:利用索引来关联子查询,不再扫描全表
explain select * from emp where emp.job in (select job from t_job);
--unique_subquery:该连接类型类似与index_subquery,使用的是唯一索引
explain select * from emp e where e.deptno in (select distinct deptno from dept);
--index_merge:在查询过程中需要多个索引组合使用,没有模拟出来
--ref_or_null:对于某个字段即需要关联条件,也需要null值的情况下,查询优化器会选择这种访问方式
explain select * from emp e where e.mgr is null or e.mgr=7369;
--ref:使用了非唯一性索引进行数据的查找
create index idx_3 on emp(deptno);
explain select * from emp e,dept d where e.deptno =d.deptno;
--eq_ref :使用唯一性索引进行数据查找
explain select * from emp,emp2 where emp.empno = emp2.empno;
--const:这个表至多有一个匹配行,
explain select * from emp where empno = 7369;
--system:表只有一行记录(等于系统表),这是const类型的特例,平时不会出现
possible_keys
显示可能应用在这张表中的索引,一个或多个,查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询实际使用
explain select * from emp,dept where emp.deptno = dept.deptno and emp.deptno = 10;
key
实际使用的索引,如果为null,则没有使用索引,查询中若使用了覆盖索引,则该索引和查询的select字段重叠。
explain select * from emp,dept where emp.deptno = dept.deptno and emp.deptno = 10;
key_len
表示索引中使用的字节数,可以通过key_len计算查询中使用的索引长度,在不损失精度的情况下长度越短越好。
explain select * from emp,dept where emp.deptno = dept.deptno and emp.deptno = 10;
ref
显示索引的哪一列被使用了,如果可能的话,是一个常数
explain select * from emp,dept where emp.deptno = dept.deptno and emp.deptno = 10;
rows
根据表的统计信息及索引使用情况,大致估算出找出所需记录需要读取的行数,此参数很重要,直接反应的sql找了多少数据,在完成目的的情况下越少越好
explain select * from emp;
extra
包含额外的信息。
--using filesort:说明mysql无法利用索引进行排序,只能利用排序算法进行排序,会消耗额外的位置
explain select * from emp order by sal;
--using temporary:建立临时表来保存中间结果,查询完成之后把临时表删除
explain select ename,count(*) from emp where deptno = 10 group by ename;
--using index:这个表示当前的查询时覆盖索引的,直接从索引中读取数据,而不用访问数据表。如果同时出现using where 表名索引被用来执行索引键值的查找,如果没有,表面索引被用来读取数据,而不是真的查找
explain select deptno,count(*) from emp group by deptno limit 10;
--using where:使用where进行条件过滤
explain select * from t_user where id = 1;
--using join buffer:使用连接缓存,情况没有模拟出来
--impossible where:where语句的结果总是false
explain select * from emp where empno = 7469;
索引优化
底层使用B+树
memeory引擎使用的是hash索引
演示树形结构网址:https://www.cs.usfca.edu/~galles/visualization/Algorithms.html
二叉树-二叉搜索树-平衡树-AVL平衡二叉查找树-红黑树-B树、B+树、B-树、B*树
innodb默认读16k大小,4页
使用b树只能存16*16*16个数据,因为每一个节点都带有数据
使用b+树只有叶子节点才存有数据,其他只有索引和指针。三层b+树能存千万级别的数据。16K数据,假设每个节点存10B大小的索引,就是16*1000B/10B=1600B数据
1600*1600*160
自带rowid
mysql8之后查询缓存取消
索引的优点
1.减少了扫描的数据量,减少了IO
2.帮助服务器避免排序和临时表
3.将随机IO变为顺序IO
索引分类
主键索引:唯一且不为NULL
唯一索引:可以允许多NULL,NULL代表未知,不是相等
普通索引
联合索引
全文索引:存在于char varchar text
前缀索引
索引采用的数据结构
B+树
hash表
特有名词:
回表:只存在于普通索引中,比如使用name作为索引,先从name查询B+树,查询到该行主键,然后查询主键的B+树,查询到该行的数据
覆盖索引:select id ,查询到主键就结束了,没有查询主键的B+树,相当于没有回表的过程,就是覆盖索引
最左前缀原则:组合索引中,必须先查最左边的索引,在查最右边的索引
索引下推:如果存在某些被索引的列的判断条件时,MySQL服务器将这一部分判断条件传递给存储引擎,然后由存储引擎通过判断索引是否符合MySQL服务器传递的条件,只有当索引符合条件时才会将数据检索出来返回给MySQL服务器
索引匹配方式
sakila实例数据库,下载地址:https://dev.mysql.com/doc/index-other.html
先导入sakila-schema结构表,再导入sakila-data数据表
# 自己创建表
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 '员工记录表';
# 创建索引
alter table staffs add index idx_nap(name, age, pos);
# 展示索引
show index from staffs
explain select * from staffs where name = 'July' and age = '23' and pos = 'dev';
全值匹配:全值匹配指的是和索引中的所有列进行匹配
explain select * from staffs where name = 'July' and age = '23';
匹配最左前缀:只匹配前面的几列
explain select * from staffs where name like 'J%';
explain select * from staffs where name like '%y'; # 不会用索引
匹配列前缀:可以匹配某一列的值的开头部分
explain select * from staffs where name > 'Mary';
匹配范围值:可以查找某一个范围的数据
explain select * from staffs where name = 'July' and age > 25;
精确匹配某一列并范围匹配另外一列:可以查询第一列的全部和第二列的部分
explain select name,age,pos from staffs where name = 'July' and age = 25 and pos = 'dev';
只访问索引的查询:查询的时候只需要访问索引,不需要访问数据行,本质上就是覆盖索引
hash索引
描述
1.基于哈希表的实现,只有精确匹配索引所有列的查询才有效。如果是范围查找是没用的。
2.在mysql中,只有memory的存储引擎显式支持哈希索引,存储在内存中
3.哈希索引自身只需存储对应的hash值,所以索引的结构十分紧凑,这让哈希索引查找的速度非常快
限制
1、哈希索引只包含哈希值和行指针,而不存储字段值,索引不能使用索引中的值来避免读取行
2、哈希索引数据并不是按照索引值顺序存储的,所以无法进行排序
3、哈希索引不支持部分列匹配查找,哈希索引是使用索引列的全部内容来计算哈希值
4、哈希索引支持等值比较查询,不支持任何范围查询
5、访问哈希索引的数据非常快,除非有很多哈希冲突,当出现哈希冲突的时候,存储引擎必须遍历链表中的所有行指针,逐行进行比较,直到找到所有符合条件的行
6、哈希冲突比较多的话,维护的代价也会很高
避免hash冲突
直接取余(基本不用)
扰动函数
1.链地址法:相同的hash值使用链表连接
2.再哈希法:提供多个hash函数,如果一个冲突则使用另一个
3.开放定址法:一旦发生了冲突,就去寻找下一个空的散列地址
4.建立公共溢出区:将哈希表分为基本表和溢出表两部分,凡是和基本表发生冲突的元素,一律填入溢出表
案例
当需要存储大量的URL,并且根据URL进行搜索查找,如果使用B+树,存储的内容就会很大
select id from url where url=""
也可以利用将url使用CRC32做哈希,可以使用以下查询方式:
select id fom url where url="" and url_crc=CRC32("")
此查询性能较高原因是使用体积很小的索引来完成查找
组合索引
建立组合索引a,b,c。有以下情形:
where a=3 # 只使用了a索引
where a=3 and b=2 # 使用了a,b索引
where a=3 and b=2 and c=4 # 使用了a,b,c索引
where b=3 或者 where c=3 # 没有使用索引
where a=3 and c=4 # 使用了a索引
where a=3 and b>10 and c=7 # 使用了a,b索引
where a=3 and b like '%xx%' and c=7 # 只使用了a索引
聚簇索引和非聚簇索引
聚簇索引:索引和数据放在一起
非聚簇索引:数据和索引分开存放
聚簇索引的优点:
1、可以把相关数据保存在一起
2、数据访问更快,因为索引和数据保存在同一个树中
3、使用覆盖索引扫描的查询可以直接使用页节点中的主键值
缺点:
1、聚簇数据最大限度地提高了IO密集型应用的性能,如果数据全部在内存,那么聚簇索引就没有什么优势
2、插入速度严重依赖于插入顺序,按照主键的顺序插入是最快的方式
3、更新聚簇索引列的代价很高,因为会强制将每个被更新的行移动到新的位置
4、基于聚簇索引的表在插入新行,或者主键被更新导致需要移动行的时候,可能面临页分裂的问题
5、聚簇索引可能导致全表扫描变慢,尤其是行比较稀疏,或者由于页分裂导致数据存储不连续的时候
覆盖索引
介绍
1、如果一个索引包含所有需要查询的字段的值,我们称之为覆盖索引
2、不是所有类型的索引都可以称为覆盖索引,覆盖索引必须要存储索引列的值
3、不同的存储实现覆盖索引的方式不同,不是所有的引擎都支持覆盖索引,memory不支持覆盖索引
优势
1、索引条目通常远小于数据行大小,如果只需要读取索引,那么mysql就会极大的较少数据访问量
2、因为索引是按照列值顺序存储的,所以对于IO密集型的范围查询会比随机从磁盘读取每一行数据的IO要少的多
3、一些存储引擎如MYISAM在内存中只缓存索引,数据则依赖于操作系统来缓存,因此要访问数据需要一次系统调用,这可能会导致严重的性能问题
4、由于INNODB的聚簇索引,覆盖索引对INNODB表特别有用
案例
1、当发起一个被索引覆盖的查询时,在explain的extra列可以看到using index的信息,此时就使用了覆盖索引
mysql> explain select store_id,film_id from inventory\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: inventory
partitions: NULL
type: index
possible_keys: NULL
key: idx_store_id_film_id
key_len: 3
ref: NULL
rows: 4581
filtered: 100.00
Extra: Using index
1 row in set, 1 warning (0.01 sec)
2、在大多数存储引擎中,覆盖索引只能覆盖那些只访问索引中部分列的查询。不过,可以进一步的进行优化,可以使用innodb的二级索引来覆盖查询。
例如:actor使用innodb存储引擎,并在last_name字段又二级索引,虽然该索引的列不包括主键actor_id,但也能够用于对actor_id做覆盖查询
mysql> explain select actor_id,last_name from actor where last_name='HOPPER'\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: actor
partitions: NULL
type: ref
possible_keys: idx_actor_last_name
key: idx_actor_last_name
key_len: 137
ref: const
rows: 2
filtered: 100.00
Extra: Using index
1 row in set, 1 warning (0.00 sec)
优化细节
show key from tableName
1.当使用索引列进行查询的时候尽量不要使用表达式,把计算放到业务层而不是数据库层
select actor_id from actor where actor_id=4;
select actor_id from actor where actor_id+1=5;
2.尽量使用主键查询,而不是其他索引,因此主键查询不会触发回表查询(普通索引查询会触发回表查询)
3.使用前缀索引(对文本前几个字符建立索引) left函数返回前几个字节 alter table tablename add key(字段名(截取的位数))
索引的选择性(Index Selectivity),它是指不重复的索引值(也称为基数 cardinality)和数据表的记录总数的比值,取值范围在 [0,1] 之间。索引的选择性越高则查询效率越高,因为选择性高的索引可以让 MySQL 在查找时过滤掉更多的行。
示例:
--创建数据表
create table citydemo(city varchar(50) not null);
insert into citydemo(city) select city from city;
--重复执行5次下面的sql语句
insert into citydemo(city) select city from citydemo;
--更新城市表的名称
update citydemo set city=(select city from city order by rand() limit 1);
--查找最常见的城市列表,发现每个值都出现45-65次,
select count(*) as cnt,city from citydemo group by city order by cnt desc limit 10;
--查找最频繁出现的城市前缀,先从3个前缀字母开始,发现比原来出现的次数更多,可以分别截取多个字符查看城市出现的次数
select count(*) as cnt,left(city,3) as pref from citydemo group by pref order by cnt desc limit 10;
select count(*) as cnt,left(city,7) as pref from citydemo group by pref order by cnt desc limit 10;
--此时前缀的选择性接近于完整列的选择性
--还可以通过另外一种方式来计算完整列的选择性,可以看到当前缀长度到达7之后,再增加前缀长度,选择性提升的幅度已经很小了
-- count(distinct 字段)/count(*) 计算区分度,区分度越大越好
select count(distinct left(city,3))/count(*) as sel3,
count(distinct left(city,4))/count(*) as sel4,
count(distinct left(city,5))/count(*) as sel5,
count(distinct left(city,6))/count(*) as sel6,
count(distinct left(city,7))/count(*) as sel7,
count(distinct left(city,8))/count(*) as sel8
from citydemo;
--计算完成之后可以创建前缀索引
alter table citydemo add key(city(7));
--注意:前缀索引是一种能使索引更小更快的有效方法,但是也包含缺点:mysql无法使用前缀索引做order by 和 group by。
4.使用索引扫描来排序
--sakila数据库中rental表在rental_date,inventory_id,customer_id上有rental_date的索引
--使用rental_date索引为下面的查询做排序
explain select rental_id,staff_id from rental where rental_date='2005-05-25' order by inventory_id,customer_id\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: rental
partitions: NULL
type: ref
possible_keys: rental_date
key: rental_date
key_len: 5
ref: const
rows: 1
filtered: 100.00
Extra: Using index condition
1 row in set, 1 warning (0.00 sec)
--order by子句不满足索引的最左前缀的要求,也可以用于查询排序,这是因为所以你的第一列被指定为一个常数
--该查询为索引的第一列提供了常量条件,而使用第二列进行排序,将两个列组合在一起,就形成了索引的最左前缀
explain select rental_id,staff_id from rental where rental_date='2005-05-25' order by inventory_id desc\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: rental
partitions: NULL
type: ref
possible_keys: rental_date
key: rental_date
key_len: 5
ref: const
rows: 1
filtered: 100.00
Extra: Using where
1 row in set, 1 warning (0.00 sec)
--下面的查询不会利用索引
explain select rental_id,staff_id from rental where rental_date>'2005-05-25' order by rental_date,inventory_id\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: rental
partitions: NULL
type: ALL
possible_keys: rental_date
key: NULL
key_len: NULL
ref: NULL
rows: 16005
filtered: 50.00
Extra: Using where; Using filesort
--该查询使用了两中不同的排序方向,但是索引列都是正序排序的
explain select rental_id,staff_id from rental where rental_date>'2005-05-25' order by inventory_id desc,customer_id asc\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: rental
partitions: NULL
type: ALL
possible_keys: rental_date
key: NULL
key_len: NULL
ref: NULL
rows: 16005
filtered: 50.00
Extra: Using where; Using filesort
1 row in set, 1 warning (0.00 sec)
--该查询中引用了一个不再索引中的列
explain select rental_id,staff_id from rental where rental_date>'2005-05-25' order by inventory_id,staff_id\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: rental
partitions: NULL
type: ALL
possible_keys: rental_date
key: NULL
key_len: NULL
ref: NULL
rows: 16005
filtered: 50.00
Extra: Using where; Using filesort
1 row in set, 1 warning (0.00 sec)
5.union all,in,or都能够使用索引,但是推荐使用in
union和union all之间选择union all(如果没有distinct需求的话)
explain select * from actor where actor_id = 1 union all select * from actor where actor_id = 2;
explain select * from actor where actor_id in (1,2);
explain select * from actor where actor_id = 1 or actor_id =2;
6.范围列可以用到索引
范围列可以用到索引,但是范围列后面的列无法用到索引,索引最多用于一个范围列
7.强制类型转换会全表扫描
不会触发索引:explain select * from user where phone=13800001234;
触发索引:explain select * from user where phone='13800001234';
8.更新十分频繁,数据区分度不高的字段上不宜建立索引
更新会变更B+树,更新频繁的字段建议索引会大大降低数据库性能
类似于性别这类区分不大的属性,建立索引是没有意义的,不能有效的过滤数据,一般区分度在80%以上的时候就可以建立索引,区分度可以使用 count(distinct(列名))/count(*) 来计算
9.创建索引的列,不允许为null,可能会得到不符合预期的结果
10.当需要进行表连接的时候,最好不要超过三张表,因为需要join的字段,数据类型必须一致
11.能使用limit的时候尽量使用limit,limit能限制输出
12.单表索引建议控制在5个以内
13.单索引字段数不允许超过5个(组合索引)
14.创建索引的时候应该避免以下错误概念
索引越多越好
过早优化,在不了解系统的情况下进行优化
索引失效情形
1.like 以%开头,索引无效;当like前缀没有%,后缀有%时,索引有效。
2.or语句前后没有同时使用索引。
3.组合索引,不是使用第一列索引,索引失效。
4.如果列类型是字符串,那一定要在条件中将数据使用引号引用起来,否则不使用索引
5.在索引列上使用 IS NULL 或 IS NOT NULL操作。
6.在索引字段上使用not,<>,!=。
7.对索引字段进行计算操作、字段上使用函数。(索引为 emp(ename,empno,sal))
8.当全表扫描速度比索引速度快时,mysql会使用全表扫描,此时索引失效。
索引监控
查询服务器使用索引状态
show status like 'Handler_read%';
Handler_read_first:读取索引第一个条目的次数
Handler_read_key:通过index获取数据的次数
Handler_read_last:读取索引最后一个条目的次数
Handler_read_next:通过索引读取下一条数据的次数
Handler_read_prev:通过索引读取上一条数据的次数
Handler_read_rnd:从固定位置读取数据的次数
Handler_read_rnd_next:从数据节点读取下一条数据的次数 越小越好
索引优化案例
准备数据
SET FOREIGN_KEY_CHECKS=0;
DROP TABLE IF EXISTS `itdragon_order_list`;
CREATE TABLE `itdragon_order_list` (
`id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT '主键id,默认自增长',
`transaction_id` varchar(150) DEFAULT NULL COMMENT '交易号',
`gross` double DEFAULT NULL COMMENT '毛收入(RMB)',
`net` double DEFAULT NULL COMMENT '净收入(RMB)',
`stock_id` int(11) DEFAULT NULL COMMENT '发货仓库',
`order_status` int(11) DEFAULT NULL COMMENT '订单状态',
`descript` varchar(255) DEFAULT NULL COMMENT '客服备注',
`finance_descript` varchar(255) DEFAULT NULL COMMENT '财务备注',
`create_type` varchar(100) DEFAULT NULL COMMENT '创建类型',
`order_level` int(11) DEFAULT NULL COMMENT '订单级别',
`input_user` varchar(20) DEFAULT NULL COMMENT '录入人',
`input_date` varchar(20) DEFAULT NULL COMMENT '录入时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10003 DEFAULT CHARSET=utf8;
INSERT INTO itdragon_order_list VALUES ('10000', '81X97310V32236260E', '6.6', '6.13', '1', '10', 'ok', 'ok', 'auto', '1', 'itdragon', '2017-08-28 17:01:49');
INSERT INTO itdragon_order_list VALUES ('10001', '61525478BB371361Q', '18.88', '18.79', '1', '10', 'ok', 'ok', 'auto', '1', 'itdragon', '2017-08-18 17:01:50');
INSERT INTO itdragon_order_list VALUES ('10002', '5RT64180WE555861V', '20.18', '20.17', '1', '10', 'ok', 'ok', 'auto', '1', 'itdragon', '2017-09-08 17:01:49');
第一个案例
select * from itdragon_order_list where transaction_id = "81X97310V32236260E";
--通过查看执行计划发现type=all,需要进行全表扫描
explain select * from itdragon_order_list where transaction_id = "81X97310V32236260E";
--优化一、为transaction_id创建唯一索引
create unique index idx_order_transaID on itdragon_order_list (transaction_id);
--当创建索引之后,唯一索引对应的type是const,通过索引一次就可以找到结果,普通索引对应的type是ref,表示非唯一性索引赛秒,找到值还要进行扫描,直到将索引文件扫描完为止,显而易见,const的性能要高于ref
explain select * from itdragon_order_list where transaction_id = "81X97310V32236260E";
--优化二、使用覆盖索引,查询的结果变成 transaction_id,当extra出现using index,表示使用了覆盖索引
explain select transaction_id from itdragon_order_list where transaction_id = "81X97310V32236260E";
第二个案例
--创建复合索引
create index idx_order_levelDate on itdragon_order_list (order_level,input_date);
--创建索引之后发现跟没有创建索引一样,都是全表扫描,都是文件排序
explain select * from itdragon_order_list order by order_level,input_date;
--可以使用force index强制指定索引
explain select * from itdragon_order_list force index(idx_order_levelDate) order by order_level,input_date;
--其实给订单排序意义不大,给订单级别添加索引意义也不大,因此可以先确定order_level的值,然后再给input_date排序
explain select * from itdragon_order_list where order_level=3 order by input_date;
查询优化
查询慢的原因
网络、IO、CPU、上下文切换、系统调用、生成统计信息、锁等待时间
优化数据访问
1.查询性能低下的主要原因是访问的数据太多,某些查询不可避免的需要筛选大量的数据,我们可以通过减少访问数据量的方式进行优化
确认应用程序是否在检索大量超过需要的数据
确认mysql服务器层是否在分析大量超过需要的数据行
2.是否向数据库请求了不需要的数据
查询不需要的记录:我们常常会误以为mysql会只返回需要的数据,实际上mysql却是先返回全部结果再进行计算,在日常的开发习惯中,经常是先用select语句查询大量的结果,然后获取前面的N行后关闭结果集。优化方式是在查询后面添加limit
多表关联时返回全部列:
select * from actor inner join film_actor using(actor_id) inner join film using(film_id) where film.title='Academy Dinosaur';
select actor.* from actor...;
不要写*,尽量加别名
总是取出全部列:在公司的企业需求中,禁止使用select *,虽然这种方式能够简化开发,但是会影响查询的性能,所以尽量不要使用
重复查询相同的数据:如果需要不断的重复执行相同的查询,且每次返回完全相同的数据,因此,基于这样的应用场景,我们可以将这部分数据缓存起来,这样的话能够提高查询效率
查询优化器
查询缓存
mysql 8之前 现在基本不用
在解析一个查询语句之前,如果查询缓存是打开的,那么mysql会优先检查这个查询是否命中查询缓存中的数据,如果查询恰好命中了查询缓存,那么会在返回结果之前会检查用户权限,如果权限没有问题,那么mysql会跳过所有的阶段,就直接从缓存中拿到结果并返回给客户端
语法解析器和预处理
mysql通过关键字将SQL语句进行解析,并生成一颗解析树(抽象语法树),mysql解析器将使用mysql语法规则验证和解析查询,例如验证使用使用了错误的关键字或者顺序是否正确等等,预处理器会进一步检查解析树是否合法,例如表名和列名是否存在,是否有歧义,还会验证权限等等
查询优化器
当语法树没有问题之后,相应的要由优化器将其转成执行计划,一条查询语句可以使用非常多的执行方式,最后都可以得到对应的结果,但是不同的执行方式带来的效率是不同的,优化器的最主要目的就是要选择最有效的执行计划
mysql使用的是基于成本的优化器,在优化的时候会尝试预测一个查询使用某种查询计划时候的成本,并选择其中成本最小的一个
1.select count(*) from film_actor;
show status like 'last_query_cost';
可以看到这条查询语句大概需要做1104个数据页才能找到对应的数据,这是经过一系列的统计信息计算来的
2.在很多情况下mysql会选择错误的执行计划,原因如下:
统计信息不准确:InnoDB因为其mvcc的架构,并不能维护一个数据表的行数的精确统计信息
执行计划的成本估算不等同于实际执行的成本:有时候某个执行计划虽然需要读取更多的页面,但是他的成本却更小,因为如果这些页面都是顺序读或者这些页面都已经在内存中的话,那么它的访问成本将很小,mysql层面并不知道哪些页面在内存中,哪些在磁盘,所以查询之际执行过程中到底需要多少次IO是无法得知的
mysql的最优可能跟你想的不一样:mysql的优化是基于成本模型的优化,但是有可能不是最快的优化
mysql不考虑其他并发执行的查询
mysql不会考虑不受其控制的操作成本:执行存储过程或者用户自定义函数的成本
优化器的优化策略
静态优化:直接对解析树进行分析,并完成优化
动态优化:动态优化与查询的上下文有关,也可能跟取值、索引对应的行数有关
mysql对查询的静态优化只需要一次,但对动态优化在每次执行时都需要重新评估
优化器的优化类型
1.重新定义关联表的顺序:数据表的关联并不总是按照在查询中指定的顺序进行,决定关联顺序时优化器很重要的功能
2.将外连接转化成内连接,内连接的效率要高于外连接
3.使用等价变换规则,mysql可以使用一些等价变化来简化并规划表达式
4.优化count(),min(),max():索引和列是否可以为空通常可以帮助mysql优化这类表达式:例如,要找到某一列的最小值,只需要查询索引的最左端的记录即可,不需要全文扫描比较
5.预估并转化为常数表达式,当mysql检测到一个表达式可以转化为常数的时候,就会一直把该表达式作为常数进行处理
explain select film.film_id,film_actor.actor_id from film inner join film_actor using(film_id) where film.film_id = 1
6.索引覆盖扫描,当索引中的列包含所有查询中需要使用的列的时候,可以使用覆盖索引
7.子查询优化
8.等值传播
如果两个列的值通过等式关联,那么mysql能够把其中一个列的where条件传递到另一个上:
explain select film.film_id from film inner join film_actor using(film_id) where film.film_id > 500;
这里使用film_id字段进行等值关联,film_id这个列不仅适用于film表而且适用于film_actor表
explain select film.film_id from film inner join film_actor using(film_id) where film.film_id > 500 and film_actor.film_id > 500;
关联查询
straight_join
join的实现原理:
Simple Nested-Loop Join
Index Nested-Loop Join
Block Nested-Loop Join
(1)Join Buffer会缓存所有参与查询的列而不是只有Join的列。
(2)可以通过调整join_buffer_size缓存大小
(3)join_buffer_size的默认值是256K,join_buffer_size的最大值在MySQL 5.1.22版本前是4G-1,而之后的版本才能在64位操作系统下申请大于4G的Join Buffer空间。
(4)使用Block Nested-Loop Join算法需要开启优化器管理配置的optimizer_switch的设置block_nested_loop为on,默认为开启。
# 查看开关
show variables like '%optimizer_switch%'
show status like 'last_query_cost';
--案例
查看不同的顺序执行方式对查询性能的影响:
explain select film.film_id,film.title,film.release_year,actor.actor_id,actor.first_name,actor.last_name from film inner join f
ilm_actor using(film_id) inner join actor using(actor_id);
查看执行的成本:
show status like 'last_query_cost';
按照自己预想的规定顺序执行:
explain select straight_join film.film_id,film.title,film.release_year,actor.actor_id,actor.first_name,actor.last_name from fil
m inner join film_actor using(film_id) inner join actor using(actor_id);
查看执行的成本:
show status like 'last_query_cost';
排序优化
1.两次排序传输(之前)
第一次数据读取是将需要排序的字段读取出来,然后进行排序,第二次是将排好序的结果按照需要去读取数据行。
这种方式效率比较低,原因是第二次读取数据的时候因为已经排好序,需要去读取所有记录而此时更多的是随机IO,读取数据成本会比较高
两次传输的优势,在排序的时候存储尽可能少的数据,让排序缓冲区可以尽可能多的容纳行数来进行排序操作
2.一次排序传输(现在)
先读取查询所需要的所有列,然后再根据给定列进行排序,最后直接返回排序结果,此方式只需要一次顺序IO读取所有的数据,而无须任何的随机IO,问题在于查询的列特别多的时候,会占用大量的存储空间,无法存储大量的数据
show variables like '%max_length_for_sort_data%'
当需要排序的列的总大小超过max_length_for_sort_data定义的字节,mysql会选择双次排序,反之使用单次排序,当然,用户可以设置此参数的值来选择排序的方式
特定类型的查询
count类型查询
-- count(1)和count(*)的查询效率是一样的
1.总有人认为myisam的count函数比较快,这是有前提条件的,只有没有任何where条件的count(*)才是比较快的
2.使用近似值
在某些应用场景中,不需要完全精确的值,可以参考使用近似值来代替,比如可以使用explain来获取近似的值,其实在很多OLAP的应用中,需要计算某一个列值的基数,有一个计算近似值的算法叫hyperloglog。
3.更复杂的优化
一般情况下,count()需要扫描大量的行才能获取精确的数据,其实很难优化,在实际操作的时候可以考虑使用索引覆盖扫描,或者增加汇总表,或者增加外部缓存系统。
关联查询
1.确保on或者using子句中的列上有索引,在创建索引的时候就要考虑到关联的顺序
当表A和表B使用列C关联的时候,如果优化器的关联顺序是B、A,那么就不需要再B表的对应列上建上索引,没有用到的索引只会带来额外的负担,一般情况下来说,只需要在关联顺序中的第二个表的相应列上创建索引
2.确保任何的groupby和order by中的表达式只涉及到一个表中的列,这样mysql才有可能使用索引来优化这个过程
子查询
子查询的优化最重要的优化建议是尽可能使用关联查询代替
limit分页
在很多应用场景中我们需要将数据进行分页,一般会使用limit加上偏移量的方法实现,同时加上合适的orderby 的子句,如果这种方式有索引的帮助,效率通常不错,否则的话需要进行大量的文件排序操作,还有一种情况,当偏移量非常大的时候,前面的大部分数据都会被抛弃,这样的代价太高。
要优化这种查询的话,要么是在页面中限制分页的数量,要么优化大偏移量的性能
1.优化此类查询的最简单的办法就是尽可能地使用覆盖索引,而不是查询所有的列
举例1:
select film_id,description from film order by title limit 50,5
explain select film.film_id,film.description from film inner join (select film_id from film order by title limit 50,5) as lim using(film_id);
举例2:
select * from rental limit 10000,5
select * from rental r join (select rental_id from rental limit 10000,5) b on r.rental_id=b.rental_id
union
除非确实需要服务器消除重复的行,否则一定要使用union all.如果没有all关键字,mysql会在查询的时候给临时表加上distinct的关键字,这个操作的代价很高
举例:行转列
name subject score
张三 语文 99
张三 数学 88
张三 英语 77
李四 语文 99
李四 数学 88
李四 英语 77
-- 转成 name yuwen shuxue yingyu,使用case when 、union、join
SELECT name,
SUM(CASE `subject` WHEN '语文' THEN score ELSE 0 END) as '语文',
SUM(CASE `subject` WHEN '数学' THEN score ELSE 0 END) as '数学',
SUM(CASE `subject` WHEN '英语' THEN score ELSE 0 END) as '英语'
FROM tb_score
GROUP BY name
-- 自己写的,感觉不正常
select s.name , sum(s1.score)/3 as '语文',sum(s3.score)/3 as '数学',sum(s3.score)/3 as '英语'
from score s join score s1 on s1.name=s.name and s1.subject='语文'
join score s2 on s2.name=s.name and s2.subject='数学'
join score s3 on s3.name=s.name and s3.subject='英语'
GROUP BY s.name
自定义变量
开窗函数
-- 自定义变量使用,两个@是系统变量
set @one :=1 select @one
set @min_actor :=(select min(actor_id) from actor)
set @last_week :=current_date-interval 1 week;
-- 自定义变量限制
1、无法使用查询缓存
2、不能在使用常量或者标识符的地方使用自定义变量,例如表名、列名或者limit子句
3、用户自定义变量的生命周期是在一个连接中有效,所以不能用它们来做连接间的通信
4、不能显式地声明自定义变量地类型
5、mysql优化器在某些场景下可能会将这些变量优化掉,这可能导致代码不按预想地方式运行
6、赋值符号:=的优先级非常低,所以在使用赋值表达式的时候应该明确的使用括号
7、使用未定义变量不会产生任何语法错误
-- 使用案例
优化排名语句
1、在给一个变量赋值的同时使用这个变量 select actor_id,@rownum:=@rownum+1 as rownum from actor limit 10;
2、查询获取演过最多电影的前10名演员,然后根据出演电影次数做一个排名 select actor_id,count(*) as cnt from film_actor group by actor_id order by cnt desc limit 10;
避免重新查询刚刚更新的数据
当需要高效的更新一条记录的时间戳,同时希望查询当前记录中存放的时间戳是什么
update t1 set lastUpdated=now() where id =1; select lastUpdated from t1 where id =1;
update t1 set lastupdated = now() where id = 1 and @now:=now(); select @now;
确定取值的顺序
在赋值和读取变量的时候可能是在查询的不同阶段
1.set @rownum:=0; select actor_id,@rownum:=@rownum+1 as cnt from actor where @rownum<=1; 因为where和select在查询的不同阶段执行,所以看到查询到两条记录,这不符合预期
2.set @rownum:=0; select actor_id,@rownum:=@rownum+1 as cnt from actor where @rownum<=1 order by first_name 当引入了order by之后,发现打印出了全部结果,这是因为order by引入了文件排序,而where条件是在文件排序操作之前取值的
3.解决这个问题的关键在于让变量的赋值和取值发生在执行查询的同一阶段:set @rownum:=0; select actor_id,@rownum as cnt from actor where (@rownum:=@rownum+1)<=1;
分区表
查看文件
cd /var/lib/mysql
frm和ibd是INNODB
frm、MYD、MYI是MYISAM引擎,
文件名带有#是分区文件
水平切分和垂直切分
水平切分:将表内容进行切分
垂直切分:按业务请求,将不同表放在不同服务器上
分区表的应用场景和优势
1.表非常大以至于无法全部都放在内存中,或者只在表的最后部分有热点数据,其他均是历史数据
2.分区表的数据更容易维护
批量删除大量数据可以使用清除整个分区的方式
对一个独立分区进行优化、检查、修复等操作
3.分区表的数据可以分布在不同的物理设备上,从而高效地利用多个硬件设备
4.可以使用分区表来避免某些特殊的瓶颈
innodb的单个索引的互斥访问
ext3文件系统的inode锁竞争
5.可以备份和恢复独立的分区
分区表的限制
1.一个表最多只能有1024个分区,在5.7版本的时候可以支持8196个分区
2.在早期的mysql中,分区表达式必须是整数或者是返回整数的表达式,在mysql5.5中,某些场景可以直接使用列来进行分区
3.如果分区字段中有主键或者唯一索引的列,那么所有主键列和唯一索引列都必须包含进来
4.分区表无法使用外键约束
分区表的底层原理
句柄对象:可以想象为inode标识
分区表由多个相关的底层表实现,这个底层表也是由句柄对象标识,我们可以直接访问各个分区。存储引擎管理分区的各个底层表和管理普通表一样(所有的底层表都必须使用相同的存储引擎),分区表的索引知识在各个底层表上各自加上一个完全相同的索引。从存储引擎的角度来看,底层表和普通表没有任何不同,存储引擎也无须知道这是一个普通表还是一个分区表的一部分。
分区表的操作按照以下的操作逻辑进行:
**select查询**
当查询一个分区表的时候,分区层先打开并锁住所有的底层表,优化器先判断是否可以过滤部分分区,然后再调用对应的存储引擎接口访问各个分区的数据
**insert操作**
当写入一条记录的时候,分区层先打开并锁住所有的底层表,然后确定哪个分区接受这条记录,再将记录写入对应底层表
**delete操作**
当删除一条记录时,分区层先打开并锁住所有的底层表,然后确定数据对应的分区,最后对相应底层表进行删除操作
**update操作**
当更新一条记录时,分区层先打开并锁住所有的底层表,mysql先确定需要更新的记录再哪个分区,然后取出数据并更新,再判断更新后的数据应该再哪个分区,最后对底层表进行写入操作,并对源数据所在的底层表进行删除操作
有些操作时支持过滤的,例如,当删除一条记录时,MySQL需要先找到这条记录,如果where条件恰好和分区表达式匹配,就可以将所有不包含这条记录的分区都过滤掉,这对update同样有效。如果是insert操作,则本身就是只命中一个分区,其他分区都会被过滤掉。mysql先确定这条记录属于哪个分区,再将记录写入对应得曾分区表,无须对任何其他分区进行操作
虽然每个操作都会“先打开并锁住所有的底层表”,但这并不是说分区表在处理过程中是锁住全表的,如果存储引擎能够自己实现行级锁,例如innodb,则会在分区层释放对应表锁。
分区表的类型
1.范围分区
根据列值在给定范围内将行分配给分区
CREATE TABLE members (
firstname VARCHAR(25) NOT NULL,
lastname VARCHAR(25) NOT NULL,
username VARCHAR(16) NOT NULL,
email VARCHAR(35),
joined DATE NOT NULL
)
PARTITION BY RANGE( YEAR(joined) ) (
PARTITION p0 VALUES LESS THAN (1960),
PARTITION p1 VALUES LESS THAN (1970),
PARTITION p2 VALUES LESS THAN (1980),
PARTITION p3 VALUES LESS THAN (1990),
PARTITION p4 VALUES LESS THAN MAXVALUE
);
2.list分区
类似于按range分区,区别在于list分区是基于列值匹配一个离散值集合中的某个值来进行选择
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 in (6,5,78),
PARTITION p1 VALUES in (11),
PARTITION p2 VALUES in (16),
PARTITION p3 VALUES in MAXVALUE
);
3.列分区
mysql从5.5开始支持column分区,可以认为i是range和list的升级版,在5.5之后,可以使用column分区替代range和list,但是column分区只接受普通列不接受表达式
4.hash分区
基于用户定义的表达式的返回值来进行选择的分区,该表达式使用将要插入到表中的这些行的列值进行计算。这个函数可以包含myql中有效的、产生非负整数值的任何表达式
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; --以4来进行取模
5.key分区
类似于hash分区,区别在于key分区只支持一列或多列,且mysql服务器提供其自身的哈希函数,必须有一列或多列包含整数值
6.子分区
在分区的基础之上,再进行分区后存储
CREATE TABLE ts (id INT, purchased DATE)
PARTITION BY RANGE( YEAR(purchased) )
SUBPARTITION BY HASH( TO_DAYS(purchased) ) (
PARTITION p0 VALUES LESS THAN (1990) (
SUBPARTITION s0,
SUBPARTITION s1
),
PARTITION p1 VALUES LESS THAN (2000) (
SUBPARTITION s2,
SUBPARTITION s3
),
PARTITION p2 VALUES LESS THAN MAXVALUE (
SUBPARTITION s4,
SUBPARTITION s5
)
);
如何使用分区表
如果需要从非常大的表中查询出某一段时间的记录,而这张表中包含很多年的历史数据,数据是按照时间排序的,此时应该如何查询数据呢?
因为数据量巨大,肯定不能在每次查询的时候都扫描全表。考虑到索引在空间和维护上的消耗,也不希望使用索引,即使使用索引,会发现会产生大量的碎片,还会产生大量的随机IO,但是当数据量超大的时候,索引也就无法起作用了,此时可以考虑使用分区来进行解决
1.全量扫描数据,不要任何索引
使用简单的分区方式存放表,不要任何索引,根据分区规则大致定位需要的数据为止,通过使用where条件将需要的数据限制在少数分区中,这种策略适用于以正常的方式访问大量数据
2.索引数据,并分离热点
如果数据有明显的热点,而且除了这部分数据,其他数据很少被访问到,那么可以将这部分热点数据单独放在一个分区中,让这个分区的数据能够有机会都缓存在内存中,这样查询就可以只访问一个很小的分区表,能够使用索引,也能够有效的使用缓存
使用分区注意的问题
1.null值会使分区过滤无效
2.分区列和索引列不匹配,会导致查询无法进行分区过滤
3.选择分区的成本可能很高
4.打开并锁住所有底层表的成本可能很高
5.维护分区的成本可能很高
服务器参数配置
全局变量
datadir=/var/lib/mysql 数据文件存放的目录
socket=/var/lib/mysql/mysql.sock mysql.socket表示server和client在同一台服务器,并且使用localhost进行连接,就会使用socket进行连接
pid_file=/var/lib/mysql/mysql.pid 存储mysql的pid
port=3306 mysql服务的端口号
default_storage_engine=InnoDB mysql存储引擎
skip-grant-tables 当忘记mysql的用户名密码的时候,可以在mysql配置文件中配置该参数,跳过权限表验证,不需要密码即可登录mysql
字符character
character_set_client 客户端数据的字符集
character_set_connection mysql处理客户端发来的信息时,会把这些数据转换成连接的字符集格式
character_set_results mysql发送给客户端的结果集所用的字符集
character_set_database 数据库默认的字符集
character_set_server mysql server的默认字符集
连接connection
show variables like '%max_connections%'
-- max_connections 151
set global max_connections=1024
max_connections mysql的最大连接数,如果数据库的并发连接请求比较大,应该调高该值
max_user_connections 限制每个用户的连接个数
back_log mysql能够暂存的连接数量,当mysql的线程在一个很短时间内得到非常多的连接请求时,就会起作用,如果mysql的连接数量达到max_connections时,新的请求会被存储在堆栈中,以等待某一个连接释放资源,如果等待连接的数量超过back_log,则不再接受连接资源
wait_timeout mysql在关闭一个非交互的连接之前需要等待的时长
非交互连接 短连接 jdbc连接,用完就关闭
交互连接 长连接 命令行
interactive_timeout 关闭一个交互连接之前需要等待的秒数
日志log
redolog undolog -- 属于innodb
binlog---属于mysql server层面的
ACID 原子性通过undolog来实现 隔离性通过锁 持久性通过redolog来实现
undolog存储的是逆操作
redo log是固定大小,循环写的
update--redo log -redolog buffer--os buffer-- 通过fsync()写入磁盘
prepare commit两阶段提交
log_error 指定错误日志文件名称,用于记录当mysqld启动和停止时,以及服务器在运行中发生任何严重错误时的相关信息
log_bin 指定二进制日志文件名称,用于记录对数据造成更改的所有查询语句
binlog_do_db 指定将更新记录到二进制日志的数据库,其他所有没有显式指定的数据库更新将忽略,不记录在日志中
binlog_ignore_db 指定不将更新记录到二进制日志的数据库
sync_binlog 指定多少次写日志后同步磁盘
general_log 是否开启查询日志记录
general_log_file 指定查询日志文件名,用于记录所有的查询语句
slow_query_log 是否开启慢查询日志记录,如果查询语句慢的话回提交到日志文件中
slow_query_log_file 指定慢查询日志文件名称,用于记录耗时比较长的查询语句
long_query_time 设置慢查询的时间,超过这个时间的查询语句才会记录日志
log_slow_admin_statements 是否将管理语句写入慢查询日志
缓存cache
用到缓存:查询(已经取消) join 索引 排序
key_buffer_size 索引缓存区的大小(只对myisam表起作用)
query cache
query_cache_size 查询缓存的大小,未来版本被删除
show status like '%Qcache%';查看缓存的相关属性
Qcache_free_blocks:缓存中相邻内存块的个数,如果值比较大,那么查询缓存中碎片比较多
Qcache_free_memory:查询缓存中剩余的内存大小
Qcache_hits:表示有多少此命中缓存
Qcache_inserts:表示多少次未命中而插入
Qcache_lowmen_prunes:多少条query因为内存不足而被移除cache
Qcache_queries_in_cache:当前cache中缓存的query数量
Qcache_total_blocks:当前cache中block的数量
query_cache_limit 超出此大小的查询将不被缓存
query_cache_min_res_unit 缓存块最小大小
query_cache_type 缓存类型,决定缓存什么样的查询.0表示禁用。1表示将缓存所有结果,除非sql语句中使用sql_no_cache禁用查询缓存。2表示只缓存select语句中通过sql_cache指定需要缓存的查询
sort_buffer_size 每个需要排序的线程分派该大小的缓冲区
max_allowed_packet=32M 限制server接受的数据包大小
join_buffer_size=2M 表示关联缓存的大小
thread_cache_size 服务器线程缓存,这个值表示可以重新利用保存再缓存中的线程数量,当断开连接时,那么客户端的线程将被放到缓存中以响应下一个客户而不是销毁,如果线程重新被请求,那么请求将从缓存中读取,如果缓存中是空的或者是新的请求,这个线程将被重新请求,那么这个线程将被重新创建,如果有很多新的线程,增加这个值即可
Threads_cached:代表当前此时此刻线程缓存中有多少空闲线程
Threads_connected:代表当前已建立连接的数量
Threads_created:代表最近一次服务启动,已创建现成的数量,如果该值比较大,那么服务器会一直再创建线程
Threads_running:代表当前激活的线程数
引擎
innodb_buffer_pool_size= 该参数指定大小的内存来缓冲数据和索引,最大可以设置为物理内存的80%
innodb_flush_log_at_trx_commit 主要控制innodb将log buffer中的数据写入日志文件并flush磁盘的时间点,值分别为0,1,2
innodb_thread_concurrency 设置innodb线程的并发数,默认为0表示不受限制,如果要设置建议跟服务器的cpu核心数一致或者是cpu核心数的两倍
innodb_log_buffer_size 此参数确定日志文件所用的内存大小,以M为单位
innodb_log_file_size 此参数确定数据日志文件的大小,以M为单位
innodb_log_files_in_group 以循环方式将日志文件写到多个文件中
read_buffer_size mysql读入缓冲区大小,对表进行顺序扫描的请求将分配到一个读入缓冲区
read_rnd_buffer_size mysql随机读的缓冲区大小
innodb_file_per_table 此参数确定为每张表分配一个新的文件
undo和Redo
undo是实现原子性,用来进行回滚操作
redo是实现持久性,数据库错误时候,继续执行,本质也是恢复