我有一张表 saleItem
+---------------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------------+--------------+------+-----+---------+-------+
| id | char(16) | NO | PRI | NULL | |
| expiry_date | char(8) | NO | MUL | NULL | |
| status | varchar(10) | NO | MUL | | |
| last_update | datetime | NO | MUL | NULL | |
| status_change | datetime | YES | MUL | NULL | |
+---------------+--------------+------+-----+---------+-------+
此处 id 是主键。此表包含数百万个条目,其中状态可以包含五个值。我想使用状态在此表上使用更新查询。当我使用时:
UPDATE saleItem SET status="aa" where status="bb";
我得到以下异常,因为此表也由其他应用程序更新:
SQLException:超出锁定等待超时;尝试重新启动交易
使用如下的子查询可以解决问题:
selectedIds=SELECT id FROM saleItem WHERE status='bb'
UPDATE saleItem SET status="aa" where id in (selectedIds);
此查询是否有效?
此查询的效果怎么样?
有没有更好的方法来解决这个问题?
创建表格查询:
CREATE TABLE `saleItem` (
`id` char(16) NOT NULL,
`expiry_date` char(8) NOT NULL,
`status` varchar(10) NOT NULL DEFAULT '',
`last_update` datetime NOT NULL,
`status_change` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `expiry_date_idx` (`expiry_date`),
KEY `status_idx` (`status`),
KEY `last_update_idx` (`last_update`),
KEY `status_change_idx` (`status_change`)
) ENGINE=InnoDB;
答案 0 :(得分:2)
不,如果尝试更新同一组行,使用子查询将无法解决问题,而不是直接解决问题。我认为问题在于您正在尝试同时更新太多行,并且您正在与锁进行争用(其他会话持有行上的锁。)
我尝试将这个巨大的交易分成更小的块,一次获得几千行。像这样:
UPDATE saleItem SET status='aa' where status='bb' LIMIT 4000;
重复多次,直到更新的行数为零。
status
是索引中的前导列吗?目前尚不清楚表中可用的索引。 SHOW CREATE TABLE saleitem
的输出可以让我们更好地了解存在哪些索引。
如果status
上没有合适的索引,那么可能MySQL会从表的开头开始,然后开始查看行。找到4000行进行更新不应该花费太长时间。下一次,如果它再次从表的开头开始,则需要查看更多的行。
所以,要做到这一点,我肯定希望得到一个索引。我们可能会尝试像
这样的东西CREATE TABLE bb_id
( ai INT NOT NULL AUTO_INCREMENT PRIMARY KEY
, id CHAR(16) NOT NULL PRIMARY KEY
);
INSERT INTO bb_id (id) SELECT id FROM saleitem WHERE status = 'bb';
然后我可以使用连接操作来更新批量的行。
UPDATE saleitem s
JOIN bb_id b
ON b.id = s.id
SET s.status = 'aa'
WHERE s.status = 'bb'
AND b.ai > 0
AND b.ai <= 4000
下一次运行,
AND b.ai > 4000
AND b.ai <= 8000
等
并不能保证您不会遇到LOCK TIMEOUT问题,但您正在运行的交易规模会更合理。你可能想要在流行音乐中做更多的4000行,或者更少。但这是我要采取的方法。
答案 1 :(得分:0)
从您提供的表格结构中,我会提出几点:
您正在使用varchar(10)作为您的状态,并且它没有KEY / INDEX。 这将导致每个间隔发生一次表扫描,以尝试找出哪些记录的状态为“bb”。
如果您不想放入索引,我会诚实地在2部分查询中执行此操作。问题是,当您更新表时,您还通过更新同一列中的值来更改混合,因此表扫描会锁定更新。
我会做以下SQL:
dispatch_async(dispatch_get_main_queue(), {
// Display alert message with confirmation
self.signInViewIndicator.stopAnimating()
var myAlert = UIAlertController(title: "Alert", message: messageToDisplay, preferredStyle: UIAlertControllerStyle.Alert)
let okAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler:nil)
myAlert.addAction(okAction);
self.presentViewController(myAlert, animated: true, completion: nil)
})
SQL Above将首先获取状态为“bb”的所有ID,然后在收到完整列表后更新它们。
答案 2 :(得分:0)
您只有5个不同的status
值?将其设为ENUM
。那将需要1个字节而不是现在的几个字节。较小 - &gt;更快。
只有5个值?对于某些值,您的UPDATE
将执行表扫描而不是使用索引!因此,对低基数的列进行索引通常是个坏主意。
以下是另一个原因:UPDATE
正在改变status
。这意味着(1)更改表中的数据,(2)删除包含旧值的索引条目,以及(3)插入具有新值的新索引条目。这是3个步骤,而不仅仅是1个步骤!
UPDATE
如何在没有INDEX(status)
的情况下提高效率?查看另一个chunking technique - 一个涉及有效地遍历PRIMARY KEY
的{{3}}。 (该博客引用DELETEs
,但分块适用于UPDATEs
等。