mysql的锁机制
如何保证数据并发访问的一致性和有效性, 是所有数据库必须解决的一个问题。
另外, 所冲突也是影响数据库并发性能的一个重要的因素, 应用程序在选择所类型时, 需要根据实际运行的需要, 选择最佳的所类型。
MySQL的不同存储引擎, 支持不同的锁定机制
锁类型
- MyISAM 和 Memory 存储引擎使用的是表级锁。
- InnoDB 存储引擎既支持行级锁, 也支持表级锁, 默认情况下使用行级锁。
各种锁的优缺点
- 表级锁, 它直接锁住的是一个表, 开销小, 加锁快, 不会出现死锁的情况, 锁定粒度大, 发生锁冲突的概率更高, 并发度最低。
- 行级锁, 它直接锁住的是一条记录, 开销大, 加锁慢, 发生锁冲突的概率较低, 并发度很高。
- 页级锁, 它是锁住的一个页面, 在 InnoDB 中一个页面为16KB, 它的开销介于表级锁和行级锁中间, 也可能会出现死锁, 锁定粒度也介于表级锁和行级锁中间, 并发度也介于表级锁和行级锁中间。
- 仅仅从锁的角度来说, 表级锁更加适合于以查询为主的应用, 只有少量按照索引条件更新数据的应用
- 行级锁更适合大量按照索引条件并发更新少量不同的数据, 同时还有并发查询的应用
innodb的行级锁
InnoDB有两种类型的行级锁, 两种内部使用的意向锁;
- 共享锁(S):允许一个事务读一行数据时, 阻止其他的事务读取相同数据的排他锁。
- 排他锁(X):允许获得排他锁的事务更新数据, 阻止其他事务取得相同数据的共享锁和排他锁。
- 意向共享锁(IS):事务打算给数据行加行共享锁。 事务在给一个数据行加共享锁前必须先取得该表的IS锁。
- 意向排他锁(IX):事务打算给数据行加行排他锁。 事务在给一个数据行加排他锁前必须先取得该表的IX锁。
- 悲观锁(抽象, 不真实存在的锁):可以认为所有加锁的设计都是悲观锁
- 乐观锁(抽象, 不真实存在的锁):可以认为所有不加锁的设计都是乐观锁
如果一个事务请求的锁模式与当前的所模式兼容, InnoDB就将请求的锁授予该事务, 如果两者不兼容, 那么该事务就要等待锁释放。
意向锁是InnoDB存储引擎自动加的, 对于普通select语句, InnoDB不会加任何锁, 对于insert, update, delete语句, InnoDB会自动给涉及的数据加排他锁,
InnoDB以通过以下语句显示添加的共享锁和排他锁:
-- 共享锁语句
select * from table_name lock in share mode;
-- 排他锁语句
select * from table_name for update;
意向锁
意向锁是表级锁, 其设计目的主要是为了在一个事务中揭示下一行将要被请求锁的类型。
意向锁是 InnoDB 自动加的, 不需要用户干预
共享锁和排他锁都是锁的行记录,
意向共享锁和意向排他锁锁定的是表
两者不兼容, 那么该事务就要等待锁释放。
对于普通select语句, InnoDB不会加任何锁, 对于insert, update, delete语句, InnoDB会自动给涉及的数据加排他锁
InnoDB 中的两个表锁:
- 意向共享锁(IS) :
表示事务准备给数据行加入共享锁, 也就是说一个数据行加共享锁前必须先取得该表的 IS 锁。
如果需要对记录 A 加共享锁, 那么此时InnoDB 会先找到这张表, 对该表加意向共享锁之后, 再对记录 A 添加共享锁 - 意向排他锁(IX) :
类似上面, 表示事务准备给数据行加入排他锁, 也就是说事务在给一个数据行加排他锁前必须先取得该表的 IX 锁。
如果需要对记录 A 加排他锁, 那么此时 InnoDB 会先找到这张表, 对该表加意向排他锁之后, 再对记录 A 添加排他锁
共享锁和排他锁, 系统在特定的条件下会自动添加共享锁或者排他锁, 也可以手动添加共享锁或者排他锁。
意向共享锁和意向排他锁都是系统自动添加和自动释放的, 整个过程无需人工干预
实例
死锁
共享锁死锁
共享锁和意向排它锁冲突导致的死锁:
事务A :BENGIN
- 操作1:查询所有数据并加共享锁
select * from table_name lock in share mode; - 操作3:更新name=test的数据
update table_name set age=10 where name=“test”;
事务B:BENGIN
- 操作2:查询所有数据并加共享锁
select * from table_name lock in share mode; - 操作4:更新name=test的数据
update table_name set age=11 where name=“test”;
上面两个事务是在不同的并发中,操作流程就是 1->2->3->4
1、其中操作1是事务A中查询所有数据并加了共享锁,
2、然后操作2是在事务B中也查询了所有数据并加共享锁,
其中共享锁之间是兼容的,不会冲突。
接下来就是死锁的步骤:
3、操作3是 事务A做了更新(innodb的事务中CURD出了select都会默认给该行加排它锁),此时事务A的排它锁和事务B的共享锁冲突,从而发生了阻塞(因为事务B没有执行完成,没有commit)。
4、操作4是 事务B也做了更新(innodb的事务中CURD出了select都会默认给该行加排它锁),此时事务B的排它锁和事务A的排它锁冲突,从而发生了阻塞(因为事务B没有执行完成,没有commit)。
5、此时事务A的update和事务B的update导致了,事务A、B相互等待,此时就是死锁。
对于单行数据的死锁,5.7的操作是让先来的的执行,后来的退出,即操作4会退出提示error。
还有一种原因是超时导致的退出:
可以通过执行 select @@innodb_lock_wait_timeout 查看