之前总是听说 MySQL 中的锁,一直没有具体的看过,晚上抽了空看了一点就先记录了下来,后面有机会在补充。
为什么需要锁
因为数据库是一个多用户共享的资源,也就是任何人都可以访问数据。当有一个商品表库存为1,当一个用户购买该商品的时候,首先需要查询商品的库存。如果一个人请求是没有任何问题的。但是当两个用户同时购买该商品的时候由于时间比较接近,当第一个用户查询完商品还未进行更新库存的时候,第二个用户就进来了,由于库存还没有更新,所以该用户也可以下单成功。最终将导致库存为-1,破幻了数据的一致性。为了解决这问题才有个锁的概念。
有哪些锁
- 锁的类型
- 读锁
- 写锁
- 锁的粒度
- 行锁
- 表锁
- 持有锁的时间
- 临时锁
- 持续锁
隔离级别
说到锁那么接下来就需要了解隔离级别,了解隔离级别就需要了解数据库的四要素。
数据库四要素(ACID)
- 原子性(Atomicity):要么全部完成,要么全 不 完成。
- 一致性(Consistency):一个事务单元提交后才可以被其他事务查看到。
- 隔离性(Isolation):并发事务之间不相互影响。设立了不同的隔离级别,通过适度破坏一致性来提高性能
- 持久性(Durability):事物提交之后即持久化到磁盘中不会丢失。
锁只是实现隔离级别的几种方式之一,除了锁,实现并发问题的方式还有时间戳,多版本控制等等,这些也可以称为无锁的并发控制。
四种隔离级别
select @@tx_isolation; #查询当前隔离级别
select @@global.tx_isolation; #查询全局隔离级别
set transaction isolation level read uncommitted; #设置当前隔离级别为 RU
set transaction isolation level read committed; #设置当前隔离级别为 RC
set transaction isolation level repeatable read; #设置当前隔离级别为 RR
set transaction isolation level serializable; #设置当前隔离级别为 RS
- 读未提交(Read Uncommitted):事务的读不阻塞其他事务的读和写,事务的写只阻塞其他事务的写,但不阻塞读。通过对写操作加“持续 X 锁”,对读操作不加锁实现。
- 读已提交(Read Committed):事务的读不阻塞其他事务的读和写,事务的写也会阻塞其他事务的读和写。通过对写操作加“持续 X 锁”,对读操作加“临时 S 锁”实现。
- 可重复读(Repeatable Read):事务的读会阻塞其他事务的写,但不阻塞读。事务的写会阻塞其他事务的读和写。通过对写操作加“持续 X 锁”,对读操作加“持续 S 锁”实现。
- 序列化(Serializable):最安全的一种级别,不会出现脏读、不可重复读、幻读问题。但是可能会导致大量的超时现象和锁竞争。使用的是表级锁
可能产生的问题
脏读
公司发工资,打了10000万到我的账户,但是该事务没有提交。这时我正好查看账户,发现10000元已经到账,非常开心。可以公司这时又发现发错了,于是回滚事务,修改金额为5000,然后提交事务。最终我实际得到的工资只有5000,空欢喜一场。
这就是脏读,两个并发的事务,其中一个读取到了另一个没有提交的数据。
不可重复读
拿着刚发的工资去买本书看看,付钱时系统读到我卡里有5000元,然而此时女友也拿着我的卡在消费,并花完了卡里的钱,并在我之前提交了事务,当我这边进行扣款的时候系统发现卡里没有钱了,提示扣款失败。我就十分纳闷,明明有钱啊,怎么。。。
这就是不可重复读,两个并发事务,在一个事务中读取到另一个事务中提交了的结果,第一事务在第二个事务提交前和提交后查看到的结果不一致。(主要是针对更新和删除操作)当隔离级别设置为Read committed时,避免了脏读,但是可能会造成不可重复读。
幻读
我又一个计划任务表,当前为空的。在第一个事务中我查看所有任务显示为空。这时女友在另一个事务增加了一条任务“早上起床做早饭”,并提交了事务。这是我又查看了一下所有任务发现还是空的,我就准备加一个任务“早上起床做早饭”,可是这时却提示我错误。我就纳了闷了,明明没有数据啊,怎么不让我加呢?难道出现了幻觉?
这就是幻读。第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。(主要针对新增操作)