1、乐观锁
在关系数据库管理系统里,乐观并发控制(又名“乐观锁”,Optimistic Concurrency Control,缩写“OCC”)是一种并发控制的方法。
它假设多用户并发的事务在处理时不会彼此互相影响,各事务能够在不产生锁的情况下处理各自影响的那部分数据。
在提交数据更新之前,每个事务会先检查在该事务读取数据后,有没有其他事务又修改了该数据。如果其他事务有更新的话,正在提交的事务会进行回滚。
乐观锁( Optimistic Locking ) 相对悲观锁而言,乐观锁假设认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让返回用户错误的信息,让用户决定如何去做。
相对于悲观锁,在对数据库进行处理的时候,乐观锁并不会使用数据库提供的锁机制。一般的实现乐观锁的方式就是记录数据版本。
例:
CREATE TABLE `optimistic_lock` ( `id` int(11) NOT NULL AUTO_INCREMENT, `state` int(11) NOT NULL, `create_time` datetime NOT NULL, `update_time` datetime DEFAULT NULL, `version` int(11) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO `optimistic_lock`(`state`, `create_time`, `version`) VALUES (0, NOW(), 1);
#执行 成功 state = 1 UPDATE `optimistic_lock` SET `state` = 1, `update_time` = NOW(), `version` = `version` + 1 WHERE `id` = 1 AND `version` = 1;
#再次执行 失败,因为 version 在上面已经被修改为 2 UPDATE `optimistic_lock` SET `state` = 1, `update_time` = NOW(), `version` = `version` + 1 WHERE `id` = 1 AND `version` = 1;
2、悲观锁
在关系数据库管理系统里,悲观并发控制(又名“悲观锁”,Pessimistic Concurrency Control,缩写“PCC”)是一种并发控制的方法。它可以阻止一个事务以影响其他用户的方式来修改数据。
如果一个事务执行的操作都某行数据应用了锁,那只有当这个事务把锁释放,其他事务才能够执行与该锁冲突的操作。
悲观并发控制主要用于数据争用激烈的环境,以及发生并发冲突时使用锁保护数据的成本要低于回滚事务的成本的环境中。
悲观锁,正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度(悲观),因此,在整个数据处理过程中,将数据处于锁定状态。
悲观锁的实现,往往依靠数据库提供的锁机制 (也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)
共享锁 和 排他锁 都属于悲观锁的范畴。
2.1、共享锁
共享锁【S锁】
又称读锁,若事务T对数据对象A加上S锁,则事务T可以读A但不能修改A,其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S锁。
这保证了其他事务可以读A,但在T释放A上的S锁之前不能对A做任何修改。
排他锁【X锁】
又称写锁。若事务T对数据对象A加上X锁,则事务T可以读A也可以修改A,其他事务不能再对A加任何锁,直到T释放A上的锁。
这保证了其他事务在T释放A上的锁之前不能再读取和修改A。
共享锁 例:
CREATE TABLE `product` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(128) NOT NULL, `stock_amount` int(11) NOT NULL, `create_time` datetime NOT NULL, `update_time` datetime DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO `product`(`name`, `stock_amount`, `create_time`) VALUES ('屠龙宝刀', 100, NOW());
在 查询窗口 1 中执行:
START TRANSACTION; SELECT stock_amount FROM `product` WHERE id=1 LOCK IN SHARE MODE;
在 查询窗口 2 中执行:
UPDATE `product` SET stock_amount = 98 WHERE id=1;
窗口会卡死,等待一会报错 timeout
在 查询窗口 1 中执行:
COMMIT;
2.2、排他锁 例:
在 查询窗口 1 中执行:
START TRANSACTION; #id 是 索引键,可以加 行锁 SELECT stock_amount FROM `product` WHERE id=1 FOR UPDATE;
在 查询窗口 2 中执行:
UPDATE `product` SET stock_amount = 98 WHERE id=1;
效果同 共享锁,因为 insert、update、delete 默认会使用排他锁
行锁 和 表锁 都属于 排他锁的范畴。
MySQL InnoDB默认行级锁。行级锁都是基于索引的,如果一条SQL语句用不到索引是不会使用行级锁的,会使用表级锁把整张表锁住,这点需要注意。