更新mysql大表太挂了

时间:2015-06-24 15:13:49

标签: mysql sql performance myisam

更新MySql MyISAM大表的性能问题根据同一个表上的索引制作列升序

我的问题是服务器只有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我也有一个升序索引。

该表有 21百万条记录,是一张MyIsam表。

表格如下:

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;

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

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

最好的问候

3 个答案:

答案 0 :(得分:0)

您可以使用其中一些:

  1. 将引擎更新为InnoDB,它只会阻止一行,而不是更新所有表。

  2. 使用photo_id和良好的orderid创建#temp表,然后从这个temp更新你的表:

    update images im, temp tp
    set im.orderid = tp.orderid
    where im.photo_id = tp.photo_id
    
  3. 这将是最快的方式,当您填写tmp表时 - 主表上没有块。

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

答案 1 :(得分:0)

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

DROP前者;后者照顾它的任何需要。 (这不会加快原始查询。)

“问题在于性能:挂几分钟!”有什么问题?

  • 其他查询会被阻止几分钟? (InnoDB应该提供帮助。)
  • 您经常运行此更新,这很烦人吗? (为什么在这个世界?)
  • 别的什么?

执行更新时,这个索引代价很高:

KEY `orderid` (`orderid`)

DROP并重新创建它。 (不要费心去掉其余部分。)与InnoDB一起使用的另一个原因是这些操作可以在不复制表的情况下完成(在5.6中)。 (21M行==很长时间,如果它必须复制表!)

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

我还有两个更具体的建议,但我想先找到你的答案。

修改分页,按hotel_id, idImageType, photo_id排序:

可以通过该三元组按顺序读取记录。甚至通过它们“分页”。

如果你在($hid, $type, $pid)之后“离开”,这将是“下一个”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 - 消除停机时间

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

  1. CREATE TABLE New建议的索引更改。
  2. 将数据加载到New。 (一定要避免你的51分钟超时;我不知道是什么造成的。)
  3. RENAME TABLE images TO old, New TO images;
  4. DROP TABLE old;
  5. 这样可以避免阻塞表加载和架构更改。原子步骤#3会有一个非常短的块。

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

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

答案 2 :(得分:0)

坦克到每个人的身体。你的答案对我很有帮助。我想现在我找到了一个更好的解决方案。

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

  • 大表上的高效分页
  • 更新大表。

要在大型表上继续使用高效的paginate,我已经找到了一个解决方案,通过在表上进行先前的更新,但这样做会在更新所需的51分钟时间内出现问题,从而导致我的java基础架构超时(spring-批量步骤)。

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

常见解决方案步骤

要像我需要的那样按照字段tupla进行分页,我创建了一个索引:

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);  

这是为了避免触摸表并仅使用索引文件......

假设我们想要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万个表记录 上的表k只需要1.5秒,因为它只使用索引hotelTypePhoto中的三个字段所以避免& #39; t访问表文件并仅在索引文件上工作。

订单就像原来的要求(hotel_id,idImageType),因为它包含在(hotel_id,idImageType,photo_id)中:相同的子集......

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

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

对表格进行分区无助于提高性能。

如果服务器将其置于缓存中,则时间会下降,或者如果您执行jdbc params语句,时间也会下降(我想)。

如果您经常使用它,它的优势在于它不关心数据是否发生变化。

解决方案2

此解决方案需要额外的字段orderid,并且需要通过批量导入进行一次订单更新,并且在下次批量导入之前数据不得更改。

然后你可以在0,000秒内在桌面上分页。

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;  

表k快速几乎与第一个解决方案相似。

这个全部更新只需要150,551秒比51分钟好得多! (150s vs 3060s)

在批处理中进行此更新后,您可以通过以下方式执行分页:

 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.1秒 但是这个查询可以被优化(bool distributive):

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 上,您有额外的字段,需要一个大内存服务器(32 GB测试),在150秒内。但是你会在0,000秒内阅读该页面 在 解决方案3 上,您没有任何额外字段,但必须存储最后一页读取指针,如果您没有从第一页开始阅读,则必须花费6 ,第一页73秒。然后你在所有其他页面上只花了0.10秒。

祝你好运

编辑3

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