更新mysql大table挂得太及时

Update mysql big table hang too time

更新时的性能问题 MySql MyISAM big table 根据相同的索引对列进行升序排列 table

我的问题是服务器只有 4 GB 内存。
我必须像这样进行更新查询:previous asked question
我的是这样的:

set @orderid = 0;  
update images im
    set im.orderid = (select @orderid := @orderid + 1) 
    ORDER BY im.hotel_id, im.idImageType;

im.hotel_id, im.idImageType 我有一个升序索引。
im.orderid 我也有一个升序索引。

table 有 2100 万条记录 并且是一个 MyIsam table。

table是这样的:

CREATE TABLE `images` (
`photo_id` int(11) NOT NULL,
`idImageType` int(11) NOT NULL,
`hotel_id` int(11) NOT NULL,
`room_id` int(11) DEFAULT NULL,
`url_original` varchar(150) COLLATE utf8_unicode_ci NOT NULL,
`url_max300` varchar(150) COLLATE utf8_unicode_ci NOT NULL,
`url_square60` varchar(150) COLLATE utf8_unicode_ci NOT NULL,
`archive` int(11) NOT NULL DEFAULT '0',
`orderid` int(11) NOT NULL DEFAULT '0',
PRIMARY KEY (`photo_id`),
KEY `idImageType` (`idImageType`),
KEY `hotel_id` (`hotel_id`),
KEY `hotel_id_idImageType` (`hotel_id`,`idImageType`),
KEY `archive` (`archive`),
KEY `room_id` (`room_id`),
KEY `orderid` (`orderid`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

性能问题:挂了几分钟!
服务器磁盘也很忙。

我的问题是:有没有更好的方式达到同样的效果?
我是否需要对 table 进行分区或其他方式来提高性能?
我无法修改服务器硬件,但可以调整 MySql 应用程序数据库服务器设置。

此致

你可以使用其中的一些:

  1. 更新引擎到 InnoDB,它只阻塞一行,而不是所有更新时的 table。

  2. 使用 photo_id 和良好的 orderid 创建 #temp table,然后从该温度更新您的 table:

    update images im, temp tp
    set im.orderid = tp.orderid
    where im.photo_id = tp.photo_id
    

这将是最快的方式,当你填充你的 tmp table - 你在主要 table 上没有块。

  1. 您可以在批量更新前删除索引。在您完成所有单次更新后,您需要重建索引并且需要很长时间。
KEY `hotel_id`             (`hotel_id`),
KEY `hotel_id_idImageType` (`hotel_id`,`idImageType`),

放弃前者;后者负责满足任何需要。 (这不会加快原始查询的速度。)

"The problem is the performance: hang for several minutes!"有什么问题?

  • 其他查询被阻塞几分钟? (InnoDB 应该有所帮助。)
  • 你运行这个更新频繁而且很烦人? (为什么在世界上??)
  • 还有别的吗?

这个索引在执行更新时开销很大:

KEY `orderid` (`orderid`)

删除它并重新创建它。 (不要打扰删除其余部分。)使用 InnoDB 的另一个原因是这些操作可以完成(在 5.6 中)而无需复制 table 。 (21M 行 == 很长时间,如果它必须复制 table!)

除了 photo_id 之外,为什么还要构建第二个唯一索引 (orderid),它已经是唯一索引了?我问这个是因为可能有另一种方法可以解决真正的问题,不涉及这个耗时的Update。

我有两个更具体的建议,但我想先在这里回答你。

编辑分页,按hotel_id, idImageType, photo_id:

排序

可以按该三元组顺序读取记录。甚至 "paginate" 通过他们。

如果您在 ($hid, $type, $pid) 之后 "left off",这里将是 'next' 20 条记录:

WHERE   hotel_id >= $hid
  AND ( hotel_id >  $hid
     OR       idImageType >= $type
        AND ( idImageType >  $type
           OR      photo_id > $pid
            )
      )
ORDER BY hotel_id, idImageType, photo_id
LIMIT 20

并且有

INDEX(hotel_id, idImageType, photo_id)

这避免了 orderid 及其耗时的更新。

一次分页一个 hotel_id 会更简单。那行得通吗?

编辑 2 -- 消除停机时间

由于您要定期重新加载整个 table,因此在重新加载时执行此操作:

  1. CREATE TABLE New 与建议索引更改。
  2. 将数据加载到 New。 (一定要避免 51 分钟的超时;我不知道是什么原因造成的。)
  3. RENAME TABLE images TO old, New TO images;
  4. DROP TABLE old;

这将避免阻塞 table 加载和架构更改。原子步骤 #3 将有一个非常短的块。

计划在每次重新加载数据时执行此过程。

另一个好处 -- 在步骤 #2 之后,您可以测试新数据以查看它是否正常。

感谢每一个人。你的回答对我帮助很大。我想现在我找到了更好的解决方案。

这个问题涉及两个关键问题:

  • 高效分页大型 table
  • 更新大table。

为了在大型 table 上进行高效分页,我找到了一个解决方案,方法是对 table 进行先前的更新,但是这样做我在更新所需的 51 分钟时间内遇到了问题以及随之而来的 java 基础架构超时(spring-批处理步骤)。

现在在你的帮助下,我找到了两种在大 table 上分页的解决方案,以及一种更新大 table.
的解决方案 为了达到这种性能,服务器需要内存。我在使用 32 GB 内存的开发服务器上尝试此解决方案。

常见的解决步骤

要按照我需要的方式对字段图普拉进行分页,我已经制作了一个索引:

KEY `hotel_id_idImageType` (`hotel_id`,`idImageType`) 

为了实现新的解决方案,我们必须通过将主键部分添加到索引尾部来更改此索引 KEY hotel_id_idImageType (hotel_id,idImageType, primary key fields):

drop index hotel_id_idImageType on images;
create index hotelTypePhoto on images (hotel_id, idImageType, photo_id);  

这是为了避免触摸 table 并且只使用索引文件...

假设我们想要第19000000条记录之后的10条记录。

这个答案里的小数点是这个,

解决方案 1

这个解决方案是非常实用的,不需要额外的字段orderid,而且在分页之前你不需要做任何更新:

select * from images im inner join 
  (select photo_id from images 
  order by hotel_id, idImageType, photo_id 
  limit 19000000,10) k 
on im.photo_id = k.photo_id;  

要在我的 2100 万 table 条记录上生成 table k 只需要 1.5 秒因为它仅使用索引 hotelTypePhoto 中的三个字段,所以无法访问 table 文件并且只能处理索引文件。

订单与原来要求的一样 (hotel_id, idImageType) 因为包含在 (hotel_id, idImageType, photo_id): 相同的子集...

连接不需要时间,所以每次第一次在同一页面上执行分页只需要 1.5 秒,如果你必须在 3 个月内批量执行它,这是一个很好的时间。

在使用 4 GB 内存的生产服务器上 相同的查询需要 3.5 秒。

分区 table 无助于提高性能。

如果服务器将其放入缓存中,时间会减少,或者如果您执行 jdbc 参数声明,时间也会减少(我想)。

如果你必须经常使用它,它的优点是它不关心数据是否改变。

解决方案 2

这个解决方案需要额外的字段orderid并且需要通过批量导入更新一次orderid并且数据在下一次批量导入之前不会改变。

然后您可以在 0,000 秒内在 table 上分页

set @orderid = 0;  
update images im inner join (
  select photo_id, (@orderid := @orderid + 1) as newOrder 
  from images order by hotel_id, idImageType, photo_id
) k
on im.photo_id = k.photo_id
set im.orderid = k.newOrder;  

table k 几乎和第一个解决方案一样快。

所有更新只用了 150,551 秒,比 51 分钟好多了!!! (150 秒对 3060 秒)

批量更新后,您可以通过以下方式进行分页:

 select * from images im where orderid between 19000000 and 19000010;

或更好

 select * from images im where orderid >= 19000000 and orderid< 19000010;  

这需要 0,000 秒来执行第一次和所有其他时间。

在 Rick 评论后编辑

解决方案 3

这个解决方案是为了避免额外的字段和偏移量的使用。但也需要像 this solution

中那样记忆最后一页

这是一个快速的解决方案,仅需 4GB 内存即可用于在线服务器生产

假设您需要读取 20000000 之后的最后十条记录。
有两种情况需要注意:

  • 如果你像我一样需要全部,你可以从第一页开始读到 20000000,并更新一些变量以获取最后一页读取的内存。
  • 你只需要阅读 20000000 之后的最后 10 个。

在第二种情况下,您必须进行预查询才能找到起始页:

select hotel_id, idImageType, photo_id 
  from images im 
  order by hotel_id, idImageType, photo_id limit 20000000,1

它给我:

+----------+-------------+----------+
| hotel_id | idImageType | photo_id |
+----------+-------------+----------+
|  1309878 |           4 | 43259857 |
+----------+-------------+----------+

这需要 6.73 秒。
所以您可以将这些值存储在变量中以备下次使用。
假设我们命名为 @hot=1309878, @type=4, @photo=43259857 然后你可以像这样在第二个查询中使用它:

select * from images im  
  where  
  hotel_id>@hot OR (
    hotel_id=@hot and idImageType>@type OR (
     idImageType=@type and photo_id>@photo
    )
  )  
  order by hotel_id, idImageType, photo_id limit 10;  

第一个子句hotel_id>@hot获取滚动索引上实际第一个字段之后的所有记录,但丢失了一些记录。要取它我们必须做OR子句,取第一个索引字段所有未读记录。

现在只需 0.10 秒。
但是这个查询可以优化(布尔分配):

select * from images im  
  where  
  hotel_id>@hot OR (
    hotel_id=@hot and 
     (idImageType>@type or idImageType=@type) 
     and (idImageType>@type or photo_id>@photo
    )
  )  
  order by hotel_id, idImageType, photo_id limit 10;  

变成:

select * from images im  
  where  
  hotel_id>@hot OR (
    hotel_id=@hot and 
     idImageType>=@type
     and (idImageType>@type or photo_id>@photo
    )
  )  
  order by hotel_id, idImageType, photo_id limit 10;  

变成:

select * from images im  
  where  
  (hotel_id>@hot OR hotel_id=@hot) and 
  (hotel_id>@hot OR
     (idImageType>=@type and (idImageType>@type or photo_id>@photo))
  )
  order by hotel_id, idImageType, photo_id limit 10;  

变成:

select * from images im  
  where  
  hotel_id>=@hot and 
  (hotel_id>@hot OR
     (idImageType>=@type and (idImageType>@type or photo_id>@photo))
  )
  order by hotel_id, idImageType, photo_id limit 10;  

它们是否与我们可以通过限制获得的数据相同?

快速不详尽测试:

select im.* from images im inner join (
  select photo_id from images order by hotel_id, idImageType, photo_id limit 20000000,10
) k 
on im.photo_id=k.photo_id 
order by im.hotel_id, im.idImageType, im.photo_id;

这需要 6.56 秒,数据与上面的查询相同。
所以测试呈阳性。

在此解决方案中,您只需在第一次需要在第一页上查找以阅读时花费 6.73 秒(但如果您需要所有内容,则不需要)。

要实现所有其他页面,您只需 0.10 秒即可获得非常好的结果。

感谢 rick 对基于存储最后一页阅读的解决方案的提示。

结论

解决方案 1 上,您没有任何额外字段,每页花费 3.5 秒
解决方案 2 上,您有额外的字段并且需要在 150 秒内使用大内存服务器(已测试 32 GB)。但随后您在 0,000 秒内阅读了该页面。
解决方案 3 你没有任何额外的字段但必须存储最后一页读取指针,如果你没有从第一页开始阅读你第一页必须花费 6.73 秒。那么您在所有其他页面上只花费 0.10 秒。

此致

编辑 3

解决方案 3 正是 Rick 建议的。对不起,在我之前的 解决方案 3 中我犯了一个错误,当我编写正确的解决方案时,我应用了一些布尔规则,比如分配 属性 等等,毕竟我得到了相同的 Rich 解决方案! 问候