层次:
所有查询语句时间
show profile for query 1
查询语句的详细时间
type 参数
show profile all for query n
show profile block io for query n
show profile context switches for query n
show profile cpu for query n
show profile ipc for query n
show profile page faults for query n
show profile source for query n
show profile swaps for query n
MySQL 的 performance schema 用于监控 MySQL server 在一个较低级别的运行过程中的资源消耗、资源等待等情况。
特点如下:
1、主要关注 server 运行过程中的元数据信息
2、performance_schema 通过监视 server 的事件来实现监视 server 内部运行情况, “事件”就是 server 内部活动中所做的任何事情以及对应的时间消耗,利用这些信息来判断 server 中的相关资源消耗在了哪里?一般来说,事件可以是函数调用、操作系统的等待、SQL 语句执行的阶段(如 sql 语句执行过程中的 parsing 或 sorting 阶段)或者整个 SQL 语句与 SQL 语句集合。事件的采集可以方便的提供 server 中的相关存储引擎对磁盘文件、表 I/O、表锁等资源的同步调用信息。
3、performance_schema 中的事件与写入二进制日志中的事件(描述数据修改的 events)、事件计划调度程序(这是一种存储程序)的事件不同。performance_schema 中的事件记录的是 server 执行某些活动对某些资源的消耗、耗时、这些活动执行的次数等情况。
4、performance_schema 中的事件只记录在本地 server 的 performance_schema 中,其下的这些表中数据发生变化时不会被写入 binlog 中,也不会通过复制机制被复制到其他 server 中。
5、 当前活跃事件、历史事件和事件摘要相关的表中记录的信息。能提供某个事件的执行次数、使用时长。进而可用于分析某个特定线程、特定对象(如 mutex 或 file)相关联的活动。
6、PERFORMANCE_SCHEMA 存储引擎使用 server 源代码中的“检测点”来实现事件数据的收集。对于 performance_schema 实现机制本身的代码没有相关的单独线程来检测,这与其他功能(如复制或事件计划程序)不同
7、收集的事件数据存储在 performance_schema 数据库的表中。这些表可以使用 SELECT 语句查询,也可以使用 SQL 语句更新 performance_schema 数据库中的表记录(如动态修改 performance_schema 的 setup_* 开头的几个配置表,但要注意:配置表的更改会立即生效,这会影响数据收集)
8、performance_schema 的表中的数据不会持久化存储在磁盘中,而是保存在内存中,一旦服务器重启,这些数据会丢失(包括配置表在内的整个 performance_schema 下的所有数据)
9、MySQL 支持的所有平台中事件监控功能都可用,但不同平台中用于统计事件时间开销的计时器类型可能会有所差异。
在 mysql 的 5.7 版本中,性能模式是默认开启的,如果想要显式的关闭的话需要修改配置文件,不能直接进行修改,会报错 Variable ‘performance_schema’ is a read only variable。
--查看 performance_schema 的属性
mysql> SHOW VARIABLES LIKE 'performance_schema';
+--------------------+-------+
| Variable_name | Value |
+--------------------+-------+
| performance_schema | ON |
+--------------------+-------+
1 row in set (0.01 sec)
--在配置文件中修改 performance_schema 的属性值,on 表示开启,off 表示关闭
[mysqld]
performance_schema=ON
--切换数据库
use performance_schema;
--查看当前数据库下的所有表,会看到有很多表存储着相关的信息
show tables;
--可以通过 show create table tablename 来查看创建表的时候的表结构
mysql> show create table setup_consumers;
+-----------------+---------------------------------
| Table | Create Table
+-----------------+---------------------------------
| setup_consumers | CREATE TABLE `setup_consumers` (
`NAME` varchar(64) NOT NULL,
`ENABLED` enum('YES','NO') NOT NULL
) ENGINE=PERFORMANCE_SCHEMA DEFAULT CHARSET=utf8 |
+-----------------+---------------------------------
1 row in set (0.00 sec)
想要搞明白后续的内容,同学们需要理解两个基本概念:
instruments:生产者,用于采集 mysql 中各种各样的操作产生的事件信息,对应配置表中的配置项我们可以称为监控采集配置项。
consumers:消费者,对应的消费者表用于存储来自 instruments 采集的数据,对应配置表中的配置项我们可以称为消费存储配置项。
performance_schema 库下的表可以按照监视不同的纬度就行分组。
--语句事件记录表,这些表记录了语句事件信息,
-- 当前语句事件表 events_statements_current、
-- 历史语句事件表 events_statements_history
-- 长语句历史事件表 events_statements_history_long
-- 聚合后的摘要表 summary,其中,summary 表还可以根据帐号(account),主机(host),程序(program),线程(thread),用户(user)和全局(global)再进行细分)
show tables like '%statement%';
--等待事件记录表,与语句事件类型的相关记录表类似:
show tables like '%wait%';
--阶段事件记录表,记录语句执行的阶段事件的表
show tables like '%stage%';
--事务事件记录表,记录事务相关的事件的表
show tables like '%transaction%';
--监控文件系统层调用的表
show tables like '%file%';
--监视内存使用的表
show tables like '%memory%';
--动态对performance_schema进行配置的配置表
show tables like '%setup%';
数据库刚刚初始化并启动时,并非所有instruments(事件采集项,在采集项的配置表中每一项都有一个开关字段,或为YES,或为NO)和consumers(与采集项类似,也有一个对应的事件类型保存表配置项,为YES就表示对应的表保存性能数据,为NO就表示对应的表不保存性能数据)都启用了,所以默认不会收集所有的事件,可能你需要检测的事件并没有打开,需要进行设置,可以使用如下两个语句打开对应的instruments和consumers(行计数可能会因MySQL版本而异)。
--打开等待事件的采集器配置项开关,需要修改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表来得知,该表中每个线程只包含一行数据,用于显示每个线程的最新监视事件
select * from events_waits_current\G
*************************** 1. row ***************************
THREAD_ID: 11
EVENT_ID: 570
END_EVENT_ID: 570
EVENT_NAME: wait/synch/mutex/innodb/buf_dblwr_mutex
SOURCE:
TIMER_START: 4508505105239280
TIMER_END: 4508505105270160
TIMER_WAIT: 30880
SPINS: NULL
OBJECT_SCHEMA: NULL
OBJECT_NAME: NULL
INDEX_NAME: NULL
OBJECT_TYPE: NULL
OBJECT_INSTANCE_BEGIN: 67918392
NESTING_EVENT_ID: NULL
NESTING_EVENT_TYPE: NULL
OPERATION: lock
NUMBER_OF_BYTES: NULL
FLAGS: NULL
/*该信息表示线程id为11的线程正在等待buf_dblwr_mutex锁,等待事件为30880
属性说明:
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;
1、启动选项
performance_schema_consumer_events_statements_current=TRUE
是否在mysql server启动时就开启events_statements_current表的记录功能(该表记录当前的语句事件信息),启动之后也可以在setup_consumers表中使用UPDATE语句进行动态更新setup_consumers配置表中的events_statements_current配置项,默认值为TRUE
performance_schema_consumer_events_statements_history=TRUE
与performance_schema_consumer_events_statements_current选项类似,但该选项是用于配置是否记录语句事件短历史信息,默认为TRUE
performance_schema_consumer_events_stages_history_long=FALSE
与performance_schema_consumer_events_statements_current选项类似,但该选项是用于配置是否记录语句事件长历史信息,默认为FALSE
除了statement(语句)事件之外,还支持:wait(等待)事件、state(阶段)事件、transaction(事务)事件,他们与statement事件一样都有三个启动项分别进行配置,但这些等待事件默认未启用,如果需要在MySQL Server启动时一同启动,则通常需要写进my.cnf配置文件中
performance_schema_consumer_global_instrumentation=TRUE
是否在MySQL Server启动时就开启全局表(如:mutex_instances、rwlock_instances、cond_instances、file_instances、users、hostsaccounts、socket_summary_by_event_name、file_summary_by_instance等大部分的全局对象计数统计和事件汇总统计信息表 )的记录功能,启动之后也可以在setup_consumers表中使用UPDATE语句进行动态更新全局配置项
默认值为TRUE
performance_schema_consumer_statements_digest=TRUE
是否在MySQL Server启动时就开启events_statements_summary_by_digest 表的记录功能,启动之后也可以在setup_consumers表中使用UPDATE语句进行动态更新digest配置项
默认值为TRUE
performance_schema_consumer_thread_instrumentation=TRUE
是否在MySQL Server启动时就开启
events_xxx_summary_by_yyy_by_event_name表的记录功能,启动之后也可以在setup_consumers表中使用UPDATE语句进行动态更新线程配置项
默认值为TRUE
performance_schema_instrument[=name]
是否在MySQL Server启动时就启用某些采集器,由于instruments配置项多达数千个,所以该配置项支持key-value模式,还支持%号进行通配等,如下:
# [=name]可以指定为具体的Instruments名称(但是这样如果有多个需要指定的时候,就需要使用该选项多次),也可以使用通配符,可以指定instruments相同的前缀+通配符,也可以使用%代表所有的instruments
## 指定开启单个instruments
--performance-schema-instrument= 'instrument_name=value'
## 使用通配符指定开启多个instruments
--performance-schema-instrument= 'wait/synch/cond/%=COUNTED'
## 开关所有的instruments
--performance-schema-instrument= '%=ON'
--performance-schema-instrument= '%=OFF'
注意,这些启动选项要生效的前提是,需要设置performance_schema=ON。另外,这些启动选项虽然无法使用show variables语句查看,但我们可以通过setup_instruments和setup_consumers表查询这些选项指定的值。
2、系统变量
show variables like '%performance_schema%';
--重要的属性解释
performance_schema=ON
/*
控制performance_schema功能的开关,要使用MySQL的performance_schema,需要在mysqld启动时启用,以启用事件收集功能
该参数在5.7.x之前支持performance_schema的版本中默认关闭,5.7.x版本开始默认开启
注意:如果mysqld在初始化performance_schema时发现无法分配任何相关的内部缓冲区,则performance_schema将自动禁用,并将performance_schema设置为OFF
*/
performance_schema_digests_size=10000
/*
控制events_statements_summary_by_digest表中的最大行数。如果产生的语句摘要信息超过此最大值,便无法继续存入该表,此时performance_schema会增加状态变量
*/
performance_schema_events_statements_history_long_size=10000
/*
控制events_statements_history_long表中的最大行数,该参数控制所有会话在events_statements_history_long表中能够存放的总事件记录数,超过这个限制之后,最早的记录将被覆盖
全局变量,只读变量,整型值,5.6.3版本引入 * 5.6.x版本中,5.6.5及其之前的版本默认为10000,5.6.6及其之后的版本默认值为-1,通常情况下,自动计算的值都是10000 * 5.7.x版本中,默认值为-1,通常情况下,自动计算的值都是10000
*/
performance_schema_events_statements_history_size=10
/*
控制events_statements_history表中单个线程(会话)的最大行数,该参数控制单个会话在events_statements_history表中能够存放的事件记录数,超过这个限制之后,单个会话最早的记录将被覆盖
全局变量,只读变量,整型值,5.6.3版本引入 * 5.6.x版本中,5.6.5及其之前的版本默认为10,5.6.6及其之后的版本默认值为-1,通常情况下,自动计算的值都是10 * 5.7.x版本中,默认值为-1,通常情况下,自动计算的值都是10
除了statement(语句)事件之外,wait(等待)事件、state(阶段)事件、transaction(事务)事件,他们与statement事件一样都有三个参数分别进行存储限制配置,有兴趣的同学自行研究,这里不再赘述
*/
performance_schema_max_digest_length=1024
/*
用于控制标准化形式的SQL语句文本在存入performance_schema时的限制长度,该变量与max_digest_length变量相关(max_digest_length变量含义请自行查阅相关资料)
全局变量,只读变量,默认值1024字节,整型值,取值范围0~1048576
*/
performance_schema_max_sql_text_length=1024
/*
控制存入events_statements_current,events_statements_history和events_statements_history_long语句事件表中的SQL_TEXT列的最大SQL长度字节数。 超出系统变量performance_schema_max_sql_text_length的部分将被丢弃,不会记录,一般情况下不需要调整该参数,除非被截断的部分与其他SQL比起来有很大差异
全局变量,只读变量,整型值,默认值为1024字节,取值范围为0~1048576,5.7.6版本引入
降低系统变量performance_schema_max_sql_text_length值可以减少内存使用,但如果汇总的SQL中,被截断部分有较大差异,会导致没有办法再对这些有较大差异的SQL进行区分。 增加该系统变量值会增加内存使用,但对于汇总SQL来讲可以更精准地区分不同的部分。
*/
配置表之间存在相互关联关系,按照配置影响的先后顺序,可添加为
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L7ARUm88-1610255685485)(C:%5CUsers%5C63198%5CAppData%5CRoaming%5CTypora%5Ctypora-user-images%5Cimage-20191203125003597.png)]
/*
performance_timers表中记录了server中有哪些可用的事件计时器
字段解释:
timer_name:表示可用计时器名称,CYCLE是基于CPU周期计数器的定时器
timer_frequency:表示每秒钟对应的计时器单位的数量,CYCLE计时器的换算值与CPU的频率相关、
timer_resolution:计时器精度值,表示在每个计时器被调用时额外增加的值
timer_overhead:表示在使用定时器获取事件时开销的最小周期值
*/
select * from performance_timers;
/*
setup_timers表中记录当前使用的事件计时器信息
字段解释:
name:计时器类型,对应某个事件类别
timer_name:计时器类型名称
*/
select * from setup_timers;
/*
setup_consumers表中列出了consumers可配置列表项
字段解释:
NAME:consumers配置名称
ENABLED:consumers是否启用,有效值为YES或NO,此列可以使用UPDATE语句修改。
*/
select * from setup_consumers;
/*
setup_instruments 表列出了instruments 列表配置项,即代表了哪些事件支持被收集:
字段解释:
NAME:instruments名称,instruments名称可能具有多个部分并形成层次结构
ENABLED:instrumetns是否启用,有效值为YES或NO,此列可以使用UPDATE语句修改。如果设置为NO,则这个instruments不会被执行,不会产生任何的事件信息
TIMED:instruments是否收集时间信息,有效值为YES或NO,此列可以使用UPDATE语句修改,如果设置为NO,则这个instruments不会收集时间信息
*/
SELECT * FROM setup_instruments;
/*
setup_actors表的初始内容是匹配任何用户和主机,因此对于所有前台线程,默认情况下启用监视和历史事件收集功能
字段解释:
HOST:与grant语句类似的主机名,一个具体的字符串名字,或使用“%”表示“任何主机”
USER:一个具体的字符串名称,或使用“%”表示“任何用户”
ROLE:当前未使用,MySQL 8.0中才启用角色功能
ENABLED:是否启用与HOST,USER,ROLE匹配的前台线程的监控功能,有效值为:YES或NO
HISTORY:是否启用与HOST, USER,ROLE匹配的前台线程的历史事件记录功能,有效值为:YES或NO
*/
SELECT * FROM setup_actors;
/*
setup_objects表控制performance_schema是否监视特定对象。默认情况下,此表的最大行数为100行。
字段解释:
OBJECT_TYPE:instruments类型,有效值为:“EVENT”(事件调度器事件)、“FUNCTION”(存储函数)、“PROCEDURE”(存储过程)、“TABLE”(基表)、“TRIGGER”(触发器),TABLE对象类型的配置会影响表I/O事件(wait/io/table/sql/handler instrument)和表锁事件(wait/lock/table/sql/handler instrument)的收集
OBJECT_SCHEMA:某个监视类型对象涵盖的数据库名称,一个字符串名称,或“%”(表示“任何数据库”)
OBJECT_NAME:某个监视类型对象涵盖的表名,一个字符串名称,或“%”(表示“任何数据库内的对象”)
ENABLED:是否开启对某个类型对象的监视功能,有效值为:YES或NO。此列可以修改
TIMED:是否开启对某个类型对象的时间收集功能,有效值为:YES或NO,此列可以修改
*/
SELECT * FROM setup_objects;
/*
threads表对于每个server线程生成一行包含线程相关的信息,
字段解释:
THREAD_ID:线程的唯一标识符(ID)
NAME:与server中的线程检测代码相关联的名称(注意,这里不是instruments名称)
TYPE:线程类型,有效值为:FOREGROUND、BACKGROUND。分别表示前台线程和后台线程
PROCESSLIST_ID:对应INFORMATION_SCHEMA.PROCESSLIST表中的ID列。
PROCESSLIST_USER:与前台线程相关联的用户名,对于后台线程为NULL。
PROCESSLIST_HOST:与前台线程关联的客户端的主机名,对于后台线程为NULL。
PROCESSLIST_DB:线程的默认数据库,如果没有,则为NULL。
PROCESSLIST_COMMAND:对于前台线程,该值代表着当前客户端正在执行的command类型,如果是sleep则表示当前会话处于空闲状态
PROCESSLIST_TIME:当前线程已处于当前线程状态的持续时间(秒)
PROCESSLIST_STATE:表示线程正在做什么事情。
PROCESSLIST_INFO:线程正在执行的语句,如果没有执行任何语句,则为NULL。
PARENT_THREAD_ID:如果这个线程是一个子线程(由另一个线程生成),那么该字段显示其父线程ID
ROLE:暂未使用
INSTRUMENTED:线程执行的事件是否被检测。有效值:YES、NO
HISTORY:是否记录线程的历史事件。有效值:YES、NO *
THREAD_OS_ID:由操作系统层定义的线程或任务标识符(ID):
*/
select * from threads
注意:在performance_schema库中还包含了很多其他的库和表,能对数据库的性能做完整的监控,大家需要参考官网详细了解。
基本了解了表的相关信息之后,可以通过这些表进行实际的查询操作来进行实际的分析。
--1、哪类的SQL执行最多?
SELECT DIGEST_TEXT,COUNT_STAR,FIRST_SEEN,LAST_SEEN FROM events_statements_summary_by_digest ORDER BY COUNT_STAR DESC
--2、哪类SQL的平均响应时间最多?
SELECT DIGEST_TEXT,AVG_TIMER_WAIT FROM events_statements_summary_by_digest ORDER BY COUNT_STAR DESC
--3、哪类SQL排序记录数最多?
SELECT DIGEST_TEXT,SUM_SORT_ROWS FROM events_statements_summary_by_digest ORDER BY COUNT_STAR DESC
--4、哪类SQL扫描记录数最多?
SELECT DIGEST_TEXT,SUM_ROWS_EXAMINED FROM events_statements_summary_by_digest ORDER BY COUNT_STAR DESC
--5、哪类SQL使用临时表最多?
SELECT DIGEST_TEXT,SUM_CREATED_TMP_TABLES,SUM_CREATED_TMP_DISK_TABLES FROM events_statements_summary_by_digest ORDER BY COUNT_STAR DESC
--6、哪类SQL返回结果集最多?
SELECT DIGEST_TEXT,SUM_ROWS_SENT FROM events_statements_summary_by_digest ORDER BY COUNT_STAR DESC
--7、哪个表物理IO最多?
SELECT file_name,event_name,SUM_NUMBER_OF_BYTES_READ,SUM_NUMBER_OF_BYTES_WRITE FROM file_summary_by_instance ORDER BY SUM_NUMBER_OF_BYTES_READ + SUM_NUMBER_OF_BYTES_WRITE DESC
--8、哪个表逻辑IO最多?
SELECT object_name,COUNT_READ,COUNT_WRITE,COUNT_FETCH,SUM_TIMER_WAIT FROM table_io_waits_summary_by_table ORDER BY sum_timer_wait DESC
--9、哪个索引访问最多?
SELECT OBJECT_NAME,INDEX_NAME,COUNT_FETCH,COUNT_INSERT,COUNT_UPDATE,COUNT_DELETE FROM table_io_waits_summary_by_index_usage ORDER BY SUM_TIMER_WAIT DESC
--10、哪个索引从来没有用过?
SELECT OBJECT_SCHEMA,OBJECT_NAME,INDEX_NAME FROM table_io_waits_summary_by_index_usage WHERE INDEX_NAME IS NOT NULL AND COUNT_STAR = 0 AND OBJECT_SCHEMA <> 'mysql' ORDER BY OBJECT_SCHEMA,OBJECT_NAME;
--11、哪个等待事件消耗时间最多?
SELECT EVENT_NAME,COUNT_STAR,SUM_TIMER_WAIT,AVG_TIMER_WAIT FROM events_waits_summary_global_by_event_name WHERE event_name != 'idle' ORDER BY SUM_TIMER_WAIT DESC
--12-1、剖析某条SQL的执行情况,包括statement信息,stege信息,wait信息
SELECT EVENT_ID,sql_text FROM events_statements_history WHERE sql_text LIKE '%count(*)%';
--12-2、查看每个阶段的时间消耗
SELECT event_id,EVENT_NAME,SOURCE,TIMER_END - TIMER_START FROM events_stages_history_long WHERE NESTING_EVENT_ID = 1553;
--12-3、查看每个阶段的锁等待情况
SELECT event_id,event_name,source,timer_wait,object_name,index_name,operation,nesting_event_id FROM events_waits_history_longWHERE nesting_event_id = 1553;
更小的通常更好
尽量使用最小的数据类型
更小的数据类型通常更快,因为占用更少的磁盘、内存和 CPU 缓存,并且处理时需要的 CPU 周期更少
简单就好
简单数据类型的操作通常需要更少的 CPU 周期
整型比字符操作代价更低: 因为字符集和校对规则是字符比较比整型比较更复杂
*时间日期:*使用 mysql 自建类型而不是字符串来存储日期和时间
*IP:*用整型存储 IP 地址
尽量避免 null
包含 NULL 的列,对 mysql 来说很难优化。会使得索引、索引统计和值比较都更加复杂
通常情况下 null 的列改为 not null 带来的性能提升比较小,所有没有必要将所有的表的 schema 进行修改,但是应该尽量避免设计成可为 null 的列
可以使用的几种整数类型:TINYINT,SMALLINT,MEDIUMINT,INT,BIGINT 分别使用 8,16,24,32,64 位存储空间。
尽量使用满足需求的最小数据类型
char:
长度固定,即每条数据占用等长字节空间。最大长度是 255 个字符,适合用在身份证号、手机号等定长字符串
varchar:
可变程度,可以设置最大长度;最大空间是 65535 个字节,适合用在长度可变的属性
text:
不设置长度,当不知道属性的最大长度时,适合用 text
按照查询速度:char > varchar > text
MySQL 把每个 BLOB 和 TEXT 值当作一个独立的对象处理。
两者都是为了存储很大数据而设计的字符串类型,分别采用二进制和字符方式存储。
datetime:
timestamp:
date:
有时可以使用枚举类代替常用的字符串类型,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;
人们经常使用 varchar(15) 来存储 ip 地址,然而,它的本质是 32 位无符号整数不是字符串,可以使用 INET_ATON() 和 INET_NTOA 函数在这两种表示方法之间转换
案例:
select inet_aton(‘1.1.1.1’)
select inet_ntoa(16843009)
范式:
反范式:
代理主键:与业务无关的,无意义的数字序列
自然主键:事物属性中的自然唯一标识
推荐使用代理主键:
不与业务耦合,因此更容易维护。
一个大多数表,最好是全部表,通用的键策略能够减少需要编写的源码数量,减少系统的总体拥有成本
字符集直接决定了数据在 MySQL 中的存储编码方式,由于同样的内容使用不同字符集表示所占用的空间大小会有较大的差异,所以通过使用合适的字符集,可以帮助我们尽可能减少数据量,进而减少 IO 操作次数。
被频繁引用且只能通过 Join 2 张(或者更多)大表的方式才能得到的独立小字段。
由于每次 Join 仅仅只是为了取得某个小字段的值,Join 到的记录又大,会造成大量不必要的 IO,完全可以通过空间换取时间的方式来优化。
不过,冗余的同时需要确保数据的一致性不会遭到破坏,确保更新的同时冗余字段也被更新。
当我们的表中存在类似于 TEXT 或者是很大的 VARCHAR 类型的大字段的时候,如果我们大部分访问这张表的时候都不需要这个字段,我们就该义无反顾的将其拆分到另外的独立表中,以减少常用数据所占用的存储空间。这样做的一个明显好处就是每个数据块中可以存储的数据条数可以大大增加,既减少物理 IO 次数,也能大大提高内存中的缓存命中率。
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 |
select 查询的序列号,包含一组数字,表示查询中执行 select 子句或者操作表的顺序
如果 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;
如果 id 不同,如果是子查询,id 的序号会递增,id 值越大优先级越高,越先被执行
explain
select *
from emp e
where e.deptno in (
select d.deptno
from dept d
where d.dname = 'SALES'
);
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 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 的查询结果不能被缓存
对应行正在访问哪一个表,表名或者别名,可能是临时表或者 union 合并结果集
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 类型的特例,平时不会出现
显示可能应用在这张表中的索引,一个或多个,查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询实际使用
explain
select *
from emp,dept
where emp.deptno = dept.deptno and emp.deptno = 10;
实际使用的索引,如果为 null,则没有使用索引,查询中若使用了覆盖索引,则该索引和查询的 select 字段重叠。
explain
select *
from emp,dept
where emp.deptno = dept.deptno and emp.deptno = 10;
表示索引中使用的字节数,可以通过 key_len 计算查询中使用的索引长度,在不损失精度的情况下长度越短越好。
explain
select *
from emp,dept
where emp.deptno = dept.deptno and emp.deptno = 10;
显示索引的哪一列被使用了,如果可能的话,是一个常数
explain
select *
from emp,dept
where emp.deptno = dept.deptno and emp.deptno = 10;
根据表的统计信息及索引使用情况,大致估算出找出所需记录需要读取的行数,此参数很重要,直接反应的 sql 找了多少数据,在完成目的的情况下越少越好
explain select * from emp;
包含额外的信息。
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;
优点:
作用:
分类:
面试技术名词:
采用的数据结构:
索引匹配方式:
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);
全值匹配:全值匹配指的是和索引中的所有列进行匹配
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 = 'July';
匹配列前缀:可以匹配某一列的值的开头部分
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';
案例:
当需要存储大量的 URL,并且根据 URL 进行搜索查找,如果使用 B+ 树,存储的内容就会很大
select id from url where url=""
也可以利用将 url 使用 CRC32 做哈希,可以使用以下查询方式:
select id fom url where url="" and url_crc=CRC32("")
此查询性能较高原因是使用体积很小的索引来完成查找
当包含多个列作为索引,需要注意的是正确的顺序依赖于该索引的查询,同时需要考虑如何更好的满足排序和分组的需要
建立组合索引 a,b,c
不同 SQL 语句使用索引情况
不是单独的索引类型,而是一种数据存储方式,指的是数据行跟相邻的键值紧凑的存储在一起
优点:
缺点:
数据文件跟索引文件分开存放
概述:
优势:
案例:
当发起一个被索引覆盖的查询时,在 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)
在大多数存储引擎中,覆盖索引只能覆盖那些只访问索引中部分列的查询。不过,可以进一步的进行优化,可以使用 innodb 的二级索引来覆盖查询。
例如:actor 使用 innodb 存储引擎,并在 ast_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)
当使用索引列进行查询的时候尽量不要使用表达式,把计算放到业务层而不是数据库层
select actor_id from actor where actor_id=4;
select actor_id from actor where actor_id+1=5;
尽量使用主键查询,而不是其他索引,因此主键查询不会触发回表查询
使用前缀索引:
有时候需要索引很长的字符串,这会让索引变的大且慢,通常情况下可以使用某个列开始的部分字符串,这样大大的节约索引空间,从而提高索引效率,但这会降低索引的选择性,索引的选择性是指不重复的索引值和数据表记录总数的比值,范围从 1/#T 到 1 之间。索引的选择性越高则查询效率越高,因为选择性更高的索引可以让 mysql 在查找的时候过滤掉更多的行。
一般情况下某个列前缀的选择性也是足够高的,足以满足查询的性能,但是对应 BLOB,TEXT,VARCHAR 类型的列,必须要使用前缀索引,因为 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 之后,再增加前缀长度,选择性提升的幅度已经很小了
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。
使用索引扫描来排序
mysql 有两种方式可以生成有序的结果:通过排序操作或者按索引顺序扫描,如果 explain 出来的 type 列的值为 index,则说明mysql使用了索引扫描来做排序
扫描索引本身是很快的,因为只需要从一条索引记录移动到紧接着的下一条记录。但如果索引不能覆盖查询所需的全部列,那么就不得不每扫描一条索引记录就得回表查询一次对应的行,这基本都是随机 IO,因此按索引顺序读取数据的速度通常要比顺序地全表扫描慢
mysql 可以使用同一个索引即满足排序,又用于查找行,如果可能的话,设计索引时应该尽可能地同时满足这两种任务。
只有当索引的列顺序和 order by 子句的顺序完全一致,并且所有列的排序方式都一样时,mysql 才能够使用索引来对结果进行排序,如果查询需要关联多张表,则只有当 order by 子句引用的字段全部为第一张表时,才能使用索引做排序。order by 子句和查找型查询的限制是一样的,需要满足索引的最左前缀的要求,否则,mysql 都需要执行顺序操作,而无法利用索引排序
--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)
union all,in,or 都能够使用索引,但是推荐使用 in
-- union
explain
select *
from actor
where actor_id = 1
union all
select *
from actor
where actor_id = 2;
-- in
explain
select *
from actor
where actor_id in (1,2);
-- or
explain
select *
from actor
where actor_id = 1 or actor_id =2;
范围列可以用到索引
强制类型转换会全表扫描
create table user(id int,name varchar(10),phone varchar(11));
alter table user add index idx_1(phone);
-- 不会触发索引
explain select * from user where phone = 13800001234;
-- 触发索引
explain select * from user where phone = '13800001234';
更新十分频繁,数据区分度不高的字段上不宜建立索引
创建索引的列,不允许为 null,可能会得到不符合预期的结果
当需要进行表连接的时候,最好不要超过三张表,因为需要 join 的字段,数据类型必须一致
能使用 limit 的时候尽量使用 limit
单表索引建议控制在 5 个以内
单索引字段数不允许超过 5 个(组合索引)
创建索引的时候应该避免以下错误概念
show status like 'Handler_read%';
准备数据:
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;
在编写快速的查询之前,需要清楚一点,真正重要的是响应时间,而且要知道在整个SQL语句的执行过程中每个步骤都花费了多长时间,要知道哪些步骤是拖垮执行效率的关键步骤,想要做到这点,必须要知道查询的生命周期,然后进行优化,不同的应用场景有不同的优化方式,不要一概而论,具体情况具体分析
查询性能低下的主要原因是访问的数据太多,某些查询不可避免的需要筛选大量的数据,我们可以通过减少访问数据量的方式进行优化
是否向数据库请求了不需要的数据
查询不需要的记录
我们常常会误以为 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 会优先检查这个查询是否命中查询缓存中的数据,如果查询恰好命中了查询缓存,那么会在返回结果之前会检查用户权限,如果权限没有问题,那么 mysql 会跳过所有的阶段,就直接从缓存中拿到结果并返回给客户端
mysql 查询完缓存之后会经过以下几个步骤:解析 SQL、预处理、优化 SQL 执行计划,这几个步骤出现任何的错误,都可能会终止查询
语法解析器和预处理:
mysql 通过关键字将 SQL 语句进行解析,并生成一颗解析树,mysql 解析器将使用 mysql 语法规则验证和解析查询,例如验证使用使用了错误的关键字或者顺序是否正确等等,预处理器会进一步检查解析树是否合法,例如表名和列名是否存在,是否有歧义,还会验证权限等等
查询优化器:
当语法树没有问题之后,相应的要由优化器将其转成执行计划,一条查询语句可以使用非常多的执行方式,最后都可以得到对应的结果,但是不同的执行方式带来的效率是不同的,优化器的最主要目的就是要选择最有效的执行计划
mysql 使用的是基于成本的优化器,在优化的时候会尝试预测一个查询使用某种查询计划时候的成本,并选择其中成本最小的一个
select count(*) from film_actor;
show status like 'last_query_cost';
可以看到这条查询语句大概需要做 1104 个数据页才能找到对应的数据,这是经过一系列的统计信息计算来的
在很多情况下 mysql 会选择错误的执行计划,原因如下:
优化器的优化策略:
优化器的优化类型:
重新定义关联表的顺序:数据表的关联并不总是按照在查询中指定的顺序进行,决定关联顺序时优化器很重要的功能
将外连接转化成内连接,内连接的效率要高于外连接
使用等价变换规则,mysql 可以使用一些等价变化来简化并规划表达式
优化 count(),min(),max():索引和列是否可以为空通常可以帮助 mysql 优化这类表达式:例如,要找到某一列的最小值,只需要查询索引的最左端的记录即可,不需要全文扫描比较
预估并转化为常数表达式,当 mysql 检测到一个表达式可以转化为常数的时候,就会一直把该表达式作为常数进行处理
explain
select film.film_id,film_actor.actor_id
from film inner
join film_actor using(film_id)
where film.film_id = 1
索引覆盖扫描,当索引中的列包含所有查询中需要使用的列的时候,可以使用覆盖索引
子查询优化:mysql 在某些情况下可以将子查询转换一种效率更高的形式,从而减少多个查询多次对数据进行访问,例如将经常查询的数据放入到缓存中
等值传播
如果两个列的值通过等式关联,那么 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;
3
关联查询:
mysql 的关联查询很重要,但其实关联查询执行的策略比较简单:mysql 对任何关联都执行嵌套循环关联操作,即 mysql 先在一张表中循环取出单条数据,然后再嵌套到下一个表中寻找匹配的行,依次下去,直到找到所有表中匹配的行为止。然后根据各个表匹配的行,返回查询中需要的各个列。mysql 会尝试再最后一个关联表中找到所有匹配的行,如果最后一个关联表无法找到更多的行之后,mysql 返回到上一层次关联表,看是否能够找到更多的匹配记录,以此类推迭代执行。整体的思路如此,但是要注意实际的执行过程中有多个变种形式:
join 的实现方式原理:
Simple Nested-Loop Join
Index Nested-Loop Join
Block Nested-Loop Join
show variables like '%optimizer_switch%'
案例演示:
查看不同的顺序执行方式对查询性能的影响:
explain
select film.film_id, film.title, film.release_year,
actor.actor_id, actor.first_name, actor.last_name
from film inner
join film_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 film inner
join film_actor using(film_id)
inner join actor using(actor_id);
查看执行的成本:show status like 'last_query_cost';
排序优化:
无论如何排序都是一个成本很高的操作,所以从性能的角度出发,应该尽可能避免排序或者尽可能避免对大量数据进行排序。
推荐使用利用索引进行排序,但是当不能使用索引的时候,mysql 就需要自己进行排序,如果数据量小则再内存中进行,如果数据量大就需要使用磁盘,mysql 中称之为 filesort。
如果需要排序的数据量小于排序缓冲区(show variables like '%sort_buffer_size%';
),mysql 使用内存进行快速排序操作,如果内存不够排序,那么 mysql 就会先将树分块,对每个独立的块使用快速排序进行排序,并将各个块的排序结果存放再磁盘上,然后将各个排好序的块进行合并,最后返回排序结果
排序算法:
两次传输排序:第一次数据读取是将需要排序的字段读取出来,然后进行排序,第二次是将排好序的结果按照需要去读取数据行。
这种方式效率比较低,原因是第二次读取数据的时候因为已经排好序,需要去读取所有记录而此时更多的是随机 IO,读取数据成本会比较高
两次传输的优势,在排序的时候存储尽可能少的数据,让排序缓冲区可以尽可能多的容纳行数来进行排序操作
单次传输排序:先读取查询所需要的所有列,然后再根据给定列进行排序,最后直接返回排序结果,此方式只需要一次顺序 IO 读取所有的数据,而无须任何的随机 IO,问题在于查询的列特别多的时候,会占用大量的存储空间,无法存储大量的数据
当需要排序的列的总大小超过 max_length_for_sort_data 定义的字节,mysql 会选择双次排序,反之使用单次排序,当然,用户可以设置此参数的值来选择排序的方式
count() 是特殊的函数,有两种不同的作用,一种是某个列值的数量,也可以统计行数
总有人认为 myisam 的 count 函数比较快,这是有前提条件的,只有没有任何 where 条件的 count(*) 才是比较快的
使用近似值:在某些应用场景中,不需要完全精确的值,可以参考使用近似值来代替,比如可以使用 explain 来获取近似的值
其实在很多 OLAP 的应用中,需要计算某一个列值的基数,有一个计算近似值的算法叫 hyperloglog。
更复杂的优化:一般情况下,count() 需要扫描大量的行才能获取精确的数据,其实很难优化,在实际操作的时候可以考虑使用索引覆盖扫描,或者增加汇总表,或者增加外部缓存系统。
子查询的优化最重要的优化建议是尽可能使用关联查询代替
优化此类查询的最简单的办法就是尽可能地使用覆盖索引,而不是查询所有的列
查看执行计划查看扫描的行数
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);
todo:行转列,列转行
mysql 总是通过创建并填充临时表的方式来执行 union 查询,因此很多优化策略在 union 查询中都没法很好的使用。经常需要手工的将 where、limit、order by 等子句下推到各个子查询中,以便优化器可以充分利用这些条件进行优化
除非确实需要服务器消除重复的行,否则一定要使用 union all,因此没有 all 关键字,mysql 会在查询的时候给临时表加上 distinct 的关键字,这个操作的代价很高
用户自定义变量是一个容易被遗忘的 mysql 特性,但是如果能够用好,在某些场景下可以写出非常高效的查询语句,在查询中混合使用过程化和关系话逻辑的时候,自定义变量会非常有用。
用户自定义变量是一个用来存储内容的临时容器,在连接 mysql 的整个过程中都存在。
开窗函数
使用:
set @one:=1
set @min_actor:=(select min(actor_id) from actor)
set @last_week:=current_date-interval 1 week;
限制:
:=
的优先级非常低,所以在使用赋值表达式的时候应该明确的使用括号案例:
优化排名语句
在给一个变量赋值的同时使用这个变量
select actor_id, @rownum:=@rownum+1 as rownum
from actor
limit 10;
查询获取演过最多电影的前 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;
确定取值的顺序
在赋值和读取变量的时候可能是在查询的不同阶段
-- 因为 where 和 select 在查询的不同阶段执行,所以看到查询到两条记录,这不符合预期
set @rownum:=0;
select actor_id, @rownum:=@rownum+1 as cnt
from actor
where @rownum<=1;
-- 当引入了 order by 之后,发现打印出了全部结果,这是因为 order by 引入了文件排序,而 where 条件是在文件排序操作之前取值的
set @rownum:=0;
select actor_id,@rownum:=@rownum+1 as cnt
from actor
where @rownum <= 1
order by first_name
-- 解决这个问题的关键在于让变量的赋值和取值发生在执行查询的同一阶段:
set @rownum:=0;
select actor_id,@rownum as cnt
from actor
where (@rownum:=@rownum+1) <= 1;
对于用户而言,分区表是一个独立的逻辑表,但是底层是由多个物理子表组成。分区表对于用户而言是一个完全封装底层实现的黑盒子,对用户而言是透明的,从文件系统中可以看到多个使用 # 分隔命名的表文件。
mysql 在创建表时使用 partition by 子句定义每个分区存放的数据,在执行查询的时候,优化器会根据分区定义过滤那些没有我们需要数据的分区,这样查询就无须扫描所有分区。
分区的主要目的是将数据安好一个较粗的力度分在不同的表中,这样可以将相关的数据存放在一起。
分区表由多个相关的底层表实现,这个底层表也是由句柄对象标识,我们可以直接访问各个分区。存储引擎管理分区的各个底层表和管理普通表一样(所有的底层表都必须使用相同的存储引擎),分区表的索引知识在各个底层表上各自加上一个完全相同的索引。从存储引擎的角度来看,底层表和普通表没有任何不同,存储引擎也无须知道这是一个普通表还是一个分区表的一部分。
分区表的操作按照以下的操作逻辑进行:
select 查询
当查询一个分区表的时候,分区层先打开并锁住所有的底层表,优化器先判断是否可以过滤部分分区,然后再调用对应的存储引擎接口访问各个分区的数据
insert 操作
当写入一条记录的时候,分区层先打开并锁住所有的底层表,然后确定哪个分区接受这条记录,再将记录写入对应底层表
delete 操作
当删除一条记录时,分区层先打开并锁住所有的底层表,然后确定数据对应的分区,最后对相应底层表进行删除操作
update 操作
当更新一条记录时,分区层先打开并锁住所有的底层表,mysql先确定需要更新的记录再哪个分区,然后取出数据并更新,再判断更新后的数据应该再哪个分区,最后对底层表进行写入操作,并对源数据所在的底层表进行删除操作
有些操作时支持过滤的,例如,当删除一条记录时,MySQL 需要先找到这条记录,如果 where 条件恰好和分区表达式匹配,就可以将所有不包含这条记录的分区都过滤掉,这对 update 同样有效。如果是 insert 操作,则本身就是只命中一个分区,其他分区都会被过滤掉。mysql 先确定这条记录属于哪个分区,再将记录写入对应得曾分区表,无须对任何其他分区进行操作
虽然每个操作都会“先打开并锁住所有的底层表”,但这并不是说分区表在处理过程中是锁住全表的,如果存储引擎能够自己实现行级锁,例如 innodb,则会在分区层释放对应表锁。
根据列值在给定范围内将行分配给分区
范围分区表的分区方式是:每个分区都包含行数据且分区的表达式在给定的范围内,分区的范围应该是连续的且不能重叠,可以使用 values less than 运算符来定义。
创建普通的表
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
);
创建带分区的表,下面建表的语句是按照 store_id 来进行分区的,指定了 4 个分区
在当前的建表语句中可以看到,store_id 的值在 1-5 的在 p0 分区,6-10 的在 p1 分区,11-15 的在 p3 分区,16-20 的在 p4 分区,但是如果插入超过 20 的值就会报错,因为 mysql 不知道将数据放在哪个分区
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),
PARTITION p1 VALUES LESS THAN (11),
PARTITION p2 VALUES LESS THAN (16),
PARTITION p3 VALUES LESS THAN (21)
);
可以使用 less than maxvalue 来避免此种情况
maxvalue 表示始终大于等于最大可能整数值的整数值
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),
PARTITION p1 VALUES LESS THAN (11),
PARTITION p2 VALUES LESS THAN (16),
PARTITION p3 VALUES LESS THAN MAXVALUE
);
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),
PARTITION p1 VALUES LESS THAN (11),
PARTITION p2 VALUES LESS THAN (16),
PARTITION p3 VALUES LESS THAN MAXVALUE
);
可以使用相同的方式根据员工的职务代码对表进行分区
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)
);
可以使用 date 类型进行分区:如虚妄根据每个员工离开公司的年份进行划分,如 year(separated)
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
);
可以使用函数根据 range 的值来对表进行分区,如 timestampunix_timestamp()
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)
);
基于时间间隔的分区方案,在 mysql5.7 中,可以基于范围或事件间隔实现分区方案,有两种选择
基于范围的分区,对于分区表达式,可以使用操作函数基于 date、time、或者 datatime 列来返回一个整数值
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
);
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)
);
基于范围列的分区,使用 date 或者 datatime 列作为分区列
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 COLUMNS(joined) (
PARTITION p0 VALUES LESS THAN ('1960-01-01'),
PARTITION p1 VALUES LESS THAN ('1970-01-01'),
PARTITION p2 VALUES LESS THAN ('1980-01-01'),
PARTITION p3 VALUES LESS THAN ('1990-01-01'),
PARTITION p4 VALUES LESS THAN MAXVALUE
);
案例:
#不分区的表
CREATE TABLE no_part_tab
(id INT DEFAULT NULL,
remark VARCHAR(50) DEFAULT NULL,
d_date DATE DEFAULT NULL
)ENGINE=MYISAM;
#分区的表
CREATE TABLE part_tab
(id INT DEFAULT NULL,
remark VARCHAR(50) DEFAULT NULL,
d_date DATE DEFAULT NULL
)ENGINE=MYISAM
PARTITION BY RANGE(YEAR(d_date))(
PARTITION p0 VALUES LESS THAN(1995),
PARTITION p1 VALUES LESS THAN(1996),
PARTITION p2 VALUES LESS THAN(1997),
PARTITION p3 VALUES LESS THAN(1998),
PARTITION p4 VALUES LESS THAN(1999),
PARTITION p5 VALUES LESS THAN(2000),
PARTITION p6 VALUES LESS THAN(2001),
PARTITION p7 VALUES LESS THAN(2002),
PARTITION p8 VALUES LESS THAN(2003),
PARTITION p9 VALUES LESS THAN(2004),
PARTITION p10 VALUES LESS THAN maxvalue);
#插入未分区表记录
DROP PROCEDURE IF EXISTS no_load_part;
DELIMITER//
CREATE PROCEDURE no_load_part()
BEGIN
DECLARE i INT;
SET i =1;
WHILE i<80001
DO
INSERT INTO no_part_tab VALUES(i,'no',ADDDATE('1995-01-01',(RAND(i)*36520) MOD 3652));
SET i=i+1;
END WHILE;
END//
DELIMITER ;
CALL no_load_part;
#插入分区表记录
DROP PROCEDURE IF EXISTS load_part;
DELIMITER&&
CREATE PROCEDURE load_part()
BEGIN
DECLARE i INT;
SET i=1;
WHILE i<80001
DO
INSERT INTO part_tab VALUES(i,'partition',ADDDATE('1995-01-01',(RAND(i)*36520) MOD 3652));
SET i=i+1;
END WHILE;
END&&
DELIMITER ;
CALL load_part;
类似于按 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,
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)
);
mysql 从 5.5 开始支持 column 分区,可以认为i是 range 和 list 的升级版,在 5.5 之后,可以使用 column 分区替代 range 和 list,但是 column 分区只接受普通列不接受表达式
CREATE TABLE `list_c` (
`c1` int(11) DEFAULT NULL,
`c2` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1
/*!50500 PARTITION BY RANGE COLUMNS(c1)
(PARTITION p0 VALUES LESS THAN (5) ENGINE = InnoDB,
PARTITION p1 VALUES LESS THAN (10) ENGINE = InnoDB) */
CREATE TABLE `list_c` (
`c1` int(11) DEFAULT NULL,
`c2` int(11) DEFAULT NULL,
`c3` char(20) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1
/*!50500 PARTITION BY RANGE COLUMNS(c1,c3)
(PARTITION p0 VALUES LESS THAN (5,'aaa') ENGINE = InnoDB,
PARTITION p1 VALUES LESS THAN (10,'bbb') ENGINE = InnoDB) */
CREATE TABLE `list_c` (
`c1` int(11) DEFAULT NULL,
`c2` int(11) DEFAULT NULL,
`c3` char(20) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1
/*!50500 PARTITION BY LIST COLUMNS(c3)
(PARTITION p0 VALUES IN ('aaa') ENGINE = InnoDB,
PARTITION p1 VALUES IN ('bbb') ENGINE = InnoDB) */
基于用户定义的表达式的返回值来进行选择的分区,该表达式使用将要插入到表中的这些行的列值进行计算。这个函数可以包含 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;
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 LINEAR HASH(YEAR(hired))
PARTITIONS 4;
类似于 hash 分区,区别在于 key 分区只支持一列或多列,且 mysql 服务器提供其自身的哈希函数,必须有一列或多列包含整数值
CREATE TABLE tk (
col1 INT NOT NULL,
col2 CHAR(5),
col3 DATE
)
PARTITION BY LINEAR KEY (col1)
PARTITIONS 3;
在分区的基础之上,再进行分区后存储
CREATE TABLE `t_partition_by_subpart`
(
`id` INT AUTO_INCREMENT,
`sName` VARCHAR(10) NOT NULL,
`sAge` INT(2) UNSIGNED ZEROFILL NOT NULL,
`sAddr` VARCHAR(20) DEFAULT NULL,
`sGrade` INT(2) NOT NULL,
`sStuId` INT(8) DEFAULT NULL,
`sSex` INT(1) UNSIGNED DEFAULT NULL,
PRIMARY KEY (`id`, `sGrade`)
) ENGINE = INNODB
PARTITION BY RANGE(id)
SUBPARTITION BY HASH(sGrade) SUBPARTITIONS 2
(
PARTITION p0 VALUES LESS THAN(5),
PARTITION p1 VALUES LESS THAN(10),
PARTITION p2 VALUES LESS THAN(15)
);
如果需要从非常大的表中查询出某一段时间的记录,而这张表中包含很多年的历史数据,数据是按照时间排序的,此时应该如何查询数据呢?
因为数据量巨大,肯定不能在每次查询的时候都扫描全表。考虑到索引在空间和维护上的消耗,也不希望使用索引,即使使用索引,会发现会产生大量的碎片,还会产生大量的随机 IO,但是当数据量超大的时候,索引也就无法起作用了,此时可以考虑使用分区来进行解决
全量扫描数据,不要任何索引:
使用简单的分区方式存放表,不要任何索引,根据分区规则大致定位需要的数据为止,通过使用where条件将需要的数据限制在少数分区中,这种策略适用于以正常的方式访问大量数据
索引数据,并分离热点:
如果数据有明显的热点,而且除了这部分数据,其他数据很少被访问到,那么可以将这部分热点数据单独放在一个分区中,让这个分区的数据能够有机会都缓存在内存中,这样查询就可以只访问一个很小的分区表,能够使用索引,也能够有效的使用缓存
# 数据文件存放的目录
datadir=/var/lib/mysql
# mysql.socket 表示 server 和 client 在同一台服务器,并且使用 localhost 进行连接,就会使用 socket 进行连接
socket=/var/lib/mysql/mysql.sock
# 存储 mysql 的 pid
pid_file=/var/lib/mysql/mysql.pid
# mysql 服务的端口号
port=3306
# mysql 存储引擎
default_storage_engine=InnoDB
# 当忘记 mysql 的用户名密码的时候,可以在 mysql 配置文件中配置该参数,跳过权限表验证,不需要密码即可登录 mysql
skip-grant-tables
key_buffer_size:
索引缓存区的大小(只对 myisam 表起作用)
query cache:
查询缓存的大小,未来版本被删除
show status like '%Qcache%';
:查看缓存的相关属性query_cache_limit:
超出此大小的查询将不被缓存
query_cache_min_res_unit:
缓存块最小大小
query_cache_type:
缓存类型,决定缓存什么样的查询
sort_buffer_size:
每个需要排序的线程分派该大小的缓冲区
max_allowed_packet=32M:
限制 server 接受的数据包大小
join_buffer_size=2M:
表示关联缓存的大小
thread_cache_size:
服务器线程缓存,这个值表示可以重新利用保存再缓存中的线程数量,当断开连接时,那么客户端的线程将被放到缓存中以响应下一个客户而不是销毁,如果线程重新被请求,那么请求将从缓存中读取,如果缓存中是空的或者是新的请求,这个线程将被重新请求,那么这个线程将被重新创建,如果有很多新的线程,增加这个值即可