【问题标题】:MySQL: The quickest way to split a big table into small tablesMySQL:将大表拆分为小表的最快方法
【发布时间】:2013-07-28 03:01:39
【问题描述】:

我有一个很大的表,里面有将近 3 亿条记录。由于select查询对我来说太慢了,我想把它分成大约800个小表。

数据集如下所示:

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

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

注意:我已经给它加了10个分区,但是速度不是很好。

【问题讨论】:

  • 听起来很适合分区。
  • 看起来您正试图在此处进行分片。该线程对此主题进行了一些很好的讨论:stackoverflow.com/questions/5541421/mysql-sharding-approaches
  • @Jack,我已经为它添加了分区,但它并不能很好地加速它。
  • 分区是要走的路。分裂是一场噩梦。你试过ANALYZE TABLE MY_TABLE吗?也许优化器需要更好的分区信息。
  • 您找到解决此问题的方法了吗?我现在面临同样的问题。谢谢!

标签: mysql


【解决方案1】:

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

  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 在您最喜欢的编程语言中循环执行此操作:这可能是最容易呈现的。

【讨论】:

    【解决方案2】:

    感谢为这个存储过程启发我的每一个人! 很高兴与您分享:

        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)

    【讨论】:

    • 您对性能提升的哪些方面感到满意?
    • 我的目的不是提高性能,而只是为了在另一个 CMS 中启用导入,该 CMS 的导入工具限制为 500 条记录。
    猜你喜欢
    • 2017-07-18
    • 1970-01-01
    • 2016-03-29
    • 1970-01-01
    • 2012-08-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多