4

我有一张非常大的桌子,里面有将近 3 亿条记录。由于选择查询对我来说太慢了,所以我想将其拆分为大约 800 个小表。

数据集如下所示:

XXXXXX column2 column3 column4 ...
XXXXXX column2 column3 column4 ...
XXXXXX column2 column3 column4 ...
YYYYYY column2 column3 column4 ...
YYYYYY column2 column3 column4 ...

我想根据第一列的值拆分表(例如,将记录XXXXXX拆分为表XXXXXX),最快的方法是什么?

注意:我已经为它添加了 10 个分区,但它并没有很好地加速它。

4

2 回答 2

12

在两种情况下,分区作为一种性能策略起作用:

  1. 该表的主要查询最终会执行表或索引扫描,并且位于具有足够资源和适当配置的系统上以执行高级别的并行性。因此,如果所有分区都在同一个物理驱动器上,那不会给你带来太多好处,你就像一开始一样受到 I/O 限制。但是,如果您使用的是 16 核系统,并且每个分区都位于物理上不同的磁盘上?分区可能会对系统性能产生惊人的改进。

  2. 分区规则使用在针对该表的最流行查询中经常使用的索引。如果您要通过该路线获得性能,则应根据通常用于过滤或约束结果集的索引值进行分区。最常见的候选者是交易日期,因为报告通常是按日历日期范围进行的。然后,查询优化器可以使用分区规则将操作限制为单个(较小的)分区,或者并行运行两个或多个分区扫描(受上述相同限制)。

我假设要拆分此表的主要原因是为了性能。但是800个分区?如果性能改进是您所追求的,那可能是错误的方法。企业数据库在高速缓存中保留尽可能多的顶级表索引以获得良好的性能。在一个五级 b-tree 中,对于一个适度使用的表,前三级很可能在第一次访问后总是保存在缓存中(这可能是 300M 行表的整数主键配置) . 通过将表拆分为 800 个部分,这意味着将有 800 个数据结构尝试保持缓存(除了表数据本身)。很有可能,如果您的访问权限或多或少由主键均匀分布,那么在一个分区上进行搜索最终会将其他分区 推出缓存,最终损害整体性能。

不过,如果您决定这样做,将表划分为 N 块的最简单方法是通过您想要针对主键的分区数的 MODULUS 对它进行分区(primary_key % 800在您的情况下为 )。较新版本的 MySQL 也支持散列分区,使得分区成任意数量的集合相当简单:

PARTITION BY HASH(some_column_value) PARTITIONS number_of_partitions

相反,如果您想将数据放入 800 个实际表中,则必须使用编辑器魔术,或使用脚本语言,并在 SQL 中执行:

CREATE TABLE table1 LIKE MasterTable
CREATE TABLE table2 LIKE MasterTable
CREATE TABLE table3 LIKE MasterTable
..
INSERT INTO table1 SELECT * FROM MasterTable WHERE id MOD 800 = 0
INSERT INTO table2 SELECT * FROM MasterTable WHERE id MOD 800 = 1
INSERT INTO table3 SELECT * FROM MasterTable WHERE id MOD 800 = 2

您可以使用动态 SQL 在您最喜欢的编程语言中循环执行此操作:这可能是最容易呈现的。

于 2013-07-28T05:20:36.120 回答
3

感谢每一位启发我这个存储过程的人!我很高兴与您分享:在此处输入图像描述

    DELIMITER $$

CREATE DEFINER=`root`@`localhost` PROCEDURE `split_tablebyrowscnt` (IN `tableName` VARCHAR(40), IN `step` INT)  BEGIN 
    SET @table := tableName; 
    SET @liminf := 0; 
    SET @limsup := step;

    SET @sql_2 = CONCAT('SELECT COUNT(*) INTO @rwcnt FROM ',@table,';'); 
    PREPARE stmt from @sql_2; 
    EXECUTE stmt; 
    DEALLOCATE PREPARE stmt;

    WHILE @liminf<@rwcnt DO

    SET @sql_1 = CONCAT('SELECT CAST(',@limsup,' as char(10)) INTO @limsup_str;'); 
    PREPARE stmt from @sql_1; 
    EXECUTE stmt; 
    DEALLOCATE PREPARE stmt; 

    SET @sql_loop =CONCAT('CREATE TABLE ',@table,'_',@limsup_str,' SELECT * FROM(SELECT @rownum:=@rownum+1 rownum,d.* FROM (',@table,' d, (SELECT @rownum:=0) r))t 
    WHERE ( rownum >?) AND (rownum <= ?);'); 
    PREPARE stmt from @sql_loop; 
    EXECUTE stmt USING @liminf,@limsup; 
    DEALLOCATE PREPARE stmt;  

    SET @sql_drop = CONCAT('ALTER TABLE ',@table,'_',@limsup_str,' DROP COLUMN rownum;'); 
    PREPARE stmt from @sql_drop; 
    EXECUTE stmt; 
    DEALLOCATE PREPARE stmt;

    SET @liminf = @liminf + step;
    SET @limsup = @limsup + step;
    END WHILE ;
    END$$

DELIMITER ;

要执行该过程: CALL split_tablebyrowscnt('myTable',100)

于 2016-01-10T19:50:23.200 回答