分区就是按一定的规则将偌大的一张mysql数据表切割成若干个表分区,物理上以文件形式分开存储,但是逻辑上是一张完成的表,对代码层也是透明的,理论上无需修改任何代码就能“完美”优化,但其实也存在一些隐藏的陷阱。分区无法解决数据库并发连接的性能问题,数据表分区也有它的瓶颈,数据庞大到一定量级的时候,还是需要做分表分库处理。
分区表按照类型可以分为范围分区(Range)、列表分区(List)以及哈希分区(Hash),表被分区后,其对应的索引也会与普通表的索引有所不同。
对于分区表上的索引可以分为两类:本地索引和全局索引。其中全局索引又可以分为分区索引和未分区索引,而本地索引必须为分区索引。因此若某个索引未分区,可以直接断定其为全局索引;在oracle中分区表也可以定义全局索引,但mysql一旦被分区,全局索引就不再支持,只存在本地索引,即分区内的索引。
Mysql分区的话索引也是按照分区子表定义的,而没有全局索引。所以Mysql表如果分区的话就只有本地索引,不存在全局索引.
分区在性能方面提升的是数据库的IO读取性能,mysql数据存储,归根到底所有的数据是按文件存储的,一旦数据变得庞大,mysql读取性能就会下降,类似于一个几个G的日志文件普通编辑器无法打开,必须切割成若干个小日志文件才能处理一样.
表分区的语句,例如:
PARTITION BY RANGE (store_id) 就是按store_id字段作为分区依据来分区,当store_id小于6的为p0区,小于11的为p1区,以此类推,一共分了4个区,这是范围分区,是比较常用的分区手段,使用RANGE 作为分区有一些规则和限制,它可以使用函数表达式,比如year(),to_date() 这种函数,但是只能是返回的一个确定的整数,且不能是一个常数,如果需要按某个字段做分区的话,如日期字段(不为整数)可以使用RANGE COLUMNS()来实现。
还需要注意的是按某一字段做条件分区的时候,比如store_id这个字段,一定是包含在主键或者唯一键范围内的,以上例子因为没有设置主键,所以能够做表分区,一旦存在主键,假如主键为id的话,store_id是不能作为分区键的,如果一定要将store_id作为主区键,就要将id和store_id作为一个联合主键来设置。
以下是一些可能出现的问题,来自《高性能Mysql》一书
NULL位会使分区过滤无效
关于分区表一个容易让人误解的地方就是分区的表达式的值可以是NULL:第一个分区是一个特殊分区。假设按照PARTITION BY RANGE YEAR(order_date)分区,那么所有order_date为NULL或者是一个非法值的时候,记录都会被存放到第一个分区。现在假设有 下面的查询:WHERE order_date BETWEEN ‘2012-01-01’AND’2012-01-31’。实际上,MySQL会检查两个分区,而不是之前猜想的 一个:它会检查2012年这个分区,同时它还会检查这个表的第一个分区。检查第一个分区是因为YEAR()函数在接收非法值的时候 可能会返回NULL值,那么这个范围的值可能会返回NULL而被存放到第一个分区了。这一点对于其他很多函数,例如TO_DAYS()也一 样。
如果第一个分区非常大,特别是当使用“全量扫描数据,不要任何索引”的策略时,代价会非常大。而且扫描两个分区来查找列 也不是我们使用分区表的初衷。为了避免这种情况,可以创建一个“无用”的第一个分区,例如,上面的例子中可以使用PARTITION p_nulls VALUES LESS THAN(0)来创建第一个分区。如果插入表中的数据都是有效的,那么第一个分区就是空的,这样即使需要检 测第一个分区,代价也会非常小。
在MySQL5.5中就不需要这个优化技巧了,因为可以直接使用列本身而不是基于列的函数进行分区:PARTITION BY RANGE COLUMNS(order_date).所以这个案例最好的解决方越是能够直接使用MySQL5.5的这个语法。
分区列和索引列不匹配
如果定义的索引列和分区列不匹配,会导致查询无法进行分区过滤。假设在列a上定义了索引,而在列b上进行分区。因为每个分区 都有其独立的索引,所以扫描列b上的索引就需要扫描每一个分区内对应的索引。如果每个分区内对应索引的非叶子节点都在内存 中,那么扫描的速度还可以接受,但如果能跳过某些分区索引当然会更好。要避免这个问题,应该避免建立和分区列不匹配的索 引,除非查询中还同时包含了可以过滤分区的条件。
听起来避免这个问题很简单,不过有时候也会遇到一些意想不到的问题。例如,在一个关联查询中,分区表在关联顺序中是第二个 表,井且关联使用的索引和分区条件并不匹配。那么关联时针对第一个表符合条件的每一行,都需要访问并搜索第二个表的所有分 区。
选择分区的成本可能很高
如前所述分区有很多类型,不同类型分区的实现方式也不同,所以它们的性能也各不相同。尤其是范围分区,对于回答“这一行 属于哪个分区”、“这些符合查询条件的行在哪些分区”这样的问题的成本可能会非常高,因为服务器需要扫描所有的分区定义的 列表来找到正确的答案。类似这样的线性搜索的效率不高,所以随着分区数的增长,成本会越来越高。
我们所实际碰到的类似这样的最糟牒的一次问题是按行写入大量数据的时候。每写入一行数据到范围分区的表时,都需要扫描分区 定义列表来找到合适的目标分区。可以通过限制分区的数量来缓解此问题,根据实践经验,对大多数系统来说,100个左右的分区是 没有问题的。其他的分区类型,比如键分区和哈希分区,则没有这样的问题。
打开并锁住所有底层表的成本可能很高
当查询访问分区表的时候,MySQL需要打开井锁住所有的底层表,这是分区表的另一个开销。这个操作在分区过滤之前发生,所以无 法通过分区过滤降低此开销,并且该开销也和分区类型无关,会影响所有的查询。这一点对一些本身操作非常快的查询,比如根据 主键查找单行,会带来明显的额外开销。可以用批量操作的方式来降低单个操作的此类开销,例如使用批量插入或者 LOAD DATA INFILE、一次删除多行数据,等等。当然同时还是需要限制分区的个数。
维护分区的成本可能很高
某些分区维护操作的速度会非常快,例如新增或者删除分区(当删除一个大分区可能会很慢,不过这是另一回事)。而有些操作, 例如重组分区或者类似ALTER语句的操作:这类操作需要复制数据。重组分区的原理与ALTER类似,先创建一个临时的分区,然后将 数据复制到其中,最后再删除原分区。
如上所述,分区表不是什么“银弹”。下面是目前分区实现中的一些其他限制:如上所述,分区表不是什么“银弹”。下面是目前分区实现中的一些其他限制:
·
所有分区都必须使用相同的存储引擎。
·
·
分区函数中可以使用的函数和表达式也有一些限制。某些存储引擎不支持分区。
·
·
对于MyISAM的分区表,不能再使用LOAD INDEXI INTO CACHE操作。
对于MyISAM表,使用分区表时需要打开更多的文件描述符。虽然看起来是一个表其实背后有很多独立的分区,每一个分区对于存储引擎来说都是一个独立的表。这样即使分区表只占用一个表缓存条目,文件描述符还是需要多个。因此,即使已经配置了合适的表缓存,以确保不会超过操作系统的单个进程可以打开的文件描述符的个数,但对于分区表而言,还是会出现超过文件描述符限制的问题。
最后,需要指出的是较老版本的MySQL问题会更多些。所有的软件都是有bug的。分区表在MySQL5.1中引人,在后面的5.1.40和5.1.50之后修复了很多分区表的b吨。MySQL5.5中,分区表又做了很多改进,这才使得分区表可以逐步考虑用在生产环境了在即将发布的MySQL5.6版本中,分区表做了更多的增强,例如新引火的ALTER TABLE EXCHANGE PARTITION。
个人的一些思考
使用分区技术能小成本的优化数据库性能,无论是代码量或者硬件成本都是比较小的,但一定要清楚分区规则和可能出现的陷阱才能规避或优化,做到更好的利用分区,如果使用,最好将现有的相关的业务全部提取出来,对涉及的sql进行评审或重构,虽然数据库分区对于代码层是完全透明的,但是,最好所有的sql都带上分区键,即使是多余的条件也应该能加上的都加上,这样才能最大程度的过滤不需要的分区,其实分区就像是颗粒度更大的索引,将分区片确定的范围越小查询速度越快。
如果使用分区,还是担心一些全局查询的效率,因为一旦分区,mysql是不支持全局索引的,这样就没有了全局索引做技术支撑,全表查询将是性能痛点,例如订单分页,需要查询所有再排序,不走索引,将是全表的随机读取,性能会十分低,看是否能将分区键加入,实现单个分区分页的实现,这样效率就会好很多,因为类似订单分页这种不算一个严格分页框架,只要实现顺序加载就能实现列表展示,这样逐一分区读数的效率比全表扫描性能会提升很多,当然这种分区规则就得是按一定的时间顺序或自增规则的范围分区才能实现的。