本来想着分区表在上一篇后就不续写了,最近又有同学咨询我分区表的新问题:无主键的分区表建议使用吗? 在此基础上的索引该如何设计? 基于这两个问题,我们来简单探讨下。
1. 无主键的分区表。
对于MySQL分区表来讲,有一个强制规定:分区键必须是主键或者是主键的交集!
但是对于没有主键的分区表,则没有这个规定, 可以选用任何一个键来作为分区键。比如下面表p1和表p2,都无显式定义主键。表p1的分区键为c1,表p2的分区键为c2。
<mysql:8.0.31:(ytt)>create table p1(c1 int,c2 int,c3 int) partition by hash(c1) partitions 10; Query OK, 0 rows affected (0.22 sec) <mysql:8.0.31:(ytt)>create table p2(c1 int,c2 int,c3 int) partition by hash(c2) partitions 10; Query OK, 0 rows affected (0.18 sec)
这种无主键的分区表使用场景不具备普遍性。 例如对于业务检索语句包含主键的需求,无主键的表无法简单实现。
下面要定位到表p1的第800001行(表p1有100W行记录,忽略造数据过程),那编写的SQL语句就比有主键的表多出很多步骤,而且性能很差。
我们这样写SQL: 时间0.27秒。
<mysql:8.0.31:(ytt)>select * from p1 where 1 limit 800000,1; +------+------+------+ | c1 | c2 | c3 | +------+------+------+ | 97 | 32 | 139 | +------+------+------+ 1 row in set (0.27 sec)
或者可以这样写SQL:时间1.85秒。
<mysql:8.0.31:(ytt)>select c1,c2,c3 from (select *,row_number() over() as sn from p1) T where sn=800001; +------+------+------+ | c1 | c2 | c3 | +------+------+------+ | 97 | 32 | 139 | +------+------+------+ 1 row in set (1.85 sec)
上面两条SQL都可以实现目标,但是效率都不高。第一条全表扫,严重依赖于磁盘的性能;第二条用子查询,依赖于MySQL自身对子查询的处理性能。
基于对比,我们来创建一张有主键的表p3,复制表p1的数据。
<mysql:8.0.31:(ytt)>create table p3(id int auto_increment,c1 int,c2 int,c3 int,primary key(id,c1)) partition by hash(c1) partitions 10; Query OK, 0 rows affected (0.25 sec) <mysql:8.0.31:(ytt)>insert p3(c1,c2,c3) table p1; Query OK, 1000000 rows affected (6.61 sec) Records: 1000000 Duplicates: 0 Warnings: 0
再次实现同样的检索:由于同样的检索基于主键,在MySQL里最优,速度最快。
<mysql:8.0.31:(ytt)>select c1,c2,c3 from p3 where id = 800001; +----+------+------+ | c1 | c2 | c3 | +----+------+------+ | 97 | 32 | 139 | +----+------+------+ 1 row in set (0.00 sec)
所以无主键的分区表和无主键的非分区表一样,仅限于数据量小并且检索不频繁的场景。
2. 无主键分区表的索引设计。
既然有这样的场景存在,那么这样的表该如何建立索引?
为了对比,我们复制表p1为p1_copy.
<mysql:8.0.31:(ytt)>create table p1_copy like p1; Query OK, 0 rows affected (0.19 sec) <mysql:8.0.31:(ytt)>insert p1_copy table p1; Query OK, 1000000 rows affected (5.46 sec) Records: 1000000 Duplicates: 0 Warnings: 0
与之前我们探讨的分区表类似,基于两点:
1. 查询包含分区键(我们这里是HASH分区,所以默认限制为等值检索)。
对于如下检索,因为是全表扫,无索引性能严重依赖磁盘性能: 时间0.04秒。
<mysql:8.0.31:(ytt)>select count(*) from p1 where c1 = 10 and c2 =10; +----------+ | count(*) | +----------+ | 115 | +----------+ 1 row in set (0.04 sec)
对于包含分区键的查询,建立索引没有必要包含分区键,因为分区键本身就已经可以精确定位到某一个分区。
<mysql:8.0.31:(ytt)>alter table p1_copy add key idx_c2(c2); Query OK, 0 rows affected (6.26 sec) Records: 0 Duplicates: 0 Warnings: 0
执行同样的查询:时间为0秒。
<mysql:8.0.31:(ytt)>select count(*) from p1_copy where c1 = 10 and c2 =10; +----------+ | count(*) | +----------+ | 115 | +----------+ 1 row in set (0.00 sec)
2. 查询不包含分区键。
对于查询不包含分区的检索语句,也没有必要包含分区键,因为会扫描所有分区,即使包含了分区键,也会扫描所有分区,对查询无益,对表的后期更新也无益。
所以最终的结论是对于无主键的分区表,在权衡各方利弊后,分区键都没有必要包含在索引定义里。
关于 MySQL 的技术内容,你们还有什么想知道的吗?赶紧留言告诉小编吧!