Mysql 分区无法正常工作

Mysql Partition Doesn't Work Correctly

我有一个按日期分区的分区 table,这是我的 table 定义:

CREATE TABLE `BBDD` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `MSISDN` int(11) DEFAULT NULL,
  `Nombre` varchar(255) DEFAULT NULL,
  `CIF_NIF` varchar(255) DEFAULT NULL,
  `phone` int(11) DEFAULT NULL,
  `PLANDEPRECIOS` varchar(255) DEFAULT NULL,
  .
  ..
  ...
  `Operador` varchar(150) DEFAULT NULL,
  PRIMARY KEY (`id`,`fecha_carga`),
  KEY `MSISDN` (`MSISDN`),
  KEY `MSISDN_2` (`MSISDN`),
  KEY `BBDD` (`BBDD`)
) ENGINE=InnoDB AUTO_INCREMENT=1607074 DEFAULT CHARSET=latin1
/*!50100 PARTITION BY RANGE ( to_days(fecha_carga))
(PARTITION p20120701 VALUES LESS THAN (735050) ENGINE = InnoDB,
 PARTITION p20120801 VALUES LESS THAN (735081) ENGINE = InnoDB,
  .
  ..
  ...
 PARTITION p20181001 VALUES LESS THAN (737333) ENGINE = InnoDB,
 PARTITION p20181101 VALUES LESS THAN (737364) ENGINE = InnoDB,
 PARTITION p20181201 VALUES LESS THAN (737394) ENGINE = InnoDB,
 PARTITION pdefault VALUES LESS THAN MAXVALUE ENGINE = InnoDB) */

行数约为 1330122,所以现在是测试我的分区和我的分区扫描的行数的时候了,我们开始吧:

EXPLAIN PARTITIONS    SELECT  *
    FROM  adsl.BBDD
    WHERE  fecha_carga >=
              cast(date_format(DATE_ADD(now(),INTERVAL -1 month),
                              '%Y-%m-01') as date )
      and  (MSISDN=622605810
              or  CIF_NIF=622605810
           ) ; 


The analizer returns:
id: 1
  select_type: SIMPLE
        table: BBDD_adsl
   partitions: p20120701,p20151001,p20151101,p20151201,p20160101,p20160201,p20160301,p20160401,p20160501,p20160601,p20160701,p20160801,p20160901,p20161001,p20161101,p20161201,p20170101,p20170201,p20170301,p20170401,p20170501,p20170601,p20170701,p20170801,p20170901,p20171001,p20171101,p20171201,p20180101,p20180201,p20180301,p20180401,p20180501,p20180601,p20180701,p20180801,p20180901,p20181001,p20181101,p20181201,pdefault
         type: ALL
possible_keys: MSISDN,MSISDN_2
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 1351342
        Extra: Using where
1 row in set (0.08 sec)

如您所见,它扫描了正确的分区,但行数太高 (1351342),就像我在进行全面扫描一样。

唯一有用的索引是 INDEX(fecha_carga),您没有。因此,它最多只能扫描一年的分区。这里有很多要点,请耐心等待...

  • OR 基本上不可能优化 MSISDN=622605810 or CIF_NIF=622605810UNION(见下文)可能会有所帮助,也许意义重大。

  • 对于与日期相关的 RANGE 分区,无论传入的值如何,都会扫描 "first" 分区。这是允许 NULL 或invalid DATEs 进来。(是的,它应该足够聪明,可以看到你的日期是有效的,但它不是。)你最好的防御是在开始时有一个虚拟分区——它应该是 'old enough' 没有数据。这至少会使它的扫描速度更快。

  • "future" 没有分区。相反,在需要时创建一个新的。我喜欢有一个名为 future 的分区。然后,就在时钟滴答作响之前,我 REORGANIZE PARTITION future INTO ... 创建下个月的分区 一个新的 future。因为 future 是空的,所以这个动作本质上是瞬时的。 my partition blog.

  • 中有更多详细信息
  • CIF_NIF varchar(255)CIF_NIF=622605810 不能很好地协同工作。执行必须解析每个值并将其转换为数字以进行比较。这使得任何索引都无法使用。要么将字段更改为 INT UNSIGNED(或某些数字数据类型),要么在数字周围加上引号(以便它是一个字符串比较)。

这是 OR --> UNION(经过一些简化):

SELECT  *
    FROM  adsl.BBDD
    WHERE  fecha_carga >= ...
      and  MSISDN=622605810
UNION  DISTINCT  -- or  ALL 
SELECT  *
    FROM  adsl.BBDD
    WHERE  fecha_carga >= ...
      and  CIF_NIF='622605810'

此外,将 MSISDN 上的两个索引替换为这些复合索引:

INDEX(CIF_NIF, fecha_carga), INDEX(MSISDN, fecha_carga) 

通过这些更改(重新制定 SELECT,加上更好的索引),无论有无 PARTITIONing,速度都会快得多。事实上,PARTITIONing 将提供零性能改进。

如果您是 "purging" 来自 DROP PARTITION 的旧数据,这是保留 PARTITIONing 的一个很好的理由。 (再次,请参阅我的博客。)