Использование разделов MySQL для разбиения таблицы по дням месяцам и годам

Приходится иметь дело с таблицами, которые содержат редко (или никогда)
обновляемые данные, такие как логи. Некоторые таблицы чистятся, некоторые
хранят записи "вечно". Чтобы уменьшить нагрузку на диск и ФС, придумали такую
вещь как partitioning  (Cекционирование).

Часто необходимо резать таблицу на partition по году по месяцу или по дням
месяца/недели. Что-то подсказывает что резать придется по полю типа timestamp.


Сделаем табличку

   CREATE TABLE `foo` (
     `id` int(11) NOT NULL AUTO_INCREMENT,
     `date_added` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
     `name` varchar(30) DEFAULT NULL,
     `email` varchar(30) DEFAULT NULL,
     PRIMARY KEY (`id`)
   );

и попробуем порезать ее по годам

   ALTER TABLE foo PARTITION BY RANGE (YEAR(date_added))
   (
    PARTITION p2011 VALUES LESS THAN (2012) ,
    PARTITION p2012 VALUES LESS THAN (2013) ,
    PARTITION p2013 VALUES LESS THAN (2014)
   );

получаем:

    ERROR 1486 (HY000): Constant, random or timezone-dependent   expressions in (sub)partitioning function are not allowed

объяснения этому вот какое: "TIMESTAMP is internally converted to the local sessions timezone."

ладно:

   SELECT UNIX_TIMESTAMP('2012-01-01 00:00:00');
   +---------------------------------------+
   | UNIX_TIMESTAMP('2012-01-01 00:00:00') |
   +---------------------------------------+
   |                            1325361600 |
   +---------------------------------------+

   SELECT UNIX_TIMESTAMP('2013-01-01 00:00:00'); = 1356984000
   SELECT UNIX_TIMESTAMP('2014-01-01 00:00:00'); = 1388520000

теперь:

    ALTER TABLE foo PARTITION BY RANGE (UNIX_TIMESTAMP(date_added))
   (
    PARTITION p2011 VALUES LESS THAN (1325361600) ,
    PARTITION p2012 VALUES LESS THAN (1356984000) ,
    PARTITION p2013 VALUES LESS THAN (1388520000) ,
    PARTITION pMAXVALUE VALUES LESS THAN (MAXVALUE)
   );
   
вот, теперь получаем:

   ERROR 1503 (HY000): A PRIMARY KEY must include all columns in the table's partitioning function

это лечится:

   ALTER table foo DROP PRIMARY KEY, add PRIMARY KEY (`id`,`date_added`);

и еще раз:

   ALTER TABLE foo PARTITION BY RANGE (UNIX_TIMESTAMP(date_added))
   (
    PARTITION p2011 VALUES LESS THAN (1325361600) ,
    PARTITION p2012 VALUES LESS THAN (1356984000) ,
    PARTITION p2013 VALUES LESS THAN (1388520000) ,
    PARTITION pMAXVALUE VALUES LESS THAN (MAXVALUE)
   );

все ок.

получаем:

   CREATE TABLE `foo` (
     `id` int(11) NOT NULL AUTO_INCREMENT,
     `date_added` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
     `name` varchar(30) DEFAULT NULL,
     `email` varchar(30) DEFAULT NULL,
     PRIMARY KEY (`id`,`date_added`)
   ) ENGINE=InnoDB
   
   PARTITION BY RANGE (UNIX_TIMESTAMP(date_added))
   (PARTITION p2011 VALUES LESS THAN (1325361600) ENGINE = InnoDB,
    PARTITION p2012 VALUES LESS THAN (1356984000) ENGINE = InnoDB,
    PARTITION p2013 VALUES LESS THAN (1388520000) ENGINE = InnoDB,
    PARTITION pMAXVALUE VALUES LESS THAN MAXVALUE ENGINE = InnoDB);


Отлично!

Такой тип "нарезки" подходит если надо архивные данные разложить по файлам "за год" или по месяцам.

Но как быть если надо, например, таблицу с логами разложить по дням месяца, то
есть в таблицу что-то пишется что хранится месяц или два потом трется.
То есть как быть если мы хотим порезать так

    PARTITION BY RANGE (MONTH(date))

или так

    PARTITION BY RANGE (DAY(date_add))

поле типа timestamp не подходит.

Гугление говорит что надо использовать datetime.

ок, создадим таблицу

   CREATE TABLE `foo` (
     `id` int(11) NOT NULL AUTO_INCREMENT,
     `date_added` datetime  DEFAULT NULL,
     `name` varchar(30) DEFAULT NULL,
     `email` varchar(30) DEFAULT NULL,
     PRIMARY KEY (`id`,`date_added`)
   ) ENGINE=InnoDB;

Обратите внимание:

   `date_added` datetime  DEFAULT NULL,

дело в том что, CURRENT_TIMESTAMP в качестве значения по умолчанию для поля
типа datetime не катит, NOW() как значение по умолчанию указывать нельзя т.к.  функция.

А  надо чтоб date_added выставлялось автоматом.

выхода 2

1. либо во всех запросах в INSERT добавлять NOW().
2. если первое невозможно, то вешать триггер который при каждом добавлении будет date_added=NOW();

что-то вроде

   DELIMITER $$

   USE `test_db`$$

   CREATE
       /*!50017 DEFINER = 'trigger'@'%' */
       TRIGGER `foo_add` BEFORE INSERT ON `foo`
       FOR EACH ROW BEGIN

         SET NEW.date_added = IFNULL(NEW.date_added, NOW());

       END;
   $$

Теперь у нас таблица с нужными типами, ключами и триггером.

и мы с легкостью можем разрезать таблицу по месяцам:

    ALTER TABLE foo PARTITION BY RANGE (MONTH(date_added))
   (
   PARTITION p01 VALUES LESS THAN (02) ,
   PARTITION p02 VALUES LESS THAN (03) ,
   PARTITION p03 VALUES LESS THAN (04) ,
   PARTITION p04 VALUES LESS THAN (05) ,
   PARTITION p05 VALUES LESS THAN (06) ,
   PARTITION p06 VALUES LESS THAN (07) ,
   PARTITION p07 VALUES LESS THAN (08) ,
   PARTITION p08 VALUES LESS THAN (09) ,
   PARTITION p09 VALUES LESS THAN (10) ,
   PARTITION p10 VALUES LESS THAN (11) ,
   PARTITION p11 VALUES LESS THAN (12) ,
   PARTITION p12 VALUES LESS THAN (13) ,
   PARTITION pmaxval VALUES LESS THAN MAXVALUE );

или даже по дням недели:

    ALTER TABLE foo PARTITION BY RANGE (DAYOFWEEK(date_added))
   (
   PARTITION p01 VALUES LESS THAN (2) ,
   PARTITION p02 VALUES LESS THAN (3) ,
   PARTITION p03 VALUES LESS THAN (4) ,
   PARTITION p04 VALUES LESS THAN (5) ,
   PARTITION p05 VALUES LESS THAN (6) ,
   PARTITION p06 VALUES LESS THAN (7) ,
   PARTITION p07 VALUES LESS THAN (8) ,
   PARTITION pmaxval VALUES LESS THAN MAXVALUE );

или даже 2 дня на partition:

   ALTER TABLE foo PARTITION BY LIST (DAY(date_added))
   (
   PARTITION p00 VALUES IN  (0,1) ,
   PARTITION p02 VALUES IN  (2,3) ,
   PARTITION p04 VALUES IN  (4,5) ,
   PARTITION p06 VALUES IN  (6,7) ,
   PARTITION p08 VALUES IN  (8,9) ,
   PARTITION p10 VALUES IN  (10,11),
   PARTITION p12 VALUES IN  (12,13),
   PARTITION p14 VALUES IN  (14,15),
   PARTITION p16 VALUES IN  (16,17),
   PARTITION p18 VALUES IN  (18,19),
   PARTITION p20 VALUES IN  (20,21),
   PARTITION p22 VALUES IN  (22,23),
   PARTITION p24 VALUES IN  (24,25),
   PARTITION p26 VALUES IN  (26,27),
   PARTITION p28 VALUES IN  (28,29),
   PARTITION p30 VALUES IN  (30,31)
   );

В общем теперь все в ваших руках.

Источник
P.S. Подразумевается что выставлена опция innodb_file_per_table
MySQL Doc