`

mysql悲观锁总结和实践

 
阅读更多

 

最近学习了一下数据库的悲观锁和乐观锁,根据自己的理解和网上参考资料总结如下:

 

悲观锁介绍(百科):

悲观锁,正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。

 

使用场景举例:以MySQL InnoDB为例

商品goods表中有一个字段status,status为1代表商品未被下单,status为2代表商品已经被下单,那么我们对某个商品下单时必须确保该商品status为1。假设商品的id为1。

 

1如果不采用锁,那么操作方法如下:

//1.查询出商品信息

select status from t_goods where id=1;

//2.根据商品信息生成订单

insert into t_orders (id,goods_id) values (null,1);

//3.修改商品status为2

update t_goods set status=2;

 

上面这种场景在高并发访问的情况下很可能会出现问题。

前面已经提到,只有当goods status为1时才能对该商品下单,上面第一步操作中,查询出来的商品status为1。但是当我们执行第三步Update操作的时候,有可能出现其他人先一步对商品下单把goods status修改为2了,但是我们并不知道数据已经被修改了,这样就可能造成同一个商品被下单2次,使得数据不一致。所以说这种方式是不安全的。

 

2使用悲观锁来实现:

在上面的场景中,商品信息从查询出来到修改,中间有一个处理订单的过程,使用悲观锁的原理就是,当我们在查询出goods信息后就把当前的数据锁定,直到我们修改完毕后再解锁。那么在这个过程中,因为goods被锁定了,就不会出现有第三者来对其进行修改了。

 

注:要使用悲观锁,我们必须关闭mysql数据库的自动提交属性,因为MySQL默认使用autocommit模式,也就是说,当你执行一个更新操作后,MySQL会立刻将结果进行提交。

 

我们可以使用命令设置MySQL为非autocommit模式:

set autocommit=0;

 

设置完autocommit后,我们就可以执行我们的正常业务了。具体如下:

//0.开始事务

begin;/begin work;/start transaction; (三者选一就可以)

//1.查询出商品信息

select status from t_goods where id=1 for update;

//2.根据商品信息生成订单

insert into t_orders (id,goods_id) values (null,1);

//3.修改商品status为2

update t_goods set status=2;

//4.提交事务

commit;/commit work;

 

注:上面的begin/commit为事务的开始和结束,因为在前一步我们关闭了mysql的autocommit,所以需要手动控制事务的提交,在这里就不细表了。

 

上面的第一步我们执行了一次查询操作:select status from t_goods where id=1 for update;

与普通查询不一样的是,我们使用了select…for update的方式,这样就通过数据库实现了悲观锁。此时在t_goods表中,id为1的 那条数据就被我们锁定了,其它的事务必须等本次事务提交之后才能执行。这样我们可以保证当前的数据不会被其它事务修改。

 

注:需要注意的是,在事务中,只有SELECT ... FOR UPDATE 或LOCK IN SHARE MODE 同一笔数据时会等待其它事务结束后才执行,一般SELECT ... 则不受此影响。拿上面的实例来说,当我执行select status from t_goods where id=1 for update;后。我在另外的事务中如果再次执行select status from t_goods where id=1 for update;则第二个事务会一直等待第一个事务的提交,此时第二个查询处于阻塞的状态,但是如果我是在第二个事务中执行select status from t_goods where id=1;则能正常查询出数据,不会受第一个事务的影响。

 

补充:MySQL select…for update的Row Lock与Table Lock

上面我们提到,使用select…for update会把数据给锁住,不过我们需要注意一些锁的级别,MySQL InnoDB默认Row-Level Lock,所以只有「明确」地指定主键,MySQL 才会执行Row lock (只锁住被选取的数据) ,否则MySQL 将会执行Table Lock (将整个数据表单给锁住)。

 

举例说明:

数据库表t_goods,包括id,status,name三个字段,id为主键,数据库中记录如下;

Sql代码 复制代码 收藏代码
  1. mysql> select * from t_goods;   
  2. +----+--------+------+   
  3. | id | status | name |   
  4. +----+--------+------+   
  5. |  1 |      1 | 道具 |   
  6. |  2 |      1 | 装备 |   
  7. +----+--------+------+   
  8. rows in set  
  9.   
  10. mysql>  
mysql> select * from t_goods;
+----+--------+------+
| id | status | name |
+----+--------+------+
|  1 |      1 | 道具 |
|  2 |      1 | 装备 |
+----+--------+------+
2 rows in set

mysql>

注:为了测试数据库锁,我使用两个console来模拟不同的事务操作,分别用console1、console2来表示。 

 

例1: (明确指定主键,并且有此数据,row lock)

console1:查询出结果,但是把该条数据锁定了

Sql代码 复制代码 收藏代码
  1. mysql> select * from t_goods where id=1 for update;   
  2. +----+--------+------+   
  3. | id | status | name |   
  4. +----+--------+------+   
  5. |  1 |      1 | 道具 |   
  6. +----+--------+------+   
  7. 1 row in set  
  8.   
  9. mysql>  
mysql> select * from t_goods where id=1 for update;
+----+--------+------+
| id | status | name |
+----+--------+------+
|  1 |      1 | 道具 |
+----+--------+------+
1 row in set

mysql>

console2:查询被阻塞

Sql代码 复制代码 收藏代码
  1. mysql> select * from t_goods where id=1 for update;  
mysql> select * from t_goods where id=1 for update;

console2:如果console1长时间未提交,则会报错

Sql代码 复制代码 收藏代码
  1. mysql> select * from t_goods where id=1 for update;   
  2. ERROR 1205 : Lock wait timeout exceeded; try restarting transaction  
mysql> select * from t_goods where id=1 for update;
ERROR 1205 : Lock wait timeout exceeded; try restarting transaction

 

例2: (明确指定主键,若查无此数据,无lock)

console1:查询结果为空

Sql代码 复制代码 收藏代码
  1. mysql> select * from t_goods where id=3 for update;   
  2. Empty set  
mysql> select * from t_goods where id=3 for update;
Empty set

console2:查询结果为空,查询无阻塞,说明console1没有对数据执行锁定

Sql代码 复制代码 收藏代码
  1. mysql> select * from t_goods where id=3 for update;   
  2. Empty set  
mysql> select * from t_goods where id=3 for update;
Empty set

 

例3: (无主键,table lock)

console1:查询name=道具 的数据,查询正常

Sql代码 复制代码 收藏代码
  1. mysql> select * from t_goods where name='道具' for update;   
  2. +----+--------+------+   
  3. | id | status | name |   
  4. +----+--------+------+   
  5. |  1 |      1 | 道具 |   
  6. +----+--------+------+   
  7. 1 row in set  
  8.   
  9. mysql>  
mysql> select * from t_goods where name='道具' for update;
+----+--------+------+
| id | status | name |
+----+--------+------+
|  1 |      1 | 道具 |
+----+--------+------+
1 row in set

mysql>

console2:查询name=装备 的数据,查询阻塞,说明console1把表给锁住了

Sql代码 复制代码 收藏代码
  1. mysql> select * from t_goods where name='装备' for update;  
mysql> select * from t_goods where name='装备' for update;

console2:若console1长时间未提交,则查询返回为空

Sql代码 复制代码 收藏代码
  1. mysql> select * from t_goods where name='装备' for update;   
  2. Query OK, -1 rows affected  
mysql> select * from t_goods where name='装备' for update;
Query OK, -1 rows affected

 

例4: (主键不明确,table lock)

console1:查询正常

Sql代码 复制代码 收藏代码
  1. mysql> begin;   
  2. Query OK, 0 rows affected   
  3.   
  4. mysql> select * from t_goods where id>0 for update;   
  5. +----+--------+------+   
  6. | id | status | name |   
  7. +----+--------+------+   
  8. |  1 |      1 | 道具 |   
  9. |  2 |      1 | 装备 |   
  10. +----+--------+------+   
  11. rows in set  
  12.   
  13. mysql>  
mysql> begin;
Query OK, 0 rows affected

mysql> select * from t_goods where id>0 for update;
+----+--------+------+
| id | status | name |
+----+--------+------+
|  1 |      1 | 道具 |
|  2 |      1 | 装备 |
+----+--------+------+
2 rows in set

mysql>

console2:查询被阻塞,说明console1把表给锁住了

Sql代码 复制代码 收藏代码
  1. mysql> select * from t_goods where id>1 for update;  
mysql> select * from t_goods where id>1 for update;

 

例5: (主键不明确,table lock)

console1:

Sql代码 复制代码 收藏代码
  1. mysql> begin;   
  2. Query OK, 0 rows affected   
  3.   
  4. mysql> select * from t_goods where id<>1 for update;   
  5. +----+--------+------+   
  6. | id | status | name |   
  7. +----+--------+------+   
  8. |  2 |      1 | 装备 |   
  9. +----+--------+------+   
  10. 1 row in set  
  11.   
  12. mysql>  
mysql> begin;
Query OK, 0 rows affected

mysql> select * from t_goods where id<>1 for update;
+----+--------+------+
| id | status | name |
+----+--------+------+
|  2 |      1 | 装备 |
+----+--------+------+
1 row in set

mysql>

console2:查询被阻塞,说明console1把表给锁住了

Sql代码 复制代码 收藏代码
  1. mysql> select * from t_goods where id<>2 for update;  
mysql> select * from t_goods where id<>2 for update;

console1:提交事务

Sql代码 复制代码 收藏代码
  1. mysql> commit;   
  2. Query OK, 0 rows affected  
mysql> commit;
Query OK, 0 rows affected

console2:console1事务提交后,console2查询结果正常

Sql代码 复制代码 收藏代码
  1. mysql> select * from t_goods where id<>2 for update;   
  2. +----+--------+------+   
  3. | id | status | name |   
  4. +----+--------+------+   
  5. |  1 |      1 | 道具 |   
  6. +----+--------+------+   
  7. 1 row in set  
  8.   
  9. mysql>  
mysql> select * from t_goods where id<>2 for update;
+----+--------+------+
| id | status | name |
+----+--------+------+
|  1 |      1 | 道具 |
+----+--------+------+
1 row in set

mysql>

 

以上就是关于数据库主键对MySQL锁级别的影响实例,需要注意的是,除了主键外,使用索引也会影响数据库的锁定级别

 

举例:

我们修改t_goods表,给status字段创建一个索引

修改id为2的数据的status为2,此时表中数据为:

Sql代码 复制代码 收藏代码
  1. mysql> select * from t_goods;   
  2. +----+--------+------+   
  3. | id | status | name |   
  4. +----+--------+------+   
  5. |  1 |      1 | 道具 |   
  6. |  2 |      2 | 装备 |   
  7. +----+--------+------+   
  8. rows in set  
  9.   
  10. mysql>  
mysql> select * from t_goods;
+----+--------+------+
| id | status | name |
+----+--------+------+
|  1 |      1 | 道具 |
|  2 |      2 | 装备 |
+----+--------+------+
2 rows in set

mysql>

 

例6: (明确指定索引,并且有此数据,row lock)

console1:

Sql代码 复制代码 收藏代码
  1. mysql> select * from t_goods where status=1 for update;   
  2. +----+--------+------+   
  3. | id | status | name |   
  4. +----+--------+------+   
  5. |  1 |      1 | 道具 |   
  6. +----+--------+------+   
  7. 1 row in set  
  8.   
  9. mysql>  
mysql> select * from t_goods where status=1 for update;
+----+--------+------+
| id | status | name |
+----+--------+------+
|  1 |      1 | 道具 |
+----+--------+------+
1 row in set

mysql>

console2:查询status=1的数据时阻塞,超时后返回为空,说明数据被console1锁定了

Sql代码 复制代码 收藏代码
  1. mysql> select * from t_goods where status=1 for update;   
  2. Query OK, -1 rows affected  
mysql> select * from t_goods where status=1 for update;
Query OK, -1 rows affected

console2:查询status=2的数据,能正常查询,说明console1只锁住了行,未锁表

Sql代码 复制代码 收藏代码
  1. mysql> select * from t_goods where status=2 for update;   
  2. +----+--------+------+   
  3. | id | status | name |   
  4. +----+--------+------+   
  5. |  2 |      2 | 装备 |   
  6. +----+--------+------+   
  7. 1 row in set  
  8.   
  9. mysql>  
mysql> select * from t_goods where status=2 for update;
+----+--------+------+
| id | status | name |
+----+--------+------+
|  2 |      2 | 装备 |
+----+--------+------+
1 row in set

mysql>

 

例7: (明确指定索引,若查无此数据,无lock)

console1:查询status=3的数据,返回空数据

Sql代码 复制代码 收藏代码
  1. mysql> select * from t_goods where status=3 for update;   
  2. Empty set  
mysql> select * from t_goods where status=3 for update;
Empty set

console2:查询status=3的数据,返回空数据

Sql代码 复制代码 收藏代码
  1. mysql> select * from t_goods where status=3 for update;   
  2. Empty set  
mysql> select * from t_goods where status=3 for update;
Empty set

 

 

以上就是关于我对数据库悲观锁的理解和总结,有不对的地方欢迎拍砖,下一次会带来数据库乐观锁的总结和实践

 

参考资料:

MySQL事务与锁定命令:http://www.docin.com/p-16805970.html

悲观锁:http://www.cnblogs.com/chenwenbiao/archive/2012/06/06/2537508.html 

分享到:
评论

相关推荐

    MySQL悲观锁总结和实践

    ### MySQL悲观锁总结和实践 #### 一、悲观锁概念详解 悲观锁是一种同步机制,其基本思想是对数据被外界修改持有悲观态度,因此在整个数据处理过程中,将数据处于锁定状态。简而言之,悲观锁认为数据在处理过程中很...

    MySQL与Java锁的学习

    在IT领域,数据库管理和编程语言的交互是至关重要的,尤其是当涉及到并发控制和数据安全时。本主题聚焦于“MySQL与...通过阅读《第15章_锁.pdf》和《锁总结.txt》等资料,可以进一步深入了解这一领域的细节和实践技巧。

    最详细的MySQL知识笔记.pdf

    在实际开发中,了解MySQL的最佳实践和常见问题解决方案是至关重要的,这有助于构建稳定、高效的数据库系统。随着技术的发展,MySQL持续更新改进,学习和掌握最新的特性和最佳实践是每个数据库管理员和开发者的必修课...

    mysql学习思路总结

    MySQL是世界上最受欢迎的关系型数据库管理系统之一,其性能、稳定性和可扩展性使其在各种规模的应用中得到广泛应用。...通过不断实践和理论学习,才能更好地掌握MySQL并应用于实际项目,从而提升系统的稳定性和性能。

    MySQL数据库工程师常见面试题.zip

    你需要理解乐观锁和悲观锁的概念,以及REPEATABLE READ、READ COMMITTED、SERIALIZABLE等不同的事务隔离级别。死锁问题及其检测与解决策略也是面试中常被问到的内容。 数据库复制是实现高可用性和负载均衡的关键。...

    4-2_互联网数据库MySql高级.pdf

    此外,还有乐观锁和悲观锁的概念,它们分别代表了不同的并发控制策略。 接下来,我们转向存储过程。存储过程是预编译的一组SQL语句,可以封装复杂的业务逻辑并多次重用。在MySQL中,创建存储过程可以通过`CREATE ...

    552写锁及 授权

    此外,它也是乐观锁和悲观锁策略中的重要组成部分。乐观锁假设很少发生并发冲突,只在提交时检查冲突;而悲观锁则在开始操作前就获取锁,防止并发冲突。 为了实现写锁,数据库系统通常会维护一种锁定机制,如锁定表...

    基于ssm+mysql学习交流平台源码数据库.doc

    解决方案是采用乐观锁或悲观锁机制,根据实际情况选择合适的并发控制策略。 - **大数据量处理**:随着用户数量增加,如何高效地处理大量数据?可以考虑引入分布式数据库或NoSQL数据库来解决传统关系型数据库在大数据...

    数据库应用技术课件(老师总结)

    2. 并发控制:在多用户环境下,确保数据的一致性和完整性,常用方法有锁机制、乐观锁、悲观锁等。 六、数据库备份与恢复 1. 备份:定期或按需复制数据库,防止数据丢失。 2. 恢复:在系统故障后,通过备份数据将...

    数据库优化总结.pdf.zip

    8. **事务与并发控制**:正确设置事务隔离级别,使用乐观锁、悲观锁或MVCC(多版本并发控制)机制,可以有效处理并发问题,保证数据一致性。 9. **监控与调优工具**:如MySQL的Performance Schema,Oracle的AWR报告...

    SQL编程总结

    2. 并发控制:在多用户环境中,SQL使用锁定、乐观锁和悲观锁策略来防止数据冲突。隔离级别(读未提交、读已提交、可重复读和串行化)决定了数据的并发行为。 五、存储过程与触发器 1. 存储过程:存储过程是一组预...

    数据库操作

    此外,使用乐观锁或悲观锁机制也是控制并发访问的有效手段。 第三题,如何备份和恢复MySQL数据库?MySQL提供多种备份工具,如mysqldump、xtrabackup等。常规的全量备份可以通过mysqldump命令实现,增量备份则需要...

    sql学习笔记 常见问题总结

    2. 并发控制:并发操作可能导致数据不一致,通过锁定机制(如读锁、写锁)和乐观锁、悲观锁策略来解决。 六、索引优化 1. 索引:加速查询的关键工具,分为B树索引、哈希索引和全文索引等类型。创建索引可以提升...

    3.五八到家账户账务系统架构与实践.pdf

    - 对于出账操作,则采用实时记账,使用悲观锁来保证数据的正确性。 ### 监控系统 文档还提到了监控系统的重要性,它包括实时业务监控系统、数据源接入、数据清洗、存储、展现等子系统。监控系统的目的是确保账务...

    Python在数据库教学中的应用.pdf

    4. 构建加锁机制的示例:Python也可以用于展示数据库中的加锁机制,例如乐观锁和悲观锁的实现方式。学生可以编写相关的代码,了解数据库并发控制中的锁机制,并观察加锁对并发操作的影响。 5. 数据库管理:Python...

    2010年9月全国计算机四级数据库工程师笔试试题答案.doc

    5. **并发控制与事务处理**:数据库中的多用户访问和并发操作需要有效的管理机制,如锁定、乐观锁、悲观锁和事务隔离级别。 6. **备份与恢复**:理解数据库的备份策略,如完整备份、增量备份和差异备份,以及如何...

    J2EE企业级项目开发-1期 任务2-6 实训项目单.doc

    学生需要了解锁、乐观锁、悲观锁等并发控制策略,以及在JavaEE环境中如何使用这些策略来处理并发问题。 在**实训步骤**中,学生首先会**创建项目**,使用MyEclipse、JDK、Tomcat和MySQL等工具搭建开发环境。然后,...

    shujukugongchengshi.rar_数据库系统 工程师

    这涉及锁机制、多版本并发控制(MVCC)、乐观锁和悲观锁等策略。理解这些机制并能合理应用,可以提高系统的并发性能。 五、性能优化 数据库性能优化是数据库系统工程师的日常工作之一。这包括索引设计、查询优化、...

    基础知识.pdf

    详细探讨了线程池的实现原理、线程生命周期、锁机制以及线程安全问题,包括volatile关键字的实现原理,悲观锁和乐观锁的概念,以及CAS乐观锁的ABA问题。 集合框架方面,详细阐述了List和Set的区别,List和Map的区别...

    微博支付-高并发场景下数据一致性问题探究.pdf

    在高并发下,合理的编程实践,如避免死锁、合理设置事务隔离级别,以及利用锁机制(乐观锁、悲观锁)都是保障数据一致性的关键。 总结来说,解决高并发场景下的数据一致性问题需要综合运用多种技术和策略,包括但不...

Global site tag (gtag.js) - Google Analytics