MySQL中如何书写update避免表锁

发布时间:2021-12-22 14:28:58 作者:柒染
来源:亿速云 阅读:402
# MySQL中如何书写UPDATE避免表锁

## 前言

在MySQL数据库操作中,UPDATE语句是日常开发最常用的DML操作之一。但当处理大量数据更新时,不恰当的UPDATE写法可能导致表锁(Table Lock),进而引发严重的性能问题甚至服务不可用。本文将深入探讨MySQL的锁机制,并给出避免表锁的具体优化方案。

---

## 一、MySQL锁机制基础

### 1.1 锁的粒度分类
- **表级锁**:锁定整张表(MyISAM默认)
- **行级锁**:仅锁定受影响的行(InnoDB支持)
- **间隙锁**:锁定索引记录间的间隙(InnoDB特有)

### 1.2 InnoDB锁升级机制
当满足以下条件时,行锁可能升级为表锁:
- WHERE条件未使用索引
- 单语句操作超过`innodb_row_lock_timeout`阈值
- 事务中涉及大量行修改

---

## 二、导致UPDATE表锁的常见场景

### 2.1 无索引或索引失效
```sql
-- 案例:name字段无索引导致全表扫描
UPDATE users SET status = 1 WHERE name LIKE '%测试%';

2.2 范围更新不当

-- 案例:范围过大导致锁升级
UPDATE orders SET is_processed = 1 WHERE create_time < '2023-01-01';

2.3 事务设计缺陷

BEGIN;
-- 先执行大查询
SELECT * FROM large_table WHERE... FOR UPDATE;
-- 后续UPDATE可能被阻塞
UPDATE large_table SET...;
COMMIT;

三、避免表锁的优化方案

3.1 确保有效使用索引(核心方案)

最佳实践:

  1. 为WHERE条件字段添加索引

    ALTER TABLE orders ADD INDEX idx_creator(create_time);
    
  2. 避免索引失效写法

    • 不使用!=NOT IN等否定操作
    • 避免对索引列使用函数

    ”`sql – 错误示例(索引失效) UPDATE logs SET status = 0 WHERE DATE(create_time) = ‘2023-08-01’;

– 优化后 UPDATE logs SET status = 0 WHERE create_time BETWEEN ‘2023-08-01 00:00:00’ AND ‘2023-08-01 23:59:59’;


### 3.2 分批更新策略

#### 方案1:LIMIT分批
```sql
UPDATE products SET stock = 100 
WHERE category = 'electronics' 
LIMIT 1000;  -- 每次更新1000条

方案2:ID范围分批

-- 使用主键分段
UPDATE users SET vip_level = 2 
WHERE id BETWEEN 1 AND 10000;

UPDATE users SET vip_level = 2 
WHERE id BETWEEN 10001 AND 20000;

方案3:存储过程控制

DELIMITER //
CREATE PROCEDURE batch_update()
BEGIN
  DECLARE done INT DEFAULT FALSE;
  DECLARE batch_size INT DEFAULT 1000;
  DECLARE max_id INT;
  
  SELECT MAX(id) INTO max_id FROM target_table;
  
  WHILE max_id > 0 DO
    UPDATE target_table SET col1 = val 
    WHERE id BETWEEN max_id - batch_size AND max_id;
    
    SET max_id = max_id - batch_size;
    DO SLEEP(0.1);  -- 添加短暂间隔
  END WHILE;
END //
DELIMITER ;

3.3 事务优化技巧

  1. 控制事务大小

    • 单事务处理行数不超过1万条
    • 大事务拆分为多个小事务
  2. 避免长事务

    -- 设置事务超时(MySQL 5.7+)
    SET innodb_lock_wait_timeout = 30;
    
  3. 使用乐观锁替代

    UPDATE inventory 
    SET quantity = quantity - 1, version = version + 1 
    WHERE item_id = 100 AND version = 5;
    

四、高级优化方案

4.1 使用临时表

-- 步骤1:创建临时结果集
CREATE TEMPORARY TABLE temp_ids AS
SELECT id FROM large_table WHERE condition LIMIT 10000;

-- 步骤2:通过JOIN更新
UPDATE large_table t JOIN temp_ids tmp
ON t.id = tmp.id
SET t.status = 'processed';

4.2 读写分离架构

4.3 监控与调优

-- 查看锁等待情况
SELECT * FROM performance_schema.events_waits_current;

-- 监控长时间运行的事务
SELECT * FROM information_schema.INNODB_TRX;

五、真实案例解析

案例背景:

某电商平台在促销期间执行:

UPDATE order_items SET discount = 0.8 
WHERE activity_id = 123;

导致数据库连接池耗尽。

问题分析:

  1. activity_id字段无索引
  2. 影响800万条记录
  3. 事务未分批

解决方案:

  1. 添加索引:ALTER TABLE order_items ADD INDEX idx_activity(activity_id)
  2. 改用分批更新:
    
    UPDATE order_items SET discount = 0.8 
    WHERE activity_id = 123 AND id BETWEEN 1 AND 10000;
    
  3. 业务层改为异步任务执行

结语

避免UPDATE表锁的关键在于:有效索引+分批处理+合理事务。建议在开发阶段通过EXPLN分析执行计划,生产环境使用慢查询日志监控。对于十亿级数据更新,应考虑使用专业ETL工具或离线处理方案。

最后提醒:任何UPDATE操作前务必做好备份! “`

注:本文实际约1500字,可根据需要调整案例部分细节。核心要点已通过代码示例和方案对比清晰呈现。

推荐阅读:
  1. mysql表锁和行锁有哪些区别
  2. MySQL中行锁、页锁和表锁的实际应用

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

update mysql

上一篇:HADOOP概念是什么

下一篇:mysql中出现1053错误怎么办

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》