【问题标题】:Improving performance of updating large table with join提高使用连接更新大表的性能
【发布时间】:2015-09-27 11:23:17
【问题描述】:

目前我有一个架构如下的表:

 mData | CREATE TABLE `mData` (
   `m1` mediumint(8) unsigned DEFAULT NULL,
   `m2` smallint(5) unsigned DEFAULT NULL,
   `m3` bigint(20) DEFAULT NULL,
   `m4` tinyint(4) DEFAULT NULL,
   `m5` date DEFAULT NULL,
   KEY `m_m1` (`m1`) USING HASH,
   KEY `m_date` (`m5`),
   KEY `m_m2` (`m2`),
   KEY `m_combined` (`m1`,`m2`,`m5`),
   KEY `m1_tradeday` (`m1`,`m5`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
 /*!50100 PARTITION BY RANGE ( YEAR(m5))
 SUBPARTITION BY HASH (MONTH(m5))
 (PARTITION p2013 VALUES LESS THAN (2014)
  (SUBPARTITION dec_2013 ENGINE = InnoDB,
   SUBPARTITION jan_2013 ENGINE = InnoDB,
   SUBPARTITION feb_2013 ENGINE = InnoDB,
   SUBPARTITION mar_2013 ENGINE = InnoDB,
   SUBPARTITION apr_2013 ENGINE = InnoDB,
   SUBPARTITION may_2013 ENGINE = InnoDB,
   SUBPARTITION jun_2013 ENGINE = InnoDB,
   SUBPARTITION jul_2013 ENGINE = InnoDB,
   SUBPARTITION aug_2013 ENGINE = InnoDB,
   SUBPARTITION sep_2013 ENGINE = InnoDB,
   SUBPARTITION oct_2013 ENGINE = InnoDB,
  SUBPARTITION nov_2013 ENGINE = InnoDB),
  PARTITION p2014 VALUES LESS THAN (2015)
  (SUBPARTITION dec_2014 ENGINE = InnoDB,
   SUBPARTITION jan_2014 ENGINE = InnoDB,
   SUBPARTITION feb_2014 ENGINE = InnoDB,
   SUBPARTITION mar_2014 ENGINE = InnoDB,
   SUBPARTITION apr_2014 ENGINE = InnoDB,
   SUBPARTITION may_2014 ENGINE = InnoDB,
   SUBPARTITION jun_2014 ENGINE = InnoDB,
   SUBPARTITION jul_2014 ENGINE = InnoDB,
   SUBPARTITION aug_2014 ENGINE = InnoDB,
   SUBPARTITION sep_2014 ENGINE = InnoDB,
   SUBPARTITION oct_2014 ENGINE = InnoDB,
   SUBPARTITION nov_2014 ENGINE = InnoDB),
  PARTITION p2015 VALUES LESS THAN (2016)
  (SUBPARTITION dec_2015 ENGINE = InnoDB,
   SUBPARTITION jan_2015 ENGINE = InnoDB,
   SUBPARTITION feb_2015 ENGINE = InnoDB,
   SUBPARTITION mar_2015 ENGINE = InnoDB,
   SUBPARTITION apr_2015 ENGINE = InnoDB,
   SUBPARTITION may_2015 ENGINE = InnoDB,
   SUBPARTITION jun_2015 ENGINE = InnoDB,
   SUBPARTITION jul_2015 ENGINE = InnoDB,
   SUBPARTITION aug_2015 ENGINE = InnoDB,
   SUBPARTITION sep_2015 ENGINE = InnoDB,
   SUBPARTITION oct_2015 ENGINE = InnoDB,
   SUBPARTITION nov_2015 ENGINE = InnoDB),
  PARTITION p2016 VALUES LESS THAN (2017)
  (SUBPARTITION dec_2016 ENGINE = InnoDB,
   SUBPARTITION jan_2016 ENGINE = InnoDB,
   SUBPARTITION feb_2016 ENGINE = InnoDB,
   SUBPARTITION mar_2016 ENGINE = InnoDB,
   SUBPARTITION apr_2016 ENGINE = InnoDB,
   SUBPARTITION may_2016 ENGINE = InnoDB,
   SUBPARTITION jun_2016 ENGINE = InnoDB,
   SUBPARTITION jul_2016 ENGINE = InnoDB,
   SUBPARTITION aug_2016 ENGINE = InnoDB,
   SUBPARTITION sep_2016 ENGINE = InnoDB,
   SUBPARTITION oct_2016 ENGINE = InnoDB,
   SUBPARTITION nov_2016 ENGINE = InnoDB),
  PARTITION pmax VALUES LESS THAN MAXVALUE
  (SUBPARTITION dec_max ENGINE = InnoDB,
   SUBPARTITION jan_max ENGINE = InnoDB,
   SUBPARTITION feb_max ENGINE = InnoDB,
   SUBPARTITION mar_max ENGINE = InnoDB,
   SUBPARTITION apr_max ENGINE = InnoDB,
   SUBPARTITION may_max ENGINE = InnoDB,
   SUBPARTITION jun_max ENGINE = InnoDB,
   SUBPARTITION jul_max ENGINE = InnoDB,
   SUBPARTITION aug_max ENGINE = InnoDB,
   SUBPARTITION sep_max ENGINE = InnoDB,
   SUBPARTITION oct_max ENGINE = InnoDB,
   SUBPARTITION nov_max ENGINE = InnoDB)) */ |

m1、m2 和 m5 在此表中设置为索引,唯一/主要不适用于我的情况。

随着数据越来越大(每天 100,000 个新行),更新命令变得非常缓慢。

我想知道是否有任何方法可以改进以下语句。

update mData as a join (select * from mData
                        where m1 = 326 and m5 = '2015-   07-06' ) as b
            on  a.m5 > b.m5 and a.m1 = b.m1
            and a.m2 = b.m2 and a.m3 = b.m3
    set a.m4 = 0;

我很确定,在 select 语句中,如果我将 mData as a 替换为 (select * from mData where m1 = 326),执行时间可以大大减少(从 5 秒到不到 1 秒)。

但是,在UPDATE 语句中不能这样做。

有没有办法解决这个问题,加快更新速度?

附:该表已按月(m5)和年(m5)分区

这里是我的join查询的EXPLAIN分区,很乱,希望你不要介意。添加 ' 和 a.m5 > '2015-07-06' 确实提高了性能,查询时间从 0.68 秒下降到 0.2 秒。

explain partitions (select * from (select * from mData where m1 = 326) as a join (select * from mData where m1 = 326 and m5= '2015-07-06') as b on  a.m5 > b.m5 and a.m1 = b.m1 and a.m2 = b.m2 and a.m3 = b.m3 and a.m5 > '2015-07-06');

|编号 |选择类型 |表|分区|类型 |可能的键 |关键 | key_len |参考 |行 |额外 | +----+--------------+------------+------ ------------------------------ -------------------- -------------------------------------------------- ---------- ---------------------------------------- ---------------------------------------- ---------- -------------------------------------------------- -------------------- ------------------ ---------------------- ---------------------------------------- ---------- -------------------------------------------------- -------- ------------------------------------------ -------------------------- ------------ -------------------------------------------------- -------------------------------------------------- ------------------------------------------------ -------------------------------------------------- ------------------------------ -------------------- -------------------------------------------------- ---------- ------------------+------+-- ---------------------------------------- --------+- -------------+---------+------+------+------------ -------------------- + | 1 |初级 | |空值 |全部 |空 |空 |空 |空 | 358 | | | 1 |初级 | |空值 |全部 |空 |空 |空 |空 | 1073 |使用哪里;使用连接缓冲区 | | 3 |派生 |数据 | p2015_jul_2015|参考 | m_m1,m_m5,m_combined,m1_m5 | m1_m5 | 8 | |第357章使用位置 | | 2 |派生 |数据 | p2013_dec_2013,p2013_jan_2013,p2013_feb_2013,P 2013_mar_2013,p2013_apr_2013,p2013_may_2013,p2013_jun_2013,p2013_jul_2013,p2013_ aug_2013,p2013_sep_2013,p2013_oct_2013,p2013_nov_2013,p2014_dec_2014,p2014_jan_2 014,p2014_feb_2014,p2014_mar_2014,p2014_apr_2014,p2014_may_2014,p2014_jun_2014,P 2014_jul_2014,p2014_aug_2014,p2014_sep_2014,p2014_oct_2014, p2014_nov_2014,p2015_ dec_2015,p2015_jan_2015,p2015_feb_2015,p2015_mar_2015,p2015_apr_2015,p2015_may_2 015,p2015_jun_2015,p2015_jul_2015,p2015_aug_2015,p2015_sep_2015,p2015_oct_2015,P 2015_nov_2015,p2016_dec_2016,p2016_jan_2016,p2016_feb_2016,p2016_mar_2016,p2016_ apr_2016,p2016_may_2016,p2016_jun_2016,p2016_jul_2016,p2016_aug_2016,p2016_sep_2 016,p2016_oct_2016,p2016_nov_2016,pmax_dec_max,pmax_jan_max,pmax_feb_max,pmax_mar_max,pmax_apr_max,pmax_may_max,pmax_jun_max,pmax_jul_max,pmax_aug_max,pmax_sep_max,pmax_oct_max,pmax_nov_max |参考 | m_m1,m_combined,m1_m5 | m_m1 | 4 | | 1074 |使用位置 |

以下是“Rick James”提出的查询说明

EXPLAIN PARTITIONS select * from ccass_data where sid = 326 and trade_day = '2015-07-06';

| id | select_type | table      | partitions     | type | possible_keys                                    | key          | key_len | ref         | rows | Extra       |
 +----+-------------+------------+----------------+------+--------------------------------------------------+--------------+---------+-------------+------+-------------+
 |  1 | SIMPLE      | mData     | p2015_jul_2015 | ref  | m_m1,m_m5,m_combined,m1_m5               | m1_m5 | 8    | const,const |  357    | Using where        |

【问题讨论】:

  • 为什么不能在更新语句中添加where m1 = 326?另外,m1,m2,m3 上是否有复合索引?
  • 因为这并没有改善。它是一个完整的表和它的子查询之间的连接,它占据了大部分的执行时间。添加 where 没有区别。
  • 对于复合索引,是不是会加快很多?非常感谢您的建议。
  • 如果您在m1,m2,m5 上有一个复合索引,则连接目前正在使用它的前两个部分。如果所有 3 个都有索引,它应该会更快,但我无法预测会快多少。
  • 我尝试了m1、m2和m5的复合索引,还是一样

标签: mysql sql performance sql-update large-data


【解决方案1】:

对于初学者,请添加INDEX(m1, m5)。看到SHOW CREATE TABLE mData;后,我可能还有其他建议。

编辑

添加AND a.m5 > '2015-07-06' 可能会启动分区修剪。我没有任何UPDATESUBPARTITION 的经验来预测。

InnoDB 必须有一个PRIMARY KEY(m1, m2, m3, m5) 会作为 PK 工作吗?

USING HASH 被忽略,因为 InnoDB 没有实现它。无论如何,它将是一个 BTree,几乎一样好。

KEY `m_m1` (`m1`)

是多余的,可以删除,因为有另一个(实际上是两个)索引以它开始

你不能用JOIN 代替子查询吗? (这样可以避免使用 tmp 表。)

【讨论】:

  • 嗨,詹姆斯,我之前添加了索引(m1,m5)。我已经用 show create table 更新了帖子,请检查,非常感谢。
  • 你从EXPLAIN PARTITIONS select * from mData where m1 = 326 and m5 = '2015- 07-06';得到多少个分区?
  • 1.冗余索引是否会减慢查询速度,实际上,添加索引的最佳规则应该是什么? 2. m1, m2, m5 一起可以形成一个主键,但是如果我添加另一个主键,它会减慢表的速度,例如表的id?因为我的表 3 每天大约有 100,000 个。我已经尝试过,一个大表之间的连接非常慢我已经用你建议测试的查询更新了我的帖子
  • 冗余索引会减慢 INSERT 的速度。它们浪费磁盘空间。以一种迟钝的方式,它们可以减慢 SELECT 的速度。
  • 一张表中不能有多个 PRIMARY KEY。如果您不提供,InnoDB 会制造一个隐藏的。添加一个 PK 将替换那个虚构的。
【解决方案2】:

首先,我将使用 m5 的固定值来限制要考虑的分区。也许您还应该在年(m5)和月(m5)上添加一个虚拟条件。 然后我会为子查询创建一个临时表,并在 m2 和 m3 上创建一个索引。然后我会使用 m1 和 m5 的固定值。 但是查询执行了多少次? 5秒并不是一个糟糕的结果。

【讨论】:

  • 你的意思是每条记录的主ID之类的吗?因为当我在两个子查询上使用 join 时,只需要不到 1 秒,我觉得它可以更快但更新语句只允许一个子查询
  • 我会尝试用存储过程替换更新查询。在存储过程中,我会: 1. 使用内部查询的结果创建一个临时表 2. 在 m2 和 m3 字段的临时表上创建一个索引 3. 更新与临时表连接的表有意义吗?跨度>
  • 好像没有。问题在于使用全表连接速度很慢,该表需要很长时间才能完成任何连接操作,除非它被修剪掉
  • 可以发布查询计划吗?听起来它没有正确使用分区。
  • 你的意思是在 MySQL 中使用 EXPLAIN 吗?请原谅我对专业术语的无知。我不是 SQL 专业人士。 :P
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-11-05
  • 1970-01-01
  • 2012-06-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多