mysql中各种锁详解

mysql中也支持各种锁–今天来将脑海中的知识来整理一下(听说大厂面试可能会问到mysql中锁的机制问题,所以还是好好学习吧)

    就像java中各种锁 轻量锁,偏向锁,重级锁–目前我还不太熟悉-------- 慢慢学习吧,我刚入门要学习的东西很多–谁让我踏上了程序员之路

    首先按照表来区分锁

    innodb支持表锁,行锁;

    myisam支持表锁;

    dbd支持页面锁和表锁;

    表锁加锁快,开销小,不会出现死锁;锁定范围大,发生锁冲突的概率最高,并发度也是最低;

    行锁加锁慢,开销大,会出现死锁;锁定范围小,发生锁冲突概率小,并发度高;

    页面锁是介于表锁和行锁之间,范围介于两者之间,并发度一般;

由于mysql8.0默认引擎innodb,默认为行锁,所以先整理一下行锁----

innodb引擎中的行锁

行锁–

行锁分为共享锁和排他锁

共享锁---->又叫读锁,当一个线程获取读锁后,会阻塞其他用户对该行数据的写操作(即阻止其他事务的排他锁),但不会阻止其他用户对改行数据的读操作(共享锁);

排他锁---->又叫写锁,当一个线程获取写锁之后,会阻塞其他用户对改行数据的读写操作

行锁的实现方式—

InnoDB行锁是给索引项加锁来实现的。所以只有通过索引项来操作数据才会有行锁,如果没有操作索引项 用的则是表锁;

默认情况下innodb用的是隐式加锁–

对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及数据集加写锁–排他锁(X)因为要操作数据;

对于普通SELECT语句,InnoDB不会加任何锁,因为不需要操作数据,只是读取数据;

显示加锁的操作格式如下–

    共享锁(S):SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE

    排他锁(X) :SELECT * FROM table_name WHERE ... FOR UPDATE

        1

        2

对于行锁用在事务的操作上,单纯为某一行数据加行锁没有太大意义(行锁是加在索引上的而不是数据上的)下面做一下演示:

mysql> start transaction ;

Query OK, 0 rows affected (0.00 sec)

mysql> desc huixin ;

+--------------+-------------+------+-----+---------+-------+

| Field        | Type        | Null | Key | Default | Extra |

+--------------+-------------+------+-----+---------+-------+

| user_id      | int        | NO  | PRI | NULL    |      |

| user_name    | varchar(64) | YES  |    | NULL    |      |

| basic_salary | int        | YES  |    | NULL    |      |

+--------------+-------------+------+-----+---------+-------+

3 rows in set (0.00 sec)

mysql> select user_id ,user_name from huixin where user_id=1002 for update ;

+---------+-----------+

| user_id | user_name |

+---------+-----------+

|    1002 | 赵四      |

+---------+-----------+

1 row in set (0.00 sec)

mysql> commit ;

Query OK, 0 rows affected (0.00 sec)

mysql> start transaction ;

Query OK, 0 rows affected (0.00 sec)

mysql> select user_id ,user_name from huixin where user_id=1002 lock in share mode ;

+---------+-----------+

| user_id | user_name |

+---------+-----------+

|    1002 | 赵四      |

+---------+-----------+

1 row in set (0.00 sec)

mysql> insert into huixin values(1008,'李二狗',4500);

Query OK, 1 row affected (0.00 sec)

mysql> update huixin set user_name='zhaosi' where user_id=1002 ;

Query OK, 1 row affected (0.00 sec)

Rows matched: 1  Changed: 1  Warnings: 0

用select 。。。。。where [condition] lock in share mode 来获得共享锁,在查询时候确保该记录没有被其他用户用update或者delete所操作,不然查询的数据可能跟原表不能保持一致。

在这里插入图片描述以上是两个用户登陆数据库,由于不能同时触发命令所以也就无法触发错误提示,仅供了解,如果是不同的设备登陆,如果我在查询的时候加了行级锁,另一个设备2同时在修改我要查询的数据,则会阻塞设备2上对该数据的修改;

用select 。。。。。where [condition] lock in share mode 来获得排他锁,在修改数据的时候不允许其他用户读和写该数据;

在这里插入图片描述以上是两个用户登陆数据库,由于不能同时触发命令所以也就无法触发错误提示,仅供了解,如果是不同的设备登陆,如果我在修改数据的时候加了行级锁,另一个设备同时在查看或者修改我要修改的数据,则会阻塞在设备2上对该数据的操作;

以上就是行锁的原理;

下面是表锁—

    innodb和myisam以及dbd都支持表锁,所以还是以innodb的表锁为例演示–

mysql> use company

Database changed

mysql> #这里是不上锁的表huixin;

mysql> desc huixin ;

+--------------+-------------+------+-----+---------+-------+

| Field        | Type        | Null | Key | Default | Extra |

+--------------+-------------+------+-----+---------+-------+

| user_id      | int        | NO  | PRI | NULL    |      |

| user_name    | varchar(64) | YES  |    | NULL    |      |

| basic_salary | int        | YES  |    | NULL    |      |

+--------------+-------------+------+-----+---------+-------+

3 rows in set (0.03 sec)

mysql> insert into huixin values(1011,'王二小',3600);

Query OK, 1 row affected (0.01 sec)

mysql> #以下是上表锁的huixin;

mysql> lock table huixin read ;

Query OK, 0 rows affected (0.00 sec)

mysql> select * from huixin ;

+---------+-----------+--------------+

| user_id | user_name | basic_salary |

+---------+-----------+--------------+

|    1001 | 爱德华    |        2500 |

|    1002 | 赵四      |        2500 |

|    1003 | 王伟      |        2200 |

|    1004 | 宫本      |        2100 |

|    1005 | 黄淮      |        3000 |

|    1006 | 胡歌      |        5000 |

|    1007 | 张浩      |        6500 |

|    1008 | 李二狗    |        4500 |

|    1011 | 王二小    |        3600 |

+---------+-----------+--------------+

9 rows in set (0.00 sec)

mysql> insert into huixin values(1009,'郑中基',5620);

ERROR 1099 (HY000): Table 'huixin' was locked with a READ lock and can't be updated

mysql> #上读锁之后,表就处于只读状态,不能修改表中数据;

#解锁

mysql> lock table huixin write ;

Query OK, 0 rows affected (0.00 sec)

mysql> insert into huixin values(1009,'郑中基',5620);

Query OK, 1 row affected (0.00 sec)

mysql> select * from huixin ;

+---------+-----------+--------------+

| user_id | user_name | basic_salary |

+---------+-----------+--------------+

|    1001 | 爱德华    |        2500 |

|    1002 | 赵四      |        2500 |

|    1003 | 王伟      |        2200 |

|    1004 | 宫本      |        2100 |

|    1005 | 黄淮      |        3000 |

|    1006 | 胡歌      |        5000 |

|    1007 | 张浩      |        6500 |

|    1008 | 李二狗    |        4500 |

|    1009 | 郑中基    |        5620 |

|    1011 | 王二小    |        3600 |

+---------+-----------+--------------+

10 rows in set (0.00 sec)

mysql>

innodb间隙锁

话不多说上代码—

#session1中,首先在huixin表中插入一列num,并设置为普通索引,(已经设置好了)这里不演示操作可自行操作

mysql> select * from huixin where user_id>1003 for update ;

+---------+-----------+--------------+-----+

| user_id | user_name | basic_salary | num |

+---------+-----------+--------------+-----+

|    1005 | ceshi    |        4556 |  5 |

|    1006 | test      |        4455 |  5 |

+---------+-----------+--------------+-----+

2 rows in set (0.00 sec)

mysql> start transaction ;

Query OK, 0 rows affected (0.00 sec)

mysql> begin ;

Query OK, 0 rows affected (0.00 sec)

mysql> select * from huixin where num = 3 for update ;

+---------+-----------+--------------+-----+

| user_id | user_name | basic_salary | num |

+---------+-----------+--------------+-----+

|    1003 | sdsad    |        4242 |  3 |

+---------+-----------+--------------+-----+

1 row in set (0.00 sec)

mysql>

  

#在session2进行操作

mysql> use company

Database changed

mysql> start transaction ;

Query OK, 0 rows affected (0.00 sec)

mysql> begin ;

Query OK, 0 rows affected (0.00 sec)

mysql> insert into huixin values (1234 ,'test',7878,2);

ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

mysql> insert into huixin values (1234 ,'test',7878,4);

ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

mysql> insert into huixin values (1234 ,'test',7878,5);

Query OK, 1 row affected (0.00 sec)

 

我们可以发现在(1,3]和[3,5)之间上了间隙锁这也是为啥插不进去数据的原因。

总结–设计表的时候尽量避免间隙锁的产生,间隙锁属于行锁的范畴,如果num不属于索引,那么此锁就属于表所的范畴,在huixin表中插入任何数据都会出现阻塞。

死锁

死锁的产生是多个session去操作同一行数据,同时该行数据又被加锁了,演示代码如下–

#session1中

mysql> set@@autocommit =0; #关闭自动提交

Query OK, 0 rows affected (0.00 sec)

mysql> show variables like '%isolation%'; #查看隔离级别

+-----------------------+-----------------+

| Variable_name        | Value          |

+-----------------------+-----------------+

| transaction_isolation | REPEATABLE-READ |

+-----------------------+-----------------+

1 row in set, 1 warning (0.00 sec)

mysql> start transaction ;

Query OK, 0 rows affected (0.00 sec)

mysql> begin ;

Query OK, 0 rows affected (0.00 sec)

mysql> select * from huixin where user_id =1111 for update ;

Empty set (0.00 sec)

mysql> insert into huixin values(1111,'ceshi',5200,100);

Query OK, 1 row affected (32.79 sec)

  

#session2中

mysql> set @@autocommit =0;

Query OK, 0 rows affected (0.00 sec)

mysql> start transaction ;

Query OK, 0 rows affected (0.00 sec)

mysql> begin ;

Query OK, 0 rows affected (0.00 sec)

mysql> select * from huixin where user_id=1111 for update ;

Empty set (0.00 sec)

mysql> insert into huixin values (1111,'ceshi',5200,100);

ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

mysql>

   

可以发现多个session同时操作一行数据(必须是索引才是行锁的范畴),会产生死锁。

死锁的解决方案

首先要解决死锁问题,在程序的设计上,当发现程序有高并发的访问某一个表时,尽量对该表的执行操作串行化,或者锁升级,一次性获取所有的锁资源。

然后也可以设置参数innodb_lock_wait_timeout,超时时间,并且将参数innodb_deadlock_detect 打开,当发现死锁的时候,自动回滚其中的某一个事务。

***总结– ***

    innodb存储引擎实现了行级锁,在锁定范围上更小,更精细,能实现更为复杂的操作但是所带来的性能损耗也会比表级锁很大,但是行锁在并发性能上的优势很明显。

    当并发量很大的时候,innodb的整体性能要比myisam高,所以在使用何种引擎的时候要考虑锁的问题,在选择锁的时候应该考虑并发量是不是很大,合理使用锁,以下是使用行锁的建议—

    尽量控制事务的大小,减少锁定资源量和锁定时长;

    数据检索最好是通过索引,因为时间快性能较好(当然数据量小也会进行全表扫描),同时也可以避免升级成表锁–合理使用索引可以更好的提高性能;

    尽可能减少在数据范围内的检索条件比如 检索条件 <50 要比 <100要好(这要求我们大致推算出数据的位置),避免间隙锁带来的负面影响而不能操作一些数据;

    在合适的情况下尽量使隔离级别较小,同时最好是一次锁定所有的所需操作的资源;

不断学习共同进步!

————————————————

版权声明:本文为CSDN博主「Gavin_Lim」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/weixin_54061333/article/details/117840883

你可能感兴趣的:(mysql中各种锁详解)