innodb减少行锁对性能的影响

本文探讨了数据库的两阶段加锁协议,指出在事务中应尽量减少锁冲突,通过调整操作顺序降低阻塞时间。同时,解释了死锁的概念,并提供了一个简单的死锁例子。在InnoDB中,解决死锁通常采用设置超时或主动回滚策略。为避免死锁,建议优化业务逻辑或控制并发度。

  基于两阶段协议,数据库的加锁和解锁分为两个阶段,加锁和解锁都应该在不同的阶段。下图操作序列中,事务B在操作id=1的数据的时候会阻塞住,直到事务A提交事务(在提交事务的时候统一解锁)才能执行自己的更新语句。

为了减少发生冲突后阻塞的时间,如果你的事务中需要锁多个行,要把最可能造成锁冲突、最可能影响并发度的锁尽量往后放。如上面的例子如果业务允许的情况下可以两条更新语句的顺序互换,让可能发生行冲突的id=1的更新语句尽量靠后。

死锁和死锁检测

当并发系统中不同线程出现循环资源依赖,涉及的线程都在等待别的线程释放资源时,就会导致这几个线程都进入无限等待的状态,称为死锁。

下面是一个很简单的死锁例子:事务A和事务B在互相等待对方的资源释放。

在innodb中,发生死锁一般有两种解决方法:

  • 进入等待直到超时,可以通过设置innodb_lock_wait_timeout调整超时时间
  • 设置死锁检测,主动回滚死锁链条中的某一个事务,让其他事务得以继续执行,将参数innodb_deadlock_detect设置为on,表示开启功能。

第一种方法超时时间不好设置,时间设置长了线上等待无法忍受,短了如果本身是正常的操作不是发生了死锁,又会有很多误判。

一般用第二种,每当一个事务被锁的时候,就要看看它所依赖的线程有没有被别人锁住,如此循环,最后判断是否出现了循环等待,也就是死锁。这个过程是有性能开销的,要判断两两事务之间是否出现死锁要O(n*2)的复杂度,在1000个并发线程的情况下死锁检测就达到百万量级。

一般两种方法解决:

确保业务不会出现死锁,把死锁检测关掉

控制并发度,例如使用中间件做一层缓冲

 参考:

 mysql45讲

InnoDB 存储引擎中,**(Row-Level Locking)是并发控制的重要机制**,用于在多用户并发访问数据库时,确保事务的隔离性和一致性。InnoDB 支持,这是相对于表级更细粒度的机制,能大大提高并发性能。 --- ## ✅ InnoDB 的实现原理 InnoDB并不是通过单独的对象来实现的,而是**通过索引项上的机制实现的**。也就是说,**InnoDB实际上是“索引记录”**。 ### 🔍 InnoDB 的实现依赖于以下核心机制: 1. **索引结构**: - InnoDB 使用 B+ 树索引结构。 - 每个索引记录(index record)都可以被加。 - 是加在索引记录上的,而不是物理上。 2. **的类型**: InnoDB 支持多种模式,常见的类型包括: - **记录(Record Lock)**:住一条索引记录。 - **间隙(Gap Lock)**:住一个范围,但不包括记录本身(用于防止幻读)。 - **临键(Next-Key Lock)**:记录 + 间隙定一个范围,并包括记录本身。 3. **的存储结构**: - 每个事务在请求时,InnoDB 会在内存中维护一个结构(`lock0sys.h` 中定义)。 - 每个结构包含事务信息、的类型、定的索引记录等信息。 4. **的兼容性**: - InnoDB 使用兼容矩阵来判断多个事务的请求是否冲突。 - 例如,两个事务不能同时对同一记录加排他(X),但可以同时加共享(S)。 --- ## ✅ 的实现细节 ### 1. 记录(Record Lock) ```sql -- 假设有主键索引 id SELECT * FROM users WHERE id = 1 FOR UPDATE; ``` - InnoDB 会在主键索引中定 `id = 1` 这条记录。 - 其他事务不能对这条记录进修改或加排他。 ### 2. 间隙(Gap Lock) ```sql -- 假设 id 是主键,当前表中没有 id = 5 的记录 SELECT * FROM users WHERE id BETWEEN 3 AND 7 FOR UPDATE; ``` - InnoDB定 `(3,7)` 这个范围,防止其他事务插入 `id=5`。 - 这是防止“幻读”的关键机制。 ### 3. 临键(Next-Key Lock) - 是记录和间隙的组合。 - 定一个索引记录及其前面的间隙。 - InnoDB 默认使用 Next-Key Lock 来避免幻读问题。 --- ## ✅ 示例:演示如何在事务中工作 ```python # Python 示例:使用 pymysql 模拟两个事务对同一 import pymysql import threading def transaction1(): conn = pymysql.connect(host='localhost', user='root', password='123456', db='testdb') cur = conn.cursor() try: conn.begin() cur.execute("UPDATE users SET name = 'Alice' WHERE id = 1") print("Transaction 1: Updated id=1") input("Transaction 1: Press Enter to commit...") conn.commit() finally: cur.close() conn.close() def transaction2(): conn = pymysql.connect(host='localhost', user='root', password='123456', db='testdb') cur = conn.cursor() try: conn.begin() print("Transaction 2: Trying to update id=1...") cur.execute("UPDATE users SET name = 'Bob' WHERE id = 1") print("Transaction 2: Updated id=1") conn.commit() except Exception as e: print("Transaction 2: Error:", e) finally: cur.close() conn.close() # 启动两个事务 t1 = threading.Thread(target=transaction1) t2 = threading.Thread(target=transaction2) t1.start() t2.start() t1.join() t2.join() ``` 在这个例子中: - 事务1先对 `id=1` 加排他; - 事务2尝试更新同一时会被阻塞,直到事务1提交或回滚; - 如果事务1回滚,事务2可以继续执; - 如果事务1提交,事务2继续执。 --- ## ✅ InnoDB 的特点 | 特性 | 描述 | |------|------| | 粒度 | ,粒度最小,适合高并发写操作 | | 死 | 可能发生,InnoDB 有死检测机制 | | 开销 | 较大,需要维护结构 | | 适用场景 | 高并发写入、更新、删除操作 | --- ## ✅ 小结 - InnoDB是基于索引记录实现的。 - 的类型包括记录、间隙、临键。 - 提高了并发性能,但也带来了死等待的问题。 - 要避免带来的性能问题,应合理设计索引,减少冲突。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值