MySQL更新查询性能低 - 我的索引错了吗?

时间:2018-05-17 13:02:46

标签: mysql

我有两个表 - 一个用于处理导入数据的临时表和现有的帐户表。我需要根据帐户表中帐号和组号的匹配来更新临时表中的用户ID。

CREATE TABLE `_temp` (
 `g_id` int(11) NOT NULL AUTO_INCREMENT,
 `g_group_norm` varchar(10) DEFAULT NULL,
 `g_uid1` varchar(25) DEFAULT NULL,
 `g_spid` int(11) DEFAULT NULL,
 `g_pid` int(11) DEFAULT NULL,
PRIMARY KEY (`g_id`),
 KEY `groupn` (`g_group_norm`) USING BTREE,
 KEY `guid` (`g_uid1`) USING BTREE,
 KEY `gspid` (`g_spid`) USING BTREE
 ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

 CREATE TABLE `accounts` (
`sa_actid` int(11) NOT NULL AUTO_INCREMENT,
`sa_grp` varchar(10) DEFAULT NULL,
`sa_account` varchar(25) DEFAULT NULL,
`sa_spid` int(11) DEFAULT NULL,
`sa_partnerid` int(11) DEFAULT NULL,
 PRIMARY KEY (`sa_actid`),
 KEY `spid` (`sa_spid`) USING BTREE,
 KEY `grp` (`sa_grp`) USING BTREE,
 KEY `act` (`sa_account`) USING BTREE,
 KEY `partnerid` (`sa_partnerid`) USING BTREE,
 ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;  

帐户表格超过500万行。我现在使用的_temp表数据可以是50,000到700,000行。

我用来设置g_spid = sa_spid的查询是:

UPDATE _temp T, accounts A SET
  T.g_spid = A.sa_spid
 WHERE T.g_uid1 = A.sa_account
   AND T.g_group_norm = A.sa_grp
   AND A.sa_partnerid = 118
   AND T.g_spid IS NULL;

帐户表有大约3M行,其中2.84M是合作伙伴ID" 118"。临时表更新工作缓慢,但在10-20k行上正常,但是当我有一组更大的数据要处理时(当前临时表大约是10万行)它似乎永远不会完成(它已经运行了现在15分钟。)

有更好的方法来执行此查询吗?我已经在g_uid1 = sa_account,g_group_norm = sa_grp等内部联接尝试了它,这似乎更慢。

2 个答案:

答案 0 :(得分:1)

你可以在_temp上使用复合索引(g_group_norm,g_uid1,g_spid)
和帐户上的复合(sa_partnerid,sa_account,sa_spid)

UPDATE _temp T
INNER JOIN accounts A  ON  T.g_uid1 = A.sa_account 
  AND T.g_group_norm = A.sa_grp 
    AND A.sa_partnerid = 118
SET T.g_spid = A.sa_spid
WHERE  T.g_spid IS NULL

答案 1 :(得分:1)

可以实现同等的结果:

 UPDATE _temp t
    SET t.g_spid =  ( SELECT MIN(a.sa_spid) 
                        FROM accounts a
                       WHERE a.sa_account   = t.g_uid1
                         AND a.sa_grp       = t.g_group_norm
                         AND a.sa_partnerid = 118
                    )
  WHERE t.g_spid       IS NULL
    AND t.g_uid1       IS NOT NULL
    AND t.g_group_norm IS NOT NULL

将为为外部查询返回的每个行执行相关子查询,因此对于性能,我们需要一个合适的索引,最好是覆盖索引。

使用相关子查询的WHERE子句中的相等条件,我们希望这三列在索引中排在第一位,最先选择最具选择性的列。 (帐户中有近95%的行的sa_partner_id值为118,这不是很有选择性,所以我们把它排在第三位。)

ON accounts (sa_account, sa_grp, sa_partner_id, sa_spid)

我们还包含sa_spid列,使其成为“覆盖”索引,以便可以完全从索引中满足子查询,而无需在基础表中查找页面。

(单例列上的索引可能对其他查询很有用,但它们不适合此特定查询。)

如果WHERE子句中的条件足够有选择性,我们也可以在_temp表上添加索引。如果我们需要查看_temp中超过10%或15%的行,则完整扫描可能会更快。

随着每一行的更新,维护g_spid列上的索引会产生开销。对于大型集合,有时删除索引的速度更快,执行更新并重新添加索引。

(我怀疑_temp表上的索引有更好的选择,但是如果不知道对表执行的其他SQL,就无法确定._temp表上的所有索引都看不到适用于此查询,除非外部查询的WHERE子句中的条件非常有选择性。)

对于_temp中的大量行,我们可能希望将操作分解为更小的集合。

使用相同的查询模式,但向外部查询添加另一个条件以将其分解为更小的集合。

作为示例(我不知道列的数据类型,值的范围或分布。正如该想法的说明,假设group_norm是DECIMAL值,范围从0.00000到0.99999均匀分布,将UPDATE分成十个“集”......

首次运行

  AND t.group_norm >= 0.0 AND t.group_norm < 0.1

第二次运行

  AND t.group_norm >= 0.1 AND t.group_norm < 0.2

第三次运行

  AND t.group_norm >= 0.2 AND t.group_norm < 0.3