week8 day3 数据库锁机制

一、数据库的锁机制

1.1 什么是锁?为什么要加入锁机制?

锁是计算机协调多个进程或者多个线程并发访问同一资源的机制,那为何要加入锁机制呢?

因为在数据库中,除了传统的计算资源(如CPU、RAM、I/O等)的争用以外,数据也是一种供需要用户共享的资源。

当并发事务同时访问一个共享的资源时,有可能导致数据不一致、数据无效等问题,例如我们在数据库的读现象中介绍过,在并发访问情况下,可能会出现脏读、不可重复读和幻读等读现象。为了应对这些问题,主流数据库都提供了锁机制,以及事务隔离级别的概念,而锁机制可以将并发的数据访问顺序化,以保证数据库中数据的一致性与有效性。

此外,锁冲突也是影响数据库并发访问性能的一个重要因素,锁对数据库而言显得尤其重要,也更加复杂。

1.2 并发控制

在计算机科学,特别是程序设计、操作系统、多处理机制和数据库等领域,并发控制(concurrency control)是确保及时纠正由并发操作导致的错误的一种机制。

数据库管理系统(DBMS)中的并发控制的任务时确保在多个事务同时操作数据库中的数据时,保证事务的隔离性、统一性以及数据库的统一性。

下面举例说明并发操作带来的数据不一致性问题:

现有两处火车票售票点,同时读取某一趟列车车票数据库中车票余额为 X。两处售票点同时卖出一张车票,同时修改余额为 X -1写回数据库,这样就造成了实际卖出两张火车票而数据库中的记录却只少了一张。 产生这种情况的原因是因为两个事务读入同一数据并同时修改,其中一个事务提交的结果破坏了另一个事务提交的结果,导致其数据的修改被丢失,破坏了事务的隔离性。并发控制要解决的就是这类问题。

封锁、时间戳、乐观并发控制(乐观锁)和悲观并发控制(悲观锁)是并发控制主要采用的技术手段。

二、锁的分类

锁的分类(oracle)

  1. 按锁的粒度分:行级锁、表级锁、页级锁(MySQL支持)。
  2. 按锁的级别分,可分为共享锁、排他锁。
  3. 按使用方式分,可分为乐观锁、悲观锁。
  4. 按加锁方式分,可分为自动锁、显式锁。
  5. 按操作分,可分为DML锁、DDL锁。

DML锁(data locks,数据锁),用于保护数据的完整性,其中包括行级锁(Row Locks (TX锁))、表级锁(table lock(TM锁))。
DDL锁(dictionary locks,数据字典锁),用于保护数据库对象的结构,如表、索引等的结构定义。其中包排他DDL锁(Exclusive DDL lock)、共享DDL锁(Share DDL lock)、可中断解析锁(Breakable parse locks)

在这里插入图片描述

三、MySQL中的行级锁、表级锁、页级锁

在DBMS中,可以按照锁的粒度分为行级锁(innodb从存储引擎)、表级锁(myisam引擎)、页级锁(bdb引擎)。

3.1 行级锁

行级锁是MySQL中锁定粒度最细的一种锁,表示只针对当前操作的行进行加锁。行级锁能大大减少数据库操作的冲突。其加锁粒度最小,但加锁开销也最大。行级锁分为共享锁和排他锁。

  • 特点:开销大、加锁慢;会出现死锁 ;锁的粒度小,发生锁冲突的概率低,并发度也最高
  • 支持引擎:innodb引擎
  • 行级锁定分为共享锁和排他锁
# 共享锁:
select * from table_name where ... lock in share mode;
# 排他锁:
select * from table_name where ... for update;

3.2 表级锁(倾向于读)

表级锁是MySQL中锁定粒度最大的一种锁,表示对当前操作的整张表加锁,它实现简单,资源消耗较少,被大部分MySQL引擎支持。最常使用的MYISAM与INNODB都支持表级锁定。表级锁定分为表共享读锁(共享锁)与表独占写锁(排他锁)。

  • 特点:开销小、加锁快;不会出现死锁;锁的粒度大,发出锁冲突的概率最高,并发度低。
  • 支持引擎:MyISAM、MEMORY、InNoDB
  • 分类:表级锁定分为表共享读锁(共享锁)与表独占写锁(排他锁),如下所示
lock table 表名 read(write),表名 read(write).....;
//给表加读锁或者写锁,例如
mysql> lock table employee write;
Query OK, 0 rows affected (0.00 sec)
mysql> show open tables where in_use>= 1;
+----------+----------+--------+-------------+
| Database | Table    | In_use | Name_locked |
+----------+----------+--------+-------------+
| ttt      | employee |      1 |           0 |
+----------+----------+--------+-------------+
1 row in set (0.00 sec)
mysql> unlock tables; -- UNLOCK TABLES释放被当前会话持有的任何锁
Query OK, 0 rows affected (0.00 sec)
 
mysql> show open tables where in_use>= 1;
Empty set (0.00 sec)
 
mysql>

3.3 页级锁

页级锁是MySQL中锁定粒度介于行级锁和表级锁中间的一种锁。表级锁速度快,但冲突多,行级冲突少,但速度慢。所以取了折中的页级,一次锁定相邻的一组记录。BDB支持页级锁。

  • 特点:开锁成本和时间介于表锁和行锁之间;会出现死锁;锁定粒度介于表锁和行锁之间,并发度一般。

四、行级锁之共享锁与排他锁(级别)

行级锁分为共享锁排他锁

与行处理有关的SQL操作有:insert、update、delete、select,这四类SQL在操作记录时,可以为行加上锁,但需要知道的是:

  1. 对于insert、delete、update来说,innodb会自动给涉及的数据加锁,而且是排他锁(X)
  2. 对于普通的select语句,innodb不会加任何锁,需要我们自己手动加,可以加两种类型的锁(共享锁和排他锁),如下所示
    # 共享锁
    select * from table_name lock in share mode;
    # 排他锁
    select * from table_name for update;
    
# 准备数据
create table employee(
id int primary key auto_increment,
name varchar(20) not null,
age int(3) unsigned not null default 20
);

insert into employee(name) values
('egon'),
('alex'),
('wupeiqi'),
('yuanhao'),
('liwenzhou'),
('jingliyang'),
('jinxin'),
('成龙'),
('歪歪'),
('丫丫'),
('丁丁'),
('星星'),
('格格'),
('张野'),
('程咬金'),
('程咬银'),
('程咬铜'),
('程咬铁')
;
update employee set age = 18 where id <=3;

实验:验证insert、update、delete是默认加排他锁的

需要知道的是,针对事务,mysql是隐式开启、隐式提交,即mysql默认会把每条sql语句都放入一个事务中,并在该条sql语句执行完毕后自动提交,所以如果我们如果开启两个session,在一个session中直接运行update,很快就运行完毕并自动提交,而提交事务或回滚事务都会释放锁,这样话就无法验证效果了,所以我们采用手动开启事务方式进行验证,如此我们就可以自己控制事务的提交了(如果忘记了该知识点,请点击这里)
在这里插入图片描述

4.1 共享锁(share lock)

共享锁又称为读锁,简称S锁,顾名思义,共享锁就是多个事务对于同一数据可以共享一把锁,获得共享锁的事务只能读数据,不能修改数据直到已释放所有共享锁,所以共享锁可以支持并发读(参考下述实验三)。

如果事务T对数据A加上共享锁后,则其他事务只能对A再加共享锁或不加锁在其他事务里一定不能再加排他锁,但是在事务T里面是可以再加的),反之亦然。

用法:

select ... lock in share mode;

在查询语句后面增加LOCK IN SHARE MODE,Mysql会对查询结果中的每行都加共享锁,当没有其他线程对查询结果集中的任何一行使用排他锁时,可以成功申请共享锁,否则会被阻塞。其他线程也可以读取使用了共享锁的表,而且这些线程读取的是同一个版本的数据。

4.2 排他锁(eXclusive lock)

排他锁又称为写锁,简称X锁,顾名思义,排他锁就是不能与其他锁并存,如一个事务获取了一个数据行的排他锁,其他事务就不能再对改行加任何类型的其他锁(共享锁和排他锁),但是获取排他锁的额事务是可以对数据进行读取和修改操作。
用法:

select * from table_name where ... for update;

在查询语句后面增加for update,MySQL会对查询结果中的每行都加排他锁,当没有其他线程对查询结果集中的任何一行使用排他锁时,可以成功申请排他锁,否则会被阻塞。

特例:加过排他锁的数据行在其他事务中是不能修改数据的,也不能通过for updatelock in share mode锁的方式查询数据,但可以直接通过select...from...查询数据,因为普通select查询没有任何锁机制

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4.3 意向锁(了解)

意向锁是表级锁,其设计目的主要是为了在一个事务中揭示下一行将要被请求锁的类型。

意向锁的作用就是当一个事务在需要获取资源锁定的时候,如果遇到自己需要的资源已经被排他锁占用的时候,该事务可以需要锁定行的表上面添加一个合适的意向锁。

如果自己需要一个共享锁,那么就在表上面添加一个意向共享锁。而如果自己需要的是某行(或者某些行)上面添加一个排他锁的话,则先在表上面添加一个意向排他锁

InnoDB中有两个意向锁(表锁):

(1)意向共享锁(IS):事务打算给数据行共享锁;,事务在给一个数据行加共享锁前必须先取得该表的IS锁
(2)意向排他锁(IX)事务打算给数据行加排他锁;事务在给一个数据行加排他锁前必须先取得该表的IX锁

意向锁是InnoDB自动加的,不需要用户干预。

五、innodb存储引擎的锁机制

mysql常用存储引擎的锁机制

MyISAM和MEMORY采用表级锁(table-level locking)

BDB采用页面锁(page-level locking)或表级锁,默认为页面锁

InnoDB支持行级锁(row-level locking)和表级锁,默认为行级锁(偏向于写)

InnoDB的锁定模式实际上可以分为四种:共享锁(S),排他锁(X),意向共享锁(IS)和意向排他锁(IX),我们可以通过以下表格来总结上面这四种所的共存逻辑关系:
在这里插入图片描述
如果一个事务请求的锁模式与当前的锁兼容,InnoDB就将请求的锁授予该事务;反之,如果两者不兼容,该事务就要等待锁释放。

5.1 行级锁与表级锁的使用区分

mysiam操作数据都是使用表级锁,mysiam总是一次性获得所需的全部锁,要么全部满足,要么全部等待。所以不会产生死锁,但是由于每操作一条记录就要锁定整个表,导致性能较低,并发不高。

**InnoDB 与 MyISAM 的最大不同有两点:一是 InnoDB 支持事务;二是 InnoDB 采用了行级锁。**也就是你需要修改哪行,就可以只锁定哪行。

在Mysql中,行级锁并不是直接锁记录,而是锁索引。InnoDB 行锁是通过给索引项加锁实现的,而索引分为主键索引和非主键索引两种

  1. 如果一条SQL语句操作了主键索引,MySQL就会锁定这条语句命中的主键索引(或者聚集索引)
  2. 如果一条SQL语句操作了非主键索引(或者辅助索引),MySQL会先锁定该非主键索引,再锁定相关的主键索引
  3. 如果没有索引,innodb就会通过隐藏的聚集索引来对记录加锁。也就是说:如果不通过索引条件检索数据,那么innodb将对表中所有数据加锁,实际效果跟表级锁一样

在实际应用中,要特别注意InnoDB行锁的这一特性,不然的话,可能导致大量的锁冲突,从而影响并发性能。

  1. 在不通过索引条件查询的时候,innodb的效果就相当于表锁
  2. 当表有多个索引的时候,不同的事务可以使用不同的索引锁定不同的行,另外,不论是使用主键索引、唯一索引或普通索引,innodbi都会使用行锁来对数据加锁。
  3. 由于MySQL的行锁是针对索引加的锁,并不是针对记录加的锁,所以即使你的SQL语句访问的是不同的记录行,但如果命中的是相同的被锁住的索引键,也还是会出现锁冲突的。
  4. 即便在条件中使用了索引字段,但是否使用索引数据是由MySQL通过判断不同的执行计划来决定的,如果MySQL认为全表扫的效率更高,比如对一些很小的表,它就不会使用索引,这种情况下innodb将锁住所有行,相当于表锁。因此,在分析锁冲突的时候,别忘了检查SQL的执行计划,以确认是否真正使用了索引,如下案例:
create table employee(
id int primary key auto_increment,
name varchar(20) not null,
age int(3) unsigned not null default 20
);

insert into employee(name) values
('egon'),
('alex'),
('wupeiqi'),
('yuanhao'),
('liwenzhou'),
('jingliyang'),
('jinxin'),
('成龙'),
('歪歪'),
('丫丫'),
('丁丁'),
('星星'),
('格格'),
('张野'),
('程咬金'),
('程咬银'),
('程咬铜'),
('程咬铁')
;
update employee set age = 16 where id=1;
update employee set age = 18 where id in (2,3,4);

步骤一:创建索引

mysql> explain select * from employee where age = 20;  -- age字段没有索引的情况下的查询计划,条件为age = 20

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

| id | select_type | table    | type | possible_keys | key  | key_len | ref  | rows | Extra       |

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

|  1 | SIMPLE      | employee | ALL  | NULL          | NULL | NULL    | NULL |   18 | Using where |

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

1 row in set (0.00 sec)

mysql> explain select * from employee where age = 18;  -- age字段没有索引的情况下的查询计划,条件为age = 18

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

| id | select_type | table    | type | possible_keys | key  | key_len | ref  | rows | Extra       |

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

|  1 | SIMPLE      | employee | ALL  | NULL          | NULL | NULL    | NULL |   18 | Using where |

+----+-------------+----------+------+---------------+------+---------+------+------+-------------+
 
mysql> create index xxx on employee(age);  -- 为age字段添加索引

Query OK, 0 rows affected (0.02 sec)

Records: 0  Duplicates: 0  Warnings: 0

 
mysql> explain select * from employee where age = 20;  -- age字段没有索引的情况下的查询计划,条件为age = 20

此时 满足age=20的行太多,即便是为age字段加了索引也是无法命中的,看下面的explain计划,key字段为NULL,证明虽然建立了索引,但压根没用上 
+----+-------------+----------+------+---------------+------+---------+------+------+-------------+

| id | select_type | table    | type | possible_keys | key  | key_len | ref  | rows | Extra       |

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

|  1 | SIMPLE      | employee | ALL  | xxx           | NULL | NULL    | NULL |   18 | Using where |

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

mysql> explain select * from employee where age = 18;  -- -- age字段没有索引的情况下的查询计划,条件为age = 18

查看计划,key字段为xxx,命中了索引,因为age=18的行总共才3行,其实我们通常就应该给那些区分度高的字段加索引,否则加了也是白加,跟没加一个鸟样
+----+-------------+----------+------+---------------+------+---------+-------+------+-------+

| id | select_type | table    | type | possible_keys | key  | key_len | ref   | rows | Extra |

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

|  1 | SIMPLE      | employee | ref  | xxx           | xxx  | 4       | const |    3 | NULL  |

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

步骤二:验证未命中索引则锁表
在这里插入图片描述
步骤三:验证命中索引则锁行

在这里插入图片描述

5.2 三种行锁的算法

innodb有三种行锁的算法,都属于排他锁:

  1. record lock:单个记录上的锁

  2. gap lock:间隙锁,锁定一个范围,但不包括记录本身

    当我们使用范围条件而不是相等条件检索数据,并请求共享或者排他锁的时候, innodb会给符合条件的已有数据的索引项加锁;
    对于键值在条件范围内但并不存在的记录,叫做“”间隙(gap),innodb也会对这个间隙加锁,这种锁就是所谓的间隙锁(next-key锁)
    例如employee表中只有101条记录,其depid的值分别是1,2,…,100,101,
    select * from table_name where id>100 for update,是一个范围查找,并且命中了索引,InnoDB不仅会对符合条件的empid值为101的记录加锁,也会对empid大于101(这些记录并不存在)的“间隙”加锁。

  3. next-key lock:等于record lock结合gap lock,也就是说next-key lock既锁定记录本身也锁定一个范围,特比需要注意的是,innodb存储引擎还会对辅助索引下一个键值加上gap lock

对于行查询,innodb采用的都是next-key lock,主要目的是解决幻读问题,以满足相关隔离级别以及恢复和复制的需要。

具体案例点击链接:练习

5.3 死锁问题

MyISAM中是不会产生死锁的,因为MyISAM总是一次性获得所需的全部锁,要么全部满足,要么全部等待。而在InnoDB中,锁是逐步获得的,就造成了死锁的可能。

下面,来看看两个死锁的例子 (一个是两个Session的两条SQL产生死锁;另一个是两个Session的一条SQL,产生死锁):

在这里插入图片描述
在这里插入图片描述
上面的两个死锁用例。第一个非常好理解,也是最常见的死锁,每个事务执行两条SQL,分别持有了一把锁,然后加另一把锁,产生死锁。

第二个用例,只有多个事务同时运行的情况下才可能出现,但隐蔽性极强,虽然每个Session都只有一条语句,仍旧会产生死锁。要分析这个死锁,首先必须用到本文前面提到的MySQL加锁的规则。针对Session 1,从name索引出发,读到的[hdc, 1],[hdc, 6]均满足条件,不仅会加name索引上的记录X锁,而且会加聚簇索引上的记录X锁,加锁顺序为先[1,hdc,100],后[6,hdc,10]。而Session 2,从pubtime索引出发,[10,6],[100,1]均满足过滤条件,同样也会加聚簇索引上的记录X锁,加锁顺序为[6,hdc,10],后[1,hdc,100]。发现没有,跟Session 1的加锁顺序正好相反,如果两个Session恰好都持有了第一把锁,请求加第二把锁,死锁就发生了。

结论:

  1. 关于死锁问题需要储备的知识
    在MySQL中,行级锁并不是直接锁记录,而是锁索引。索引分为主键索引和辅助索引两种,如果一条SQL语句操作了主键索引,MySQL就会锁定这条主键索引;如果一条SQL语句操作了非主键索引,MySQL就会锁定非主键索引,再锁定主键索引。在范围操作且添加共享锁或者排他锁时,MySQL不仅锁定where条件扫描过的所有索引记录,而且会锁定相邻的键值,即所谓的next-key lock。
  2. 死锁发生的本质原理
    死锁发生与否,并不在于事务中有所少条SQL语句,死锁的关键在于:两个(或以上)的session加锁的顺序不一致。而使用本文上面提到的,分析MySQL每条SQL语句的加锁规则,分析出每条SQL语句的加锁顺序,然后检查多个并发SQL间是否存在以相反顺序加锁的情况,就可以分析出各种潜在的死锁情况,也可以分析出线上发生死锁的原因。

发生死锁后,innodb一般都可以检测到,并使一个事务释放锁退回,另一个获取锁完成事务。

有多种方式可以避免死锁,

  1. 如果不同程序会并发地存取多个表,尽量约定以相同的顺序访问表,可以大大降低死锁机会。
  2. 在同一个事务中,尽可能做到一次锁定所需要的所有资源,减少死锁产生概率。
  3. 对于非常容易产生死锁的业务部分,可以尝试使用升级锁定颗粒度,通过表级锁定来减少死锁产生的概率。
  4. 在程序以批量方式处理数据的时候,如果事先对数据排序,保证每个线程按固定的顺序来处理记录,也可以大大降低出现死锁的概率。

5.4 什么时候使用表锁

绝大部分情况使用行锁,但在个别特殊事务中,也可以考虑使用表锁

  1. 事务需要更新大部分数据,表又较大
    若使用默认的行锁,不仅该事务执行效率低(因为需要对较多行加锁,加锁是需要耗时的); 而且可能造成其他事务长时间锁等待和锁冲突; 这种情况下可以考虑使用表锁来提高该事务的执行速度
  2. 事务涉及多个表,较复杂,很可能引起死锁,造成大量事务回滚
    这种情况也可以考虑一次性锁定事务涉及的表,从而避免死锁、减少数据库因事务回滚带来的开销当然,应用中这两种事务不能太多,否则,就应该考虑使用MyISAM。

5.5 行锁优化建议

通过检查InnoDB_row_lock状态变量来分析系统上的行锁的争夺情况,在着手根据状态量来分析改善;

show status like ‘innodb_row_lock%’;//查看行锁的状态

  • 尽可能让所有数据检索都通过索引来完成, 从而避免无索引行锁升级为表锁
  • 合理设计索引,尽量缩小锁的范围
  • 尽可能减少检索条件,避免间隙锁
  • 尽量控制事务大小,减少锁定资源量和时间长度
  • 尽可能低级别事务隔离,详见下一章节

六、乐观锁与悲观锁(使用方式)

数据库管理系统(DBMS)中的并发控制的任务是确保在多个事务同时存取数据库中同一数据时不破坏事务的隔离性和统一性以及数据库的统一性。

乐观并发控制(乐观锁)和悲观并发控制(悲观锁)是并发控制主要采用的技术手段。

无论是悲观锁还是乐观锁,都是人们定义出来的概念,可以认为是一种思想。其实不仅仅是关系型数据库系统中有乐观锁和悲观锁的概念,像memcache、hibernate、tair等都有类似的概念。

针对于不同的业务场景,应该选用不同的并发控制方式。所以,不要把乐观并发控制和悲观并发控制狭义的理解为DBMS中的概念,更不要把他们和数据中提供的锁机制(行锁、表锁、排他锁、共享锁)混为一谈。其实,在DBMS中,悲观锁正是利用数据库本身提供的锁机制来实现的。

下面来分别学习一下悲观锁和乐观锁。

6.1 悲观锁

在关系数据库管理系统里,悲观并发控制(又名“悲观锁”,Pessimistic Concurrency Control,缩写“PCC”)是一种并发控制的方法。它可以阻止一个事务以影响其他用户的方式来修改数据。如果一个事务执行的操作都某行数据应用了锁,那只有当这个事务把锁释放,其他事务才能够执行与该锁冲突的操作。

悲观并发控制主要用于数据争用激烈的环境,以及发生并发冲突时使用锁保护数据的成本要低于回滚事务的成本的环境中。

悲观锁,正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度(悲观),因此,在整个数据处理过程中,将数据处于锁定状态。 悲观锁的实现,往往依靠数据库提供的锁机制 (也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据),现在互联网高并发的架构中,受到fail-fast思路的影响,悲观锁已经非常少见了。

在数据库中,悲观锁的流程如下:

在对任意记录进行修改前,先尝试为该记录加上排他锁(exclusive locking)。

如果加锁失败,说明该记录正在被修改,那么当前查询可能要等待或者抛出异常。 具体响应方式由开发者根据实际需要决定。

如果成功加锁,那么就可以对记录做修改,事务完成后就会解锁了。

其间如果有其他对该记录做修改或加排他锁的操作,都会等待我们解锁或直接抛出异常。

ps:行锁、表锁、读锁、写锁都是在操作之前先上排他锁

优点:
    悲观并发控制实际上是“先取锁再访问”的保守策略,为数据处理的安全提供了保证。
缺点:
  (a)在效率方面,处理加锁的机制会让数据库产生额外的开销,还有增加产生死锁的机会;
  (b) 在只读型事务处理中由于不会产生冲突,也没必要使用锁,这样做只能增加系统负载;还有会降低了并行性,一个事务如果锁定了某行数据,其他事务就必须等待该事务处理完才可以处理那行数

6.2 乐观锁

在关系数据库管理系统里,乐观并发控制(又名“乐观锁”,Optimistic Concurrency Control,缩写“OCC”)是一种并发控制的方法。它假设多用户并发的事务在处理时不会彼此互相影响,各事务能够在不产生锁的情况下处理各自影响的那部分数据。在提交数据更新之前,每个事务会先检查在该事务读取数据后,有没有其他事务又修改了该数据。如果其他事务有更新的话,正在提交的事务会进行回滚。

乐观锁( Optimistic Locking ) 相对悲观锁而言,乐观锁假设认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让返回用户错误的信息,让用户决定如何去做。

相对于悲观锁,在对数据库进行处理的时候,乐观锁并不会使用数据库提供的锁机制。一般的实现乐观锁的方式就是记录数据版本。

数据版本,为数据增加的一个版本标识。当读取数据时,将版本标识的值一同读出,数据每更新一次,同时对版本标识进行更新。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的版本标识进行比对,如果数据库表当前版本号与第一次取出来的版本标识值相等,则予以更新,否则认为是过期数据。

在数据库中,乐观锁的实现有两种方式

  1. 使用版本号实现
    每一行数据多一个字段version,每次更新数据对应版本号+1,
    原理:读出数据,将版本号一同读出,之后更新,版本号+1,提交数据版本号大于数据库当前版本号,则予以更新,否则认为是过期数据,重新读取数据
  2. 使用时间戳实现
    每一行数据多一个字段time
    原理:读出数据,将时间戳一同读出,之后更新,提交数据时间戳等于数据库当前时间戳,则予以更新,否则认为是过期数据,重新读取数据

如何选择

在乐观锁与悲观锁的选择上面,主要看下两者的区别以及适用场景就可以了。

1、乐观锁并未真正加锁,效率高。一旦锁的粒度掌握不好,更新失败的概率就会比较高,容易发生业务失败。

2、悲观锁依赖数据库锁,效率低。更新失败的概率比较低。

随着互联网三高架构(高并发、高性能、高可用)的提出,悲观锁已经越来越少的被使用到生产环境中了,尤其是并发量比较大的业务场景。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值