mysql数据库3—数据库锁
1.锁的概念
在现实生活中是为我们想要隐藏于外界所使用的一种工具,在计算机中,是协调多个进程或线程并发访问某一资源的
一种机制,在数据库当中,除了传统的计算资源(CPU、RAM、I/O等等)的争用之外,数据也是一种供许多用户共享
访问的资源,如何保证数据并发访问的一致性、有效性,是所有数据库必须解决的一个问题,锁的冲突也是影响数据
库并发访问性能的一个重要因素。锁对数据库而言显得尤为重要。
在购买商品时, 商品库存只有1个时 ,两个人同时买时, 谁买到的问题,会用到事务, 先从库存表中取出物品的数据, 然后
插入订单,付款后,插入付款表信息。更新商品的数量, 在这个过程中, 使用锁可以对有限的资源进行保护,实现隔离和并
发的矛盾
2.锁的分类
1.按操作分
读锁(共享锁):针对同一份数据,多个读取操作可以同时进行而不互相影响
写锁(排它锁):当前写操作没有完成前,会阻断其他写锁和读锁
2.按粒度分
(1) 表锁
(2)行锁
(3)页锁
PS:不同的存储引擎支持不同的锁机制。根据不同的存储引擎,MySQL中锁的特性分为以下
存储引擎 | 行锁 | 表锁 | 页锁 |
MyISAM | 支持 | ||
InnoDB | 支持 | 支持 | |
BDB | 支持 | 支持 |
3.表锁
1.概念
偏向MyISAM存储引擎,开销小, 加锁快, 无死锁,锁定粒度大, 发生锁冲突的概率最高, 并发最底
PS:表锁(MyISAM存储引擎), 在使用SELECT查询语句前, 会自动给涉及的所有表加读锁, 查询操作完成之后会释放锁
由于读锁的添加和释放由MySQL自动完成, 且无法具体捕获加锁和释放锁的时机, 因此需要手动通过命令加锁和释放锁来得到效果。
2.涉及sql命令
(1)查看所有数据库中正在打开的非临时表
-- 查询全部表
show open tables;
-- 查询是否锁表
show OPEN TABLES where In_use > 0;
-- 查询指定数据库指定表的是否打开(使用或锁住)
show open tables from 数据名 where `Table` = '表名';
查询结果:
查询结果列的意思:
In_use: 打开表的表锁的次数, 0 代表未打开, 1 代表 表已锁住
Name_locked; 表名是否被锁, 0 代表未锁, 只有当删除表时或者重命名表名时, 才为1
(2)给表加锁
# 手动为表加上读锁
lock table 表名1 read;
# 手动为表加上写锁
lock table 表名1 write;
(3)释放所有的锁
unlock tables;
3.表锁读锁对操作和性能产生哪些影响
(1)对表"user"进行添加读锁
lock table user read;(共享锁)
当前连接(会话)可以进行的操作:
问题 | 解答 |
是否可以查看自己 | 可以 |
是否可以更新数据or插入数据 | 不可以 |
能不能读别的表 | 不可以,当前表还没有解锁,不能放下当前, 操作别的内容 |
另一个连接(会话)可以进行的操作:
问题 | 解答 |
是否可以查看自己 | 可以 |
是否可以更新数据or插入数据 | 处理阻塞状态,等待解锁后, 才能进行更新 |
能不能读别的表 | 可以 |
4.表锁写锁对操作和性能产生哪些影响
(1)对表"user"进行添加写锁
lock table user write;(排它锁)
当前连接(会话)可以进行的操作:
问题 | 解答 |
能否读自己锁过的表 | 可以 |
能否改自己锁过的表 | 可以 |
能否读取别的表 | 不可以 |
另一个连接(会话)可以进行的操作:
问题 | 解答 |
能否操作没有加过锁的表 | 可以 |
能否对被锁过的表进行操作 | 处理阻塞状态,等待解锁后, 才能进行操作 |
总结:
表读锁(共享锁)只会阻塞其他线程(包括自己)对该表写操作, 不会堵塞其他线程对该表读操作;
表写锁(排它锁)则会把其他线程对该表的读操作和写操作都堵塞
4.行锁
1.概念
行锁是InnoDB引擎实现的,同样分为两种类型,共享锁和排他锁。为了让行级锁定和表级锁定共存,InnoDB也同样使用了意向锁(表级锁定)的概念,也就有了意向共享锁和意向排他锁这两种。
PS:
行锁是通过索引上的索引项来实现的,InnoDB这种行锁实现特点意味者:只有通过索引条件检索数据,InnoDB才会使用行级锁,否则,InnoDB将使用表锁!(索引条件比如是主键索引、唯一索引或普通索引)
2.行锁的排他锁
在查询之后添加for update,其它操作会被阻塞,直到锁定的行提交commit;
select * from 表 WHERE id = xxx for update
PS:
select for update 的使用场景,为了避免自己看到的数据并不是数据库存储的最新数据并且看到的数据只能由自己修改,需要用 for update 来限制
3.行锁的共享锁
查找到的数据加上一个 share 锁,这个就是表示其他的事务只能对这些数据进行简单的select 操作,并不能够进行 DML 操作。
select * from 表 WHERE id = xxx lock in share mode
PS:
为了确保自己查到的数据没有被其他的事务正在修改,也就是说确保查到的数据是最新的数据,并且不允许其他人来修改数据。但是自己不一定能够修改数据,因为有可能其他的事务也对这些数据 使用了 in share mode 的方式上了 S 锁。
总结:for update 和 lock in share mode 的区别:前一个上的是排他锁(X 锁),一旦一个事务获取了这个锁,其他的事务是没法在这些数据上执行 for update ;后一个是共享锁,多个事务可以同时的对相同数据执行 lock in share mode。
4.查看行锁的使用信息
show status like 'innodb_row_lock%';
查询结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eNnvpGUl-1640272480992)(E:\学习资料\mysql\assets\image-20211223151959164.png)]
对于各个状态说明如下:
Innodb_row_lock_current_waits:当前正在等待锁的数量;
Innodb_row_lock_time:从系统启动到现在锁定总时间长度;
Innodb_row_lock_time_avg:每次等待所花平均时间;
Innodb_row_lock_time_max:从系统启动到现在等待最长的一次所花的时间长度;
Innodb_row_lock_waits:系统启动到现在总共等待的次数;
5.悲观锁
1.概念
每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)
PS:悲观锁并不是某个具体的“锁”,而是一种并发编程的基本概念,是根据看待并发同步的角度。
2.使用方式
1.通过行锁的排他锁来实现悲观锁。
-- 获取数据的时候加锁获取
select * from table_xxx where id='xxx' for update;
注意:id字段一定是主键或者唯一索引,不然是锁表(id必须有索引条件才能用到行锁)
2.通过 Lock 接口实现悲观锁
lock() 等方法就是执行加锁,而 unlock() 方法是执行解锁。处理资源之前必须要先加锁并拿到锁,等到处理完了之后再解开锁,这就是非常典型的悲观锁思想。
3.通过synchronize实现悲观锁
synchronize也是一种悲观锁的表现
6.乐观锁
1.概念
每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新数据的时候需要判断该数据是否被别人修改过。如果数据被其他线程修改,则不进行数据更新,如果数据没有被其他线程修改,则进行数据更新。由于数据没有进行加锁,期间该数据可以被其他线程进行读写操作。
2.使用方式
1.通过版本号方式
就是给数据增加一个版本标识,在数据库上就是表中增加一个version字段每次更新把这个字段加1,
读取数据的时候把version读出来,更新的时候比较version。如果还是开始读取的version就可以更新了,如果现在的version比老的version大,说明有其他事务更新了该数据,并增加了版本号,这时候得到一个无法更新的通知,用户自行根据这个通知来决定怎么处理,比如重新开始一遍。
示例:
-- 查询出当前要被修改的数据版本号
select version from user where id =3;
-- 修改的时候,校验版本号是否一致
update user set name ='小林' ,version = version + 1 where version = 1
7.死锁
死锁:是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去.此时称系统处于死锁状态或系统产生了死锁。
PS:
死锁的关键在于:两个或以上的Session加锁的顺序不一致。那么对应的解决死锁问题的关键就是:让不同的session加锁有次序