性能监控 使用show profile查询剖析工具,可以指定具体的type 此工具默认是禁用的,可以通过服务器变量在会话级别动态的修改 set profiling=1; 当设置完成之后,在服务器上执行的所有语句,都会测量其耗费的时间和其他一些查询执行状态变更相关的数据。 select * from emp; 在mysql的命令行模式下只能显示两位小数的时间,可以使用如下命令查看具体的执行时间 show profiles; 执行如下命令可以查看详细的每个步骤的时间: show profile for query 1; type all:显示所有性能信息 show profile all for query n block io:显示块io操作的次数 show profile block io for query n context switches:显示上下文切换次数,被动和主动 show profile context switches for query n cpu:显示用户cpu时间、系统cpu时间 show profile cpu for query n IPC:显示发送和接受的消息数量 show profile ipc for query n page faults:显示页错误数量 show profile page faults for query n source:显示源码中的函数名称与位置 show profile source for query n swaps:显示swap的次数 show profile swaps for query n 使用performance schema来更加容易的监控mysql 使用show processlist查看连接的线程个数,来观察是否有大量线程处于不正常的状态或者其他不正常的特征 属性说明 id表示session id user表示操作的用户 host表示操作的主机 db表示操作的数据库 command表示当前状态 sleep:线程正在等待客户端发送新的请求 query:线程正在执行查询或正在将结果发送给客户端 locked:在mysql的服务层,该线程正在等待表锁 analyzing and statistics:线程正在收集存储引擎的统计信息,并生成查询的执行计划 Copying to tmp table:线程正在执行查询,并且将其结果集都复制到一个临时表中 sorting result:线程正在对结果集进行排序 sending data:线程可能在多个状态之间传送数据,或者在生成结果集或者向客户端返回数据 info表示详细的sql语句 time表示相应命令执行时间 state表示命令执行状态 schema与数据类型优化 数据类型的优化 更小的通常更好 简单就好 整型比字符操作代价更低,因为字符集和校对规则是字符比较比整型比较更复杂, 使用mysql自建类型而不是字符串来存储日期和时间 用整型存储IP地址 尽量避免null 如果查询中包含可为NULL的列,对mysql来说很难优化,因为可为null的列使得索引、索引统计和值比较都更加复杂,坦白来说,通常情况下null的列改为not null带来的性能提升比较小,所有没有必要将所有的表的schema进行修改,但是应该尽量避免设计成可为null的列 实际细则 整数类型 可以使用的几种整数类型:TINYINT,SMALLINT,MEDIUMINT,INT,BIGINT分别使用8,16,24,32,64位存储空间。 尽量使用满足需求的最小数据类型 字符和字符串类型 varchar根据实际内容长度保存数据 char固定长度的字符串 BLOB和TEXT类型 MySQL 把每个 BLOB 和 TEXT 值当作一个独立的对象处理。 两者都是为了存储很大数据而设计的字符串类型,分别采用二进制和字符方式存储。 datetime和timestamp 1、不要使用字符串类型来存储日期时间数据 2、日期时间类型通常比字符串占用的存储空间小 3、日期时间类型在进行查找过滤时可以利用日期来进行比对 4、日期时间类型还有着丰富的处理函数,可以方便的对时间类型进行日期计算 5、使用int存储日期时间不如使用timestamp类型 使用枚举代替字符串类型 有时可以使用枚举类代替常用的字符串类型,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) 合理使用范式和反范式 范式 优点: 范式化的更新通常比反范式要快 当数据较好的范式化后,很少或者没有重复的数据 范式化的数据比较小,可以放在内存中,操作比较快 缺点: 通常需要进行关联 案例
反范式
优点: 所有的数据都在同一张表中,可以避免关联 可以设计有效的索引; 缺点: 表格内的冗余较多,删除数据时候会造成表有些有用的信息丢失 案例:
主键的选择
代理主键 与业务无关的,无意义的数字序列 自然主键 事物属性中的自然唯一标识 推荐使用代理主键 它们不与业务耦合,因此更容易维护 一个大多数表,最好是全部表,通用的键策略能够减少需要编写的源码数量,减少系统的总体拥有成本 字符集的选择 1.纯拉丁字符能表示的内容,没必要选择 latin1 之外的其他字符编码,因为这会节省大量的存储空间。 2.如果我们可以确定不需要存放多种语言,就没必要非得使用UTF8或者其他UNICODE字符类型,这回造成大量的存储空间浪费。 3.MySQL的数据类型可以精确到字段,所以当我们需要大型数据库中存放多字节数据的时候,可以通过对不同表不同字段使用不同的数据类型来较大程度减小数据存储量,进而降低 IO 操作次数并提高缓存命中率。 存储引擎的选择
适当的数据冗余
1.被频繁引用且只能通过 Join 2张(或者更多)大表的方式才能得到的独立小字段。 2.这样的场景由于每次Join仅仅只是为了取得某个小字段的值,Join到的记录又大,会造成大量不必要的 IO,完全可以通过空间换取时间的方式来优化。不过,冗余的同时需要确保数据的一致性不会遭到破坏,确保更新的同时冗余字段也被更新。 适当拆分 当我们的表中存在类似于 TEXT 或者是很大的 VARCHAR类型的大字段的时候,如果我们大部分访问这张表的时候都不需要这个字段,我们就该义无反顾的将其拆分到另外的独立表中,以减少常用数据所占用的存储空间。这样做的一个明显好处就是每个数据块中可以存储的数据条数可以大大增加,既减少物理 IO 次数,也能大大提高内存中的缓存命中率。 通过索引进行优化 索引基本知识 索引的优点 1、大大减少了服务器需要扫描的数据量 2、帮助服务器避免排序和临时表 3、将随机io变成顺序io 索引的用处 1、快速查找匹配WHERE子句的行 2、从consideration中消除行,如果可以在多个索引之间进行选择,mysql通常会使用找到最少行的索引 3、如果表具有多列索引,则优化器可以使用索引的任何最左前缀来查找行 4、当有表连接的时候,从其他表检索行数据 5、查找特定索引列的min或max值 6、如果排序或分组时在可用索引的最左前缀上完成的,则对表进行排序和分组 7、在某些情况下,可以优化查询以检索值而无需查询数据行 索引的分类 主键索引:主键做索引 唯一索引:唯一键做索引 普通索引:普通列做索引 全文索引:text,varchar或char列做索引 组合索引:多个列做索引 面试技术名词 回表:回表指b+树先利用其它字段查出表id,再利用id查出记录信息 覆盖索引:b+树子节点本来存放的是非id字段,但是现在直接存放id,即覆盖 最左匹配:多个索引字段,组合索引,where字段必须满足左边要都匹配 索引下推:先按索引过滤再关联查询 索引采用的数据结构 哈希表 B+树 索引匹配方式 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'; 哈希索引 组合索引 聚簇索引与非聚簇索引 覆盖索引 优化小细节 索引监控 简单案例 查询优化 查询慢的原因 网络 CPU IO 上下文切换 系统调用 生成统计信息 锁等待时间 优化数据访问 查询性能低下的主要原因是访问的数据太多,某些查询不可避免的需要筛选大量的数据,我们可以通过减少访问数据量的方式进行优化 确认应用程序是否在检索大量超过需要的数据 确认mysql服务器层是否在分析大量超过需要的数据行 是否向数据库请求了不需要的数据 查询不需要的记录 我们常常会误以为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会选择错误的执行计划,原因如下: 统计信息不准确:InnoDB因为其mvcc的架构,并不能维护一个数据表的行数的精确统计信息 执行计划的成本估算不等同于实际执行的成本: 有时候某个执行计划虽然需要读取更多的页面,但是他的成本却更小,因为如果这些页面都是顺序读或者这些页面都已经在内存中的话,那么它的访问成本将很小,mysql层面并不知道哪些页面在内存中,哪些在磁盘,所以查询之际执行过程中到底需要多少次IO是无法得知的 mysql的最优可能跟你想的不一样:mysql的优化是基于成本模型的优化,但是有可能不是最快的优化 mysql不考虑其他并发执行的查询 mysql不会考虑不受其控制的操作成本:执行存储过程或者用户自定义函数的成本 优化器的优化策略 静态优化:直接对解析树进行分析,并完成优化 动态优化:动态优化与查询的上下文有关,也可能跟取值、索引对应的行数有关 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 关联查询 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%' 案例演示 查看不同的顺序执行方式对查询性能的影响: 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'; 排序优化 排序的算法 两次传输排序 第一次数据读取是将需要排序的字段读取出来,然后进行排序,第二次是将排好序的结果按照需要去读取数据行。 这种方式效率比较低,原因是第二次读取数据的时候因为已经排好序,需要去读取所有记录而此时更多的是随机IO,读取数据成本会比较高 两次传输的优势,在排序的时候存储尽可能少的数据,让排序缓冲区可以尽可能多的容纳行数来进行排序操作 单次传输排序 先读取查询所需要的所有列,然后再根据给定列进行排序,最后直接返回排序结果,此方式只需要一次顺序IO读取所有的数据,而无须任何的随机IO,问题在于查询的列特别多的时候,会占用大量的存储空间,无法存储大量的数据 当需要排序的列的总大小超过max_length_for_sort_data定义的字节,mysql会选择双次排序,反之使用单次排序,当然,用户可以设置此参数的值来选择排序的方式 优化特定类型的查询 优化count()查询 总有人认为myisam的count函数比较快,这是有前提条件的,只有没有任何where条件的count(*)才是比较快的 使用近似值:在某些应用场景中,不需要完全精确的值,可以参考使用近似值来代替,比如可以使用explain来获取近似的值 其实在很多OLAP的应用中,需要计算某一个列值的基数,有一个计算近似值的算法叫hyperloglog。 更复杂的优化:一般情况下,count()需要扫描大量的行才能获取精确的数据,其实很难优化,在实际操作的时候可以考虑使用索引覆盖扫描,或者增加汇总表,或者增加外部缓存系统。 优化关联查询 确保on或者using子句中的列上有索引,在创建索引的时候就要考虑到关联的顺序 当表A和表B使用列C关联的时候,如果优化器的关联顺序是B、A,那么就不需要再B表的对应列上建上索引,没有用到的索引只会带来额外的负担,一般情况下来说,只需要在关联顺序中的第二个表的相应列上创建索引 确保任何的groupby和order by中的表达式只涉及到一个表中的列,这样mysql才有可能使用索引来优化这个过程 优化子查询 子查询的优化最重要的优化建议是尽可能使用关联查询代替,因为子查询会产生临时表,也根据具体情况,如果子查询可以过滤大量数据,还是推荐使用子查询 优化limit分页 优化此类查询的最简单的办法就是尽可能地使用覆盖索引,而不是查询所有的列 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); 查看执行计划查看扫描的行数 优化union查询 除非确实需要服务器消除重复的行,否则一定要使用union all,因此没有all关键字,mysql会在查询的时候给临时表加上distinct的关键字,这个操作的代价很高 推荐使用用户自定义变量 自定义变量的使用 set @one :=1 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; 确定取值的顺序 在赋值和读取变量的时候可能是在查询的不同阶段 set @rownum:=0; select actor_id,@rownum:=@rownum+1 as cnt from actor where @rownum<=1; 因为where和select在查询的不同阶段执行,所以看到查询到两条记录,这不符合预期 set @rownum:=0; select actor_id,@rownum:=@rownum+1 as cnt from actor where @rownum<=1 order by first_name 当引入了orde;r by之后,发现打印出了全部结果,这是因为order by引入了文件排序,而where条件是在文件排序操作之前取值的 解决这个问题的关键在于让变量的赋值和取值发生在执行查询的同一阶段: set @rownum:=0; select actor_id,@rownum as cnt from actor where (@rownum:=@rownum+1)<=1; 分区表 对于用户而言,分区表是一个独立的逻辑表,但是底层是由多个物理子表组成。分区表对于用户而言是一个完全封装底层实现的黑盒子,对用户而言是透明的,从文件系统中可以看到多个使用#分隔命名的表文件。 mysql在创建表时使用partition by子句定义每个分区存放的数据,在执行查询的时候,优化器会根据分区定义过滤那些没有我们需要数据的分区,这样查询就无须扫描所有分区。 分区的主要目的是将数据安好一个较粗的力度分在不同的表中,这样可以将相关的数据存放在一起。 分区表的应用场景 表非常大以至于无法全部都放在内存中,或者只在表的最后部分有热点数据,其他均是历史数据 分区表的数据更容易维护 批量删除大量数据可以使用清除整个分区的方式 对一个独立分区进行优化、检查、修复等操作 分区表的数据可以分布在不同的物理设备上,从而高效地利用多个硬件设备 可以使用分区表来避免某些特殊的瓶颈 innodb的单个索引的互斥访问 ext3文件系统的inode锁竞争 可以备份和恢复独立的分区 分区表的限制 一个表最多只能有1024个分区,在5.7版本的时候可以支持8196个分区 在早期的mysql中,分区表达式必须是整数或者是返回整数的表达式,在mysql5.5中,某些场景可以直接使用列来进行分区 如果分区字段中有主键或者唯一索引的列,那么所有主键列和唯一索引列都必须包含进来 分区表无法使用外键约束 分区表的原理 分区表由多个相关的底层表实现,这个底层表也是由句柄对象标识,我们可以直接访问各个分区。存储引擎管理分区的各个底层表和管理普通表一样(所有的底层表都必须使用相同的存储引擎),分区表的索引知识在各个底层表上各自加上一个完全相同的索引。从存储引擎的角度来看,底层表和普通表没有任何不同,存储引擎也无须知道这是一个普通表还是一个分区表的一部分。 分区表的操作按照以下的操作逻辑进行: select查询 当查询一个分区表的时候,分区层先打开并锁住所有的底层表,优化器先判断是否可以过滤部分分区,然后再调用对应的存储引擎接口访问各个分区的数据 insert操作 当写入一条记录的时候,分区层先打开并锁住所有的底层表,然后确定哪个分区接受这条记录,再将记录写入对应底层表 delete操作 当删除一条记录时,分区层先打开并锁住所有的底层表,然后确定数据对应的分区,最后对相应底层表进行删除操作 update操作 当更新一条记录时,分区层先打开并锁住所有的底层表,mysql先确定需要更新的记录再哪个分区,然后取出数据并更新,再判断更新后的数据应该再哪个分区,最后对底层表进行写入操作,并对源数据所在的底层表进行删除操作 有些操作时支持过滤的,例如,当删除一条记录时,MySQL需要先找到这条记录,如果where条件恰好和分区表达式匹配,就可以将所有不包含这条记录的分区都过滤掉,这对update同样有效。如果是insert操作,则本身就是只命中一个分区,其他分区都会被过滤掉。mysql先确定这条记录属于哪个分区,再将记录写入对应得曾分区表,无须对任何其他分区进行操作 虽然每个操作都会“先打开并锁住所有的底层表”,但这并不是说分区表在处理过程中是锁住全表的,如果存储引擎能够自己实现行级锁,例如innodb,则会在分区层释放对应表锁。 分区表的类型 范围分区:根据列值在给定范围内将行分配给分区 列表分区:类似于按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) */ 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; 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; key分区:类似于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条件将需要的数据限制在少数分区中,这种策略适用于以正常的方式访问大量数据 索引数据,并分离热点 如果数据有明显的热点,而且除了这部分数据,其他数据很少被访问到,那么可以将这部分热点数据单独放在一个分区中,让这个分区的数据能够有机会都缓存在内存中,这样查询就可以只访问一个很小的分区表,能够使用索引,也能够有效的使用缓存 在使用分区表的时候需要注意的问题 null值会使分区过滤无效 分区列和索引列不匹配,会导致查询无法进行分区过滤 选择分区的成本可能很高 打开并锁住所有底层表的成本可能很高 维护分区的成本可能很高 行转列 create table tmp(rq varchar(10),shengfu varchar(5)); insert into tmp values('2005-05-09','sheng'); insert into tmp values('2005-05-09','sheng'); insert into tmp values('2005-05-09','fu'); insert into tmp values('2005-05-09','fu'); insert into tmp values('2005-05-10','sheng'); insert into tmp values('2005-05-10','fu'); insert into tmp values('2005-05-10','fu'); SELECT rq, count(CASE WHEN shengfu = 'sheng' THEN 1 END ) sheng, count(CASE WHEN shengfu = 'fu' THEN 2 END ) fu FROM tmp GROUP BY rq; 2005-05-09 2 2 2005-05-10 1 2