memo:因为工作主要用到存储引擎是innoDB,其他索引引擎不在此文中描述
前置知识
并发事务带来的问题
1、脏读:事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据
简单理解:别人都回滚了,还能读到别人的脏数据
2、不可重复读:事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果 不一致。
简单理解:读到别人提交的update数据,前后读的数据不一样
3、幻读:系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是系统管理员B就在这个时候插入了一条具体分数的记录,当系统管理员A改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。
简单理解;读到别人提交的insert数据
小结:不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表
事务隔离级别
读未提交(read-uncommitted) | 什么都防不住 |
---|---|
不可重复读(read-committed) | 防止脏读,MVCC实现 |
可重复读(repeatable-read) | 防止不可重复读(mysql默认级别),MVCC实现 |
串行化(serializable) | 防止幻读 |
MVCC
多版本并发控制。mysql也好,oracle也好,都是有这种机制的,但是具体实现不同
innoDB的实现,是在每一行的后面添加2个隐藏的列,数据创建时间,数据过期时间
当然这里的时间并不是具体的时间值,而是一个递增的事务id
每一次开始事务,都会递增。通过事务id来限制crud
比如select的时候,只获取创建时间事务id,小于当前事务id的数据
(确保数据是事务开始前被创建的)
过期时间事务id要么大于当前事务id,要么未定义
(确保数据要么不会过期,要么只会被后面运行的事务删掉)
锁争夺情况监测
mysql> show status like 'innodb_row_lock%';
+-------------------------------+-------+
| Variable_name | Value |
+-------------------------------+-------+
| Innodb_row_lock_current_waits | 0 |
| Innodb_row_lock_time | 18702 |
| Innodb_row_lock_time_avg | 18702 |
| Innodb_row_lock_time_max | 18702 |
| Innodb_row_lock_waits | 1 |
+-------------------------------+-------+
如果发现锁争用比较严重,如InnoDB_row_lock_waits和InnoDB_row_lock_time_avg的值比较高
锁的类型
共享锁(s)
SQL:SELECT FROM table_name WHERE … LOCK IN SHARE MODE;
commit (解锁)
*概述:又称读锁。允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。若事务T对数据对象A加上S锁,则事务T可以读A但不能修改A,其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S锁。这保证了其他事务可以读A,但在T释放A上的S锁之前不能对A做任何修改。
排他锁(x)
SQL:SELECT FROM table_name WHERE … FOR UPDATE;
commit (解锁)
*概述:又称写锁。允许获取排他锁的事务更新数据,阻止其他事务取得相同的数据集共享读锁和排他写锁。若事务T对数据对象A加上X锁,事务T可以读A也可以修改A,其他事务不能再对A加任何锁,直到T释放A上的锁。
还有其他意向锁,自增锁 间隙锁,等等,不做描述
特点及注意事项
实现方式
InnoDB通过给索引上锁,如果匹配不到索引,会直接上表锁
注意事项
在了解InnoDB锁特性后,用户可以通过设计和SQL调整等措施减少锁冲突和死锁,包括:
- 尽量使用较低的隔离级别; 精心设计索引,并尽量使用索引访问数据,使加锁更精确,从而减少锁冲突的机会;
- 选择合理的事务大小,小事务发生锁冲突的几率也更小;
- 给记录集显式加锁时,最好一次性请求足够级别的锁。
比如要修改数据的话,最好直接申请排他锁,
而不是先申请共享锁,修改时再请求排他锁,这样容易产生死锁;
- 不同的程序访问一组表时,应尽量约定以相同的顺序访问各表,对一个表而言,尽可能以固定的顺序存取表中的行。这样可以大大减少死锁的机会;
- 尽量用相等条件访问数据,这样可以避免间隙锁对并发插入的影响; 不要申请超过实际需要的锁级别;除非必须,查询时不要显示加锁;
- 对于一些特定的事务,可以使用表锁来提高处理速度或减少死锁的可能。