Subpartitioning — also known as composite
partitioning — is the further division of each
partition in a partitioned table. For example, consider the
following CREATE TABLE
statement:
CREATE TABLE ts (id INT, purchased DATE) PARTITION BY RANGE( YEAR(purchased) ) SUBPARTITION BY HASH( TO_DAYS(purchased) ) SUBPARTITIONS 2 ( PARTITION p0 VALUES LESS THAN (1990), PARTITION p1 VALUES LESS THAN (2000), PARTITION p2 VALUES LESS THAN MAXVALUE );
Table ts
has 3 RANGE
partitions. Each of these partitions —
p0
, p1
, and
p2
— is further divided into 2
subpartitions. In effect, the entire table is divided into
3 * 2 = 6
partitions. However, due to the
action of the PARTITION BY RANGE
clause, the
first 2 of these store only those records with a value less than
1990 in the purchased
column.
In MySQL 5.4, it is possible to subpartition tables
that are partitioned by RANGE
or
LIST
. Subpartitions may use either
HASH
or KEY
partitioning.
This is also known as composite
partitioning.
SUBPARTITION BY HASH
and
SUBPARTITION BY KEY
generally follow the
same syntax rules as PARTITION BY HASH
and
PARTITION BY KEY
, respectively. An
exception to this is that SUBPARTITION BY
KEY
(unlike PARTITION BY KEY
)
does not currently support a default column, so the column
used for this purpose must be specified, even if the table has
an explicit primary key. This is a known issue which we are
working to address; see
Issues with subpartitions, for
more information and an example.
It is also possible to define subpartitions explicitly using
SUBPARTITION
clauses to specify options for
individual subpartitions. For example, a more verbose fashion of
creating the same table ts
as shown in the
previous example would be:
CREATE TABLE ts (id INT, purchased DATE) PARTITION BY RANGE( YEAR(purchased) ) SUBPARTITION BY HASH( TO_DAYS(purchased) ) ( PARTITION p0 VALUES LESS THAN (1990) ( SUBPARTITION s0, SUBPARTITION s1 ), PARTITION p1 VALUES LESS THAN (2000) ( SUBPARTITION s2, SUBPARTITION s3 ), PARTITION p2 VALUES LESS THAN MAXVALUE ( SUBPARTITION s4, SUBPARTITION s5 ) );
Some syntactical items of note:
Each partition must have the same number of subpartitions.
If you explicitly define any subpartitions using
SUBPARTITION
on any partition of a
partitioned table, you must define them all. In other words,
the following statement will fail:
CREATE TABLE ts (id INT, purchased DATE) PARTITION BY RANGE( YEAR(purchased) ) SUBPARTITION BY HASH( TO_DAYS(purchased) ) ( PARTITION p0 VALUES LESS THAN (1990) ( SUBPARTITION s0, SUBPARTITION s1 ), PARTITION p1 VALUES LESS THAN (2000), PARTITION p2 VALUES LESS THAN MAXVALUE ( SUBPARTITION s2, SUBPARTITION s3 ) );
This statement would still fail even if it included a
SUBPARTITIONS 2
clause.
Each SUBPARTITION
clause must include (at
a minimum) a name for the subpartition. Otherwise, you may
set any desired option for the subpartition or allow it to
assume its default setting for that option.
Subpartition names must be unique across the entire table.
For example, the following CREATE
TABLE
statement is valid in MySQL
5.4:
CREATE TABLE ts (id INT, purchased DATE) PARTITION BY RANGE( YEAR(purchased) ) SUBPARTITION BY HASH( TO_DAYS(purchased) ) ( PARTITION p0 VALUES LESS THAN (1990) ( SUBPARTITION s0, SUBPARTITION s1 ), PARTITION p1 VALUES LESS THAN (2000) ( SUBPARTITION s2, SUBPARTITION s3 ), PARTITION p2 VALUES LESS THAN MAXVALUE ( SUBPARTITION s4, SUBPARTITION s5 ) );
Subpartitions can be used with especially large tables to
distribute data and indexes across many disks. Suppose that you
have 6 disks mounted as /disk0
,
/disk1
, /disk2
, and so
on. Now consider the following example:
CREATE TABLE ts (id INT, purchased DATE) PARTITION BY RANGE( YEAR(purchased) ) SUBPARTITION BY HASH( TO_DAYS(purchased) ) ( PARTITION p0 VALUES LESS THAN (1990) ( SUBPARTITION s0 DATA DIRECTORY = '/disk0/data' INDEX DIRECTORY = '/disk0/idx', SUBPARTITION s1 DATA DIRECTORY = '/disk1/data' INDEX DIRECTORY = '/disk1/idx' ), PARTITION p1 VALUES LESS THAN (2000) ( SUBPARTITION s2 DATA DIRECTORY = '/disk2/data' INDEX DIRECTORY = '/disk2/idx', SUBPARTITION s3 DATA DIRECTORY = '/disk3/data' INDEX DIRECTORY = '/disk3/idx' ), PARTITION p2 VALUES LESS THAN MAXVALUE ( SUBPARTITION s4 DATA DIRECTORY = '/disk4/data' INDEX DIRECTORY = '/disk4/idx', SUBPARTITION s5 DATA DIRECTORY = '/disk5/data' INDEX DIRECTORY = '/disk5/idx' ) );
In this case, a separate disk is used for the data and for the
indexes of each RANGE
. Many other variations
are possible; another example might be:
CREATE TABLE ts (id INT, purchased DATE) PARTITION BY RANGE(YEAR(purchased)) SUBPARTITION BY HASH( TO_DAYS(purchased) ) ( PARTITION p0 VALUES LESS THAN (1990) ( SUBPARTITION s0a DATA DIRECTORY = '/disk0' INDEX DIRECTORY = '/disk1', SUBPARTITION s0b DATA DIRECTORY = '/disk2' INDEX DIRECTORY = '/disk3' ), PARTITION p1 VALUES LESS THAN (2000) ( SUBPARTITION s1a DATA DIRECTORY = '/disk4/data' INDEX DIRECTORY = '/disk4/idx', SUBPARTITION s1b DATA DIRECTORY = '/disk5/data' INDEX DIRECTORY = '/disk5/idx' ), PARTITION p2 VALUES LESS THAN MAXVALUE ( SUBPARTITION s2a, SUBPARTITION s2b ) );
Here, the storage is as follows:
Rows with purchased
dates from before
1990 take up a vast amount of space, so are split up 4 ways,
with a separate disk dedicated to the data and to the
indexes for each of the two subpartitions
(s0a
and s0b
) making
up partition p0
. In other words:
The data for subpartition s0a
is
stored on /disk0
.
The indexes for subpartition s0a
are
stored on /disk1
.
The data for subpartition s0b
is
stored on /disk2
.
The indexes for subpartition s0b
are
stored on /disk3
.
Rows containing dates ranging from 1990 to 1999 (partition
p1
) do not require as much room as those
from before 1990. These are split between 2 disks
(/disk4
and
/disk5
) rather than 4 disks as with the
legacy records stored in p0
:
Data and indexes belonging to p1
's
first subpartition (s1a
) are stored
on /disk4
— the data in
/disk4/data
, and the indexes in
/disk4/idx
.
Data and indexes belonging to p1
's
second subpartition (s1b
) are stored
on /disk5
— the data in
/disk5/data
, and the indexes in
/disk5/idx
.
Rows reflecting dates from the year 2000 to the present
(partition p2
) do not take up as much
space as required by either of the two previous ranges.
Currently, it is sufficient to store all of these in the
default location.
In future, when the number of purchases for the decade
beginning with the year 2000 grows to a point where the
default location no longer provides sufficient space, the
corresponding rows can be moved using an ALTER
TABLE ... REORGANIZE PARTITION
statement. See
Section 17.3, “Partition Management”, for an
explanation of how this can be done.
The DATA DIRECTORY
and INDEX
DIRECTORY
options are disallowed when the
NO_DIR_IN_CREATE
server SQL
mode is in effect. This is true for partitions and
subpartitions.
User Comments
Here is an example of programatically building a sub partitioned table by year and month using PHP.
http://chrisjohnson.blogsite.org/programatically-building-mysql-partitioned-tables/
See link for why I like to name my partitions rather than use system generated names.
<?PHP
$sql="CREATE TABLE `data_collected` (
`id` int(11) NOT NULL DEFAULT '0',
`sensor_number` int(11) NOT NULL DEFAULT '0',
`data_date` date NOT NULL DEFAULT '0000-00-00',
`data_time` char(4) NOT NULL DEFAULT '',
`value` varchar(255) NOT NULL,
PRIMARY KEY (`id`,`sensor_number`,`data_date`,`data_time`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1
/*!50100 PARTITION BY RANGE (YEAR(data_date))
SUBPARTITION BY HASH (MONTH(data_date))
(";
for($i=1994;$i<2012;$i++)
{
$m=$i-1;
$sql.="\nPARTITION p$m VALUES LESS THAN ($i)
(SUBPARTITION s_dec_$m ENGINE = MyISAM,
SUBPARTITION s_jan_$m ENGINE = MyISAM,
SUBPARTITION s_feb_$m ENGINE = MyISAM,
SUBPARTITION s_mar_$m ENGINE = MyISAM,
SUBPARTITION s_apr_$m ENGINE = MyISAM,
SUBPARTITION s_may_$m ENGINE = MyISAM,
SUBPARTITION s_jun_$m ENGINE = MyISAM,
SUBPARTITION s_jul_$m ENGINE = MyISAM,
SUBPARTITION s_aug_$m ENGINE = MyISAM,
SUBPARTITION s_sep_$m ENGINE = MyISAM,
SUBPARTITION s_oct_$m ENGINE = MyISAM,
SUBPARTITION s_nov_$m ENGINE = MyISAM)";
if ($i!=2011)
$sql.=',';
else
$sql.=")*/";
}
echo $sql;
mysql_connect("localhost",'bla','bla');
mysql_select_db("weather");
mysql_query("drop table if exists data_collected");
mysql_query($sql) or die(mysql_error());
?>
Add your own comment.