MYSQL 对 4000 万条记录表和 128GB 内存的专用服务器进行大量更新需要很长时间

Posted

技术标签:

【中文标题】MYSQL 对 4000 万条记录表和 128GB 内存的专用服务器进行大量更新需要很长时间【英文标题】:MYSQL lot's of update on 40 million record table wih 128GB ram dedicated server take long time 【发布时间】:2019-02-12 13:02:36 【问题描述】:

我们遇到了需要很长时间才能更新单个表的问题。该表包含约 3000 万行。

每天运行的作业会截断表并在该表中插入来自其他来源的新数据。

这是表格:

CREATE TABLE tempportfolio1 (
  SR_NO int(4) NOT NULL AUTO_INCREMENT,
  TR_DATE date DEFAULT NULL,
  TRAN_CODE decimal(18,0) DEFAULT NULL,
  TRAN_TYPE varchar(20) DEFAULT NULL,
  SCH_CODE bigint(8) DEFAULT NULL,
  Nature varchar(25) DEFAULT NULL,
  UNITS decimal(19,4) DEFAULT NULL,
  BAL_UNITS decimal(19,4) DEFAULT NULL,
  DIVD_RECD double DEFAULT '0',
  FOLIO_NO varchar(50) DEFAULT NULL,
  FLAG varchar(5) DEFAULT NULL,
  MBALANCE double DEFAULT NULL,
  PBALANCE double DEFAULT NULL,
  MTotalBalance double DEFAULT NULL,
  PL_NOTIONAL decimal(19,4) DEFAULT NULL,
  PL_BOOKED decimal(19,4) DEFAULT NULL,
  AGE int(4) DEFAULT NULL,
  RET_ABS decimal(19,4) DEFAULT NULL,
  RET_CAGR decimal(19,4) DEFAULT NULL,
  INDEX_AMT decimal(19,4) DEFAULT NULL,
  RET_INDEX_ABS decimal(19,4) DEFAULT NULL,
  Ret_Index_CAGR decimal(19,4) DEFAULT NULL,
  CURRENT_AMT decimal(19,4) DEFAULT NULL,
  GAIN_LOSS_LT decimal(19,4) DEFAULT NULL,
  GAIN_LOSS_ST decimal(19,4) DEFAULT NULL,
  UNITS_FOR_DIVID decimal(19,4) DEFAULT NULL,
  factor double DEFAULT NULL,
  LatestNav double DEFAULT '10',
  NavDate date DEFAULT NULL,
  IType int(4) DEFAULT NULL,
  Rate double DEFAULT NULL,
  CurrAmt double DEFAULT NULL,
  IndexVal double DEFAULT NULL,
  LatestIndexVal double DEFAULT NULL,
  Field int(4) DEFAULT NULL,
  Client_Code int(4) DEFAULT NULL,
  Branch_Code int(4) DEFAULT NULL,
  Rm_Code int(4) DEFAULT NULL,
  Group_Name varchar(100) DEFAULT NULL,
  Type1 varchar(20) DEFAULT NULL,
  Type2 varchar(20) DEFAULT NULL,
  IsOnline tinyint(3) unsigned DEFAULT NULL,
  SFactor double DEFAULT NULL,
  OSch_Code int(4) DEFAULT NULL,
  PRIMARY KEY (SR_NO),
  KEY SCH_Code (SCH_CODE),
  KEY OSch_Code (OSch_Code)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

注意:拥有此索引的原因是我们在 SP 中进行了许多选择和更新,这将减少表扫描。

   UPDATE TempPortFolio1
          INNER JOIN Clients
             ON Clients.ClientId = TempPortFolio1.Client_Code
   SET IType = InvCode;


      UPDATE TempPortFolio1
             INNER JOIN SchDate ON TempPortFolio1.Sch_Code = SchDate.Sch_Code
      SET LatestNav = NavRs, NavDate = LDate;


    UPDATE TempPortFolio1
    SET RATE = 0
    WHERE TRAN_TYPE = 'BONUS';


    UPDATE TempPortFolio1
    SET LatestNav = 10
    WHERE LatestNav = 0 OR LatestNav IS NULL;


    UPDATE TempPortFolio1
    SET NavDate = Tr_date
    WHERE NavDate < Tr_date AND Tran_Type <> 'Reinvestment';


    UPDATE TempPortFolio1
    SET Age = DATEDIFF(NAVDATE, TR_DATE),
        CurrAmt = (LatestNav * Units),
        PL_Notional = (UNITS * (LatestNav - Rate)),
        Divd_Recd = 0;




    UPDATE TempPortFolio1 TP INNER JOIN snature_new SM ON SM.CLASSCODE = TP.Type2
    SET GAIN_LOSS_ST = (CASE WHEN (Age < 365) THEN PL_Notional ELSE NULL END),
        GAIN_LOSS_LT = (CASE WHEN (Age >= 365) THEN PL_Notional ELSE NULL END)
    WHERE SM.Indexation = 0;


    UPDATE TempPortFolio1 TP INNER JOIN snature_new SM ON SM.CLASSCODE = TP.Type2
    SET GAIN_LOSS_ST =
           (CASE
               WHEN (TIMESTAMPDIFF(MONTH, TR_DATE, NAVDATE) < 36)
               THEN
                  PL_Notional
               ELSE
                  NULL
            END),
        GAIN_LOSS_LT =
           (CASE
               WHEN (TIMESTAMPDIFF(MONTH, TR_DATE, NAVDATE) >= 36)
               THEN
                  PL_Notional
               ELSE
                  NULL
            END)
    WHERE SM.Indexation = 1;


     UPDATE TempPortFolio1
   SET RET_INDEX_ABS = ((LatestIndexVal - IndexVal) / IndexVal) * 100;


   UPDATE TempPortFolio1
   SET Ret_Index_CAGR =
          CASE
             WHEN Age <= 365
             THEN
                ((CONVERT(RET_INDEX_ABS, decimal) / age) * 365)
             ELSE
                  (  POWER((((LatestIndexVal)) / (IndexVal)),
                           (365 / CONVERT(IFNULL(AGE, 1), decimal)))
                   - 1)
                * 100
          END
   WHERE     age <> 0
         AND LatestIndexVal <> 0
         AND IndexVal <> 0
         AND AGE IS NOT NULL;


   UPDATE TempPortFolio1
   SET ret_abs =
            (  ((((UNITS * LATESTNAV) + DIVD_RECD)) - (UNITS * RATE))
             / (UNITS * RATE))
          * 100
   WHERE UNITS <> 0 AND rate <> 0;

   UPDATE TempPortFolio1
   SET RET_CAGR =
          CASE
             WHEN Age <= 365
             THEN
                ((ret_abs / age) * 365)
             ELSE
                  (  POWER(
                        ((((UNITS * LATESTNAV) + DIVD_RECD)) / (UNITS * RATE)),
                        (365 / CONVERT(IFNULL(AGE, 1), DECIMAL)))
                   - 1)
                * 100
          END
   WHERE age <> 0 AND UNITS <> 0 AND rate <> 0 AND AGE IS NOT NULL;



   UPDATE TempPortFolio1
   SET Age = 0, LatestNav = 10
   WHERE Age IS NULL;

   UPDATE TempPortFolio1
   SET Factor = (UNITS * RATE * AGE);

   UPDATE TempPortFolio1
   SET SFactor = (UNITS * RATE * IndexVal * AGE);

它们之间有很多更新,但需要的时间更少。 原因只​​有两个索引,因为以上所有查询都更新了整个表(4000 万条记录)。所以我认为不需要索引。

每次更新大约需要 25 分钟。服务器有足够的内存来进行所有操作。 我尝试过临时表,但性能没有提高,因为整个表更新了,没有分区逻辑会帮助我这么认为。?

我在 Windows 10 上运行此查询。有什么方法可以提高 UPDATE 查询的速度?任何与配置相关的更改都会有帮助吗?

请帮忙

--编辑

这里是对多个连接表查询的解释这里是更新 2 的解释计划

1   SIMPLE  SchDate     index   PRIMARY,Sch_Code,IDX_1  Sch_Code    4       39064   100 Using index
1   SIMPLE  TempPortFolio1 ref SCH_Code    SCH_Code    9   SchDate.Sch_Code    1   100 Using index condition.

因为其他更新很简单,一张表,所以我认为不需要解释。

【问题讨论】:

提交是如何配置的。自动提交是否设置为开启? SCH_Code 和 OSch_Code 的基数如何? 更新查询的EXPLAIN 输出在哪里?其中哪些是长期运行的? SCH_Code 和 OSch_Code 的基数(不同)记录较少。但这有助于我进行其他需要更少时间的更新(不在此处) 【参考方案1】:

例如使用 LIMIT 1000 将更新拆分为块(限制适用于 UPDATE 查询)。

例如:

 UPDATE TempPortFolio1
    SET Age = DATEDIFF(NAVDATE, TR_DATE),
        CurrAmt = (LatestNav * Units),
        PL_Notional = (UNITS * (LatestNav - Rate)),
        Divd_Recd = 0 LIMIT 1000;

【讨论】:

我已经添加了示例。 我尝试过:临时表、分区、循环、内存表,但性能方面没有帮助。有什么方法可以处理 Windows 的并行性(Percona 工具支持,但仅在 Linux 中) 我们可以编写一个多线程实用程序来执行同时更新吗? percona.com/blog/2014/01/07/… 那行不通——它会一遍又一遍地更新相同的 1000!【参考方案2】:

使用PRIMARY KEY 浏览表格。一次检查 1000 行。详情讨论here

UPDATE 必须保存旧行以防崩溃。这就是您的UPDATE 如此缓慢的原因之一。而且,由于日志的大小,更新超过一定数量的行会变得更慢,因为需要花费额外的精力来保存它们。

不要使用OFFSETLIMIT——它们只会越来越慢。

您的某些UPDATEs 可能可以使用索引:

UPDATE TempPortFolio1
SET RATE = 0
WHERE TRAN_TYPE = 'BONUS';

可以使用INDEX(TRAN_TYPE)

但是那些没有WHERE 子句的人必须检查所有 40M 行。即使表可能可以放入 buffer_pool,但仍需要很长时间。

桌子可能比它需要的要胖。

decimal(19,4) 占用 9 个字节,允许的值最大为 999999999999999.9999;你真的有那么大的价值观吗? AGE int(4) -- 除非你在谈论人类的“年龄”,否则我建议你使用 1 字节的 TINYINT UNSIGNED 而不是 4 字节的 INT SIGNED。 (同时,(4) 没有任何意义。)哦,我看到 AGE 可能是“天”,所以也许 2 字节的 SMALLINT UNSIGNED(范围 0..64K)可能是合适的。 DOUBLE 占用 8 个字节,并且由于在二进制和十进制之间切换而存在额外舍入的风险。

通常进行大量更新是架构设计不佳的标志,因为这意味着“价值”不是保存在一个地方,而是保存在数百万个地方。冗余是数据库中的禁忌。

回到慢速UPDATE。有什么顾虑:

只是查询需要很长时间? (在经过时间之后,分块将花费更长的时间。) 它阻止了其他东西? (分块避免这种情况。) 您需要现在进行更改吗? (太糟糕了。) 您是否需要将所有相关行更改为相同的“即时”,即使该即时距您开始查询还有几分钟? (这就是你通过单一的、缓慢的 UPDATE 得到的结果。)

【讨论】:

【参考方案3】:

这个答案给想知道的人。

因此,每天都会截断/插入数据,并且每天都会运行作业。

我们制作了一个 SP,它根据行数(我们计算 count(*))删除并重新创建具有动态范围分区的表。

我们制作了第二个 SP,所有更新(大约 30 个)都是动态的,分区必须在执行时应用

than we Created script file which execute every day and do following task
      1   call 1'st SP
      2   create number of dynamic(replace event name and partition number ) event (after interval one minute) as number of partition  using file .
      3  each event will call Second SP with different Partition Paralleled .

这个过程每天都在重复,只用了(30 分钟)4000 万行来执行所有更新。

【讨论】:

以上是关于MYSQL 对 4000 万条记录表和 128GB 内存的专用服务器进行大量更新需要很长时间的主要内容,如果未能解决你的问题,请参考以下文章

向具有 4000 万条记录的表添加多列主键

Java和MySql连接问题

Mysql 性能调优问题

如何清除 ibdata1 文件以及它如何影响性能?

简单的 Spark 应用程序在 3 GB 数据上运行缓慢

在 BigQuery 之上设计 API