----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-------+------------+--------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| user | 0 | PRIMARY | 1 | id | A | 5 | NULL | NULL | | BTREE | | |
| user | 1 | index_username_age | 1 | username | A | 4 | NULL | NULL | | BTREE | | |
| user | 1 | index_username_age | 2 | age | A | 5 | NULL | NULL | | BTREE | | |
+-------+------------+--------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
3 rows in set (0.00 sec)
mysql>
上面可以看出,我们建立联合索引成功,下面我们开始测试,首先,我们在窗口A执行以下操作:
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from user where username='tom' and age = 20 for update;
+----+----------+-----+
| id | username | age |
+----+----------+-----+
| 1 | tom | 20 |
+----+----------+-----+
1 row in set (0.00 sec)
mysql>
可以看出,和我们之前的操作没啥两样,同样是打开事务进行操作,现在我们在窗口B执行以下操作:
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from user where username='tom' and age = 20 for update;
-
很清楚的看到B窗口被锁住了,但是我们现在确定的是加的锁,并不知道是行锁还是表锁,没关系,我们换个条件:
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from user where username='joey' and age = 22 for update;
+----+----------+-----+
| id | username | age |
+----+----------+-----+
| 2 | joey | 22 |
+----+----------+-----+
1 row in set (0.00 sec)
mysql>
这样,我们很清楚的就能看到走的是行锁了。
只不过大家要注意联合索引的命中规则也就是最左匹配原则,我们可以试一试单独使用username作为条件看看走的什么锁,也可以看看单独使用age走的什么锁,这里就不再演示了,大家可以自行的尝试。
总结
前提:必须在事务里面
样例:select * from table where column = condition for update;
结果:
- 当coulmn是索引列的时候,也就是查询走的索引的时候,这个时候锁的就是行(行锁);
- 当coulmn不是索引的时候,也就是查询没走索引的时候,这个时候锁的就是整个表(表锁);
悲观锁
含义
悲观锁是从数据库层面加锁。总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它释放锁;
例子
上面其实关于行锁和表锁的测试那里我们使用的排他锁也就是悲观锁;
select * from table where xxx for update
在上面我们举的例子够多了,这里不再多说;
乐观锁
含义
总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据;
例子
表中有一个版本字段,第一次读的时候,获取到这个字段。处理完业务逻辑开始更新的时候,需要再次查看该字段的值是否和第一次的一样。如果一样就更新,反之拒绝。之所以叫乐观,因为这个模式没有从数据库加锁,等到更新的时候再判断是否可以更新。
update table set xxx where id = 1 and version = 1;
上面的语句就很清楚的说明了乐观锁,在对id = 1的数据进行更新的同时添加了version = 1的条件,version是当前事务开始之前查询出来的版本号,如果这个时候其他事务对id = 1的数据进行了更新会将version+1,所以如果其他事务进行了更新,这条语句是执行不成功的;
参考文章:https://juejin.im/post/5b4977ae5188251b146b2fc8
间隙锁GAP
当我们用范围条件检索数据而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合范围条件的已有数据记录的索引项加锁;对于键值在条件范围内但并不存在的记录,叫做“间隙(GAP)”。InnoDB也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁。
值得注意的是:间隙锁只会在Repeatable read
隔离级别下使用~
例子:假如emp表中只有101条记录,其empid的值分别是1,2,...,100,101
Select * from emp where empid > 100 for update;
上面是一个范围查询,InnoDB不仅会对符合条件的empid值为101的记录加锁,也会对empid大于101(这些记录并不存在)的“间隙”加锁。
InnoDB使用间隙锁的目的有两个:
- 为了防止幻读(上面也说了,
Repeatable read
隔离级别下再通过GAP锁即可避免了幻读)
- 满足恢复和复制的需要
- MySQL的恢复机制要求:在一个事务未提交前,其他并发事务不能插入满足其锁定条件的任何记录,也就是不允许出现幻读 ;
死锁
并发的问题就少不了死锁,在MySQL中同样会存在死锁的问题。
但一般来说MySQL通过回滚帮我们解决了不少死锁的问题了,但死锁是无法完全避免的,可以通过以下的经验参考,来尽可能少遇到死锁:
- 1)以固定的顺序访问表和行。比如对两个job批量更新的情形,简单方法是对id列表先排序,后执行,这样就避免了交叉等待锁的情形;