欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  数据库

MySQL中的事务与锁

程序员文章站 2022-04-18 16:21:40
...
这篇文章详细介绍一下数据库事务与锁的相关知识。主要是一些概念性的东西看起来可能比较乏味,但作为一名合格的程序员来说,你应该掌握也必须掌握。这些理论知识好比是一个人的内功,我们平时敲代码是外功,只有内外兼修,相互促进,才能达到武林高手的境界。好了废话不多说,下面开始。

数据库事务

事务的边界

事务的开始边界(begin)
事务的结束边界(commit):提交事务,永久保存被事务更新后的数据库状态。
事务的异常结束边界(rollback):撤销事务,使数据库退回到执行事务前的初始状态。

每启动一个MySQL.exe程序,就会得到一个单独的数据库连接。每个数据库连接都有一个全局变量autocommit,表示当前的事务模式,它有两个值可选:

  • 0:表示手工提交模式

  • 1:表示自动提交模式,默认值

我们可以查看和修改这个值。

数据库事务的4个特性(ACID):

  • 原子性(Atomicity):事务是一个原子操作单元,其对数据的修改,要么全部执行,要么全都不执行;

  • 一致性(Consistent):在事务开始和完成时,数据都必须保持一致状态;

  • 隔离性(Isolation):数据库系统提供一定的隔离机制,保证事务在不受外部并发操作影响的“独立”环境执行;

  • 持久性(Durable):事务完成之后,它对于数据的修改是永久性的,即使出现系统故障也能够保持。

事务隔离级别

数据库事务隔离级别,只是针对一个事务能不能读取其它事务的中间结果。

MySQL中的事务与锁

  • Read Uncommitted (读取未提交内容)
    在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少。读取未提交的数据,也被称之为脏读( Dirty Read )。

  • Read Committed (读取提交内容)
    这是大多数数据库系统的默认隔离级别(但不是 MySQL 默认的)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这种隔离级别 也支持所谓的不可重复读( Nonrepeatable Read ),因为同一事务的其他实例在该实例处理其间可能会有新的 commit ,所以同一 select 可能返回不同结果。

  • Repeatable Read (可重读)
    这是 MySQL 的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。不过理论上,这会导致另一个棘手的问题:幻读 ( Phantom Read )。简单的说,幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的 ” 幻影 ” 行。 InnoDB和 Falcon 存储引擎通过多版本并发控制( MVCC , Multiversion Concurrency Control )机制解决了该问题。

  • Serializable (可串行化)
    这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。

隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。
对于多数应用程序,可以有效考虑把数据库系统的隔离级别设为Read Committed,它能够避免脏读,而且具有较好的并发性能。尽管它会导致不可重复读、虚度和第二类丢失更新这些并发问题,在可能出现这类问题的个别场合,可以由应用程序采用悲观锁和乐观锁来控制。

事务的传播性

  1. PROPAGATION_REQUIRED
    加入当前正要执行的事务,如果当前事务不存在,那么就起一个新的事务。Spring 操作数据库默认的事务传播行为就是 propagation_required 。

  2. PROPAGATION_SUPPORTS
    如果当前在事务中,即以事务的形式运行,如果当前不再一个事务中,那么就以非事务的形式运行.

  3. PROPAGATION_MANDATORY
    必须在一个事务中运行。也就是说,他只能被一个父事务调用。否则,他就要抛出异常。

  4. PROPAGATION_REQUIRES_NEW
    挂起当前事务,另起一个新的事务。

  5. PROPAGATION_NOT_SUPPORTED

    当前不支持事务。如果在事务中,会挂起当前事务,自己以非事务的行为运行。

  6. PROPAGATION_NEVER
    不能在事务中运行,如果在事务中运行就会抛出异常。

  7. PROPAGATION_NESTED
    嵌套的事务依赖父事务,父事务提交,它跟着提交,父事务回滚,它跟着回滚。

行级锁

Mysql中三种类型的锁:

行级:引擎 INNODB , 单独的一行记录加锁
页级:引擎 BDB,一次锁定相邻的一组记录。
表级:引擎 MyISAM , 理解为锁住整个表,可以同时读,写不行。
三种锁的特性可大致归纳如下:
1) 表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
2) 行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
3) 页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。

我们这里主要谈论的是行级锁,一般的在秒杀系统中我们会对商品库存使用行级锁,因为秒杀的时候库存是一个很重要的数据,我们在创建数据库的表时可能会出现下面这样的设置:

ENGINE = InnoDB AUTO_INCREMENT=10 DEFAULT CHARACTER SET = utf8 comment='用户表

将引擎设置为InnoDB,InnnoDB与其他引擎的不同:一是支持事务(TRANCSACTION),二是采用了行级锁。

InnoDB中两种模式的行级锁:

1)共享锁:允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。
( Select * from table_name where ……lock in share mode)
2)排他锁:允许获得排他锁的事务更新数据,阻止其他事务取得相同数据集的共享读锁和 排他写锁。(select * from table_name where…..for update)

为了允许行锁和表锁共存,实现多粒度锁机制;同时还有两种内部使用的意向锁(都是表锁),分别为意向共享锁和意向排他锁。

  • 意向共享锁(IS):事务打算给数据行加行共享锁,事务在给一个数据行加共享锁前必须先取得该表的IS锁。

  • 意向排他锁(IX):事务打算给数据行加行排他锁,事务在给一个数据行加排他锁前必须先取得该表的IX锁。

注意:InnoDB行锁是通过给索引上的索引项加锁来实现的,这一点与Oracle不同,后者是通过在数据块中对相应数据行加锁来实现的。InnoDB这种行锁实现特点意味着:只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁!MySQL的行锁是针对索引加的锁,不是针对记录加的锁,所以虽然是访问不同行的记录,但是如果是使用相同的索引键,是会出现锁冲突的。应用设计的时候要注意这一点。

行级锁的优缺点

行级锁定的优点:

  • 当在许多线程中访问不同的行时只存在少量锁定冲突。

  • 回滚时只有少量的更改。

  • 可以长时间锁定单一的行。

行级锁定的缺点:

  • 比页级或表级锁定占用更多的内存。

  • 当在表的大部分数据上使用时,比页级或表级锁定速度慢,因为你必须获取更多的锁。如果你在大部分数据上经常进行GROUP BY操作或

者必须经常扫描整个表,比其它锁定明显慢很多。

hibernate中通过行级锁实现的悲观锁。

一些例子:

假设有个表单products ,里面有id跟name二个栏位,id是主键。
1: 明确指定主键,并且有此条记录,执行row lock。若查无此记录,无lock。

SELECT * FROM products WHERE id='3' FOR UPDATE;SELECT * FROM products WHERE id='3' and name="cat" FOR UPDATE;

2: 无主键,执行table lock。

SELECT * FROM products WHERE name='Mouse' FOR UPDATE;

3: 主键不明确,table lock。

SELECT * FROM products WHERE id<>'3' FOR UPDATE;

注意: FOR UPDATE仅适用于InnoDB,且必须在事务块(BEGIN/COMMIT)中才能生效。此外,如果A与B都对表id进行查询但查询不到记录,则A与B在查询上不会进行row锁,但A与B都会获取排它锁,此时A再插入一条记录的话则会因为B已经有锁而处于等待中,此时B再插入一条同样的数据则会抛出Deadlock found when trying to get lock; try restarting transaction。然后释放锁,此时A就获得了锁而插入成功。

以上就是MySQL中的事务与锁的内容,更多相关内容请关注PHP中文网(www.php.cn)!

相关标签: MySQ,事务与锁