mysql 事务

场景构思

假设该场景发生于一个银行转账背景下,月中,又到了发工资的日子。学校打算给A老师发放一个月的工资。(此处,我们假设转账都是由人工操作的),整个过程本应该如下:

  1. 学校财务核对A老师工资单

  2. 确认学校账上还有这么多钱

  3. 向银行提出转账申请,银行扣除学校财务卡上的指定金额

  4. 银行向A老师工资卡打入指定金额

  5. 银行汇报双方交易完成

    但是,当这个过程执行完第3步的时候,突然大断电!整个电力系统进入瘫痪。待电力系统回复之后,银行并不会继续执行4、5步甚至连1,2,3步的操作记录都丢失了。此时出现了如下的问题:

  • 学校认为,工资已经发出

  • A老师认为,学校还没有发工资

  • 银行认为,从来就没有发生过转账的事情

    其实整个过程可以用一个词来描述:数据库中的数据产生了“不一致性”

一致性

上述背景中设计到了一个概念,叫做不一致性,这是和一致性相对的概念。那么,什么是一致性呢?

一致性的意思是,在一系列数据库行为的前后两个时间点上,数据是正确对应的。放在上面的例子来看,就是操作前后,两个账户的总金额是一样的,这样就保证不会凭空的丢失掉不该丢失掉的金钱。


  MySQL的事务支持不是绑定在MySQL服务器本身,而是与存储引擎相关1.MyISAM:不支持事务,用于只读程序提高性能 2.InnoDB:支持ACID事务、行级锁、并发 3.Berkeley DB:支持事务。

 一个事务是一个连续的一组数据库操作,就好像它是一个单一的工作单元进行。换言之,永远不会是完整的事务,除非该组内的每个单独的操作是成功的。如果在事务的任何操作失败,则整个事务将失败。
  • 在 MySQL 中只有使用了 Innodb 数据库引擎的数据库或表才支持事务。
  • 事务处理可以用来维护数据库的完整性,保证成批的 SQL 语句要么全部执行,要么全部不执行。
  • 事务用来管理 insert,update,delete 语句

一般来说,事务是必须满足4个条件(ACID)::原子性(Atomicity,或称不可分割性)、一致性(Consistency)、隔离性(Isolation,又称独立性)、持久性(Durability)。

  • 原子性:一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。

  • 一致性:在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。

  • 隔离性:数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。

  • 持久性:事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

事务控制语句:

  • BEGIN或START TRANSACTION;显式地开启一个事务;

  • COMMIT;也可以使用COMMIT WORK,不过二者是等价的。COMMIT会提交事务,并使已对数据库进行的所有修改称为永久性的;

  • ROLLBACK;有可以使用ROLLBACK WORK,不过二者是等价的。回滚会结束用户的事务,并撤销正在进行的所有未提交的修改;

  • SAVEPOINT identifier;SAVEPOINT允许在事务中创建一个保存点,一个事务中可以有多个SAVEPOINT;

  • RELEASE SAVEPOINT identifier;删除一个事务的保存点,当没有指定的保存点时,执行该语句会抛出一个异常;

  • ROLLBACK TO identifier;把事务回滚到标记点;

  • SET TRANSACTION;用来设置事务的隔离级别。InnoDB存储引擎提供事务的隔离级别有READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ和SERIALIZABLE。

    事务隔离级别分为4种:

    READ UNCOMMITED // 允许脏读,不可重复读及幻读;最低级别,只能保证不读取物理上损坏的数据

    READ COMMITED   //不允许脏读, 允许不可重复读幻读;语句级

    REPEATABLE READ // 不允许脏读和不可重复读,允许幻读;事务级

    SERIALIZABLE READ // 不允许读,不可重复读;最该级别,事务级

    事务隔离性及在MySQL中实践:当多个线程都开启事务操作数据库中数据时,数据库系统要能进行隔离操作,以保证各个线程获取数据的准确性。

    设置事务隔离级别:

    A、查询Mysql系统隔离默认级别:

    Mysql> SELECT @@global.tx_isolation;

    结果:

    @@global.tx_isolation

    REPEATABLE-READ

    B、查询Mysql会话隔离默认级别:

    Mysql> SELECT @@tx_isolation;

    结果:

    @@tx_isolation

    REPEATABLE-READ

    C、修改系统及会话隔离级别

    这里我们将事务的级别修改为READ COMMITED:

    mysql> SET global transaction isolation level read committed;

    mysql> SET session transaction isolation level read committed;

    结果:


  • MYSQL 事务处理主要有两种方法:

    1、用 BEGIN/START TRANSACTION, ROLLBACK, COMMIT来实现

    • BEGIN/START TRANSACTION 开始一个事务
    • ROLLBACK 事务回滚
    • COMMIT 事务确认

    2、直接用 SET 来改变 MySQL 的自动提交模式:

    • SET AUTOCOMMIT=0 禁止自动提交
    • SET AUTOCOMMIT=1 开启自动提交
并发事务处理带来的问题;如果没有隔离,会发生的几种问题:
丢失更新

当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,会发生丢失更新问题。每个事务都不知道其他是事务的存在,最后的更新将覆盖由其他事务所做的更新,这将导致数据丢失。

脏读(Dirty Read)

一个事务处理过程里读取了另一个未提交的事务中的数据。(当一个事务正访问数据并对其进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问这个数据,然后使用该数据。因为这个数据是还没提交的数据,则另外一个事务读到的这个数据是脏数据,依据脏数据所做的操作可能是不正确的)

不可重复读(NonRepeatable Read)

对于数据库中的某个数据,一个事务多次查询却返回了不同的数据值,这是由于在查询的间隔期间,另外一个事务修改并提交了该数据。

一个事务在读取某些 数据后的某个时间,再次读取以前读过的数据,却发现其读出的数据已经发生改变或被删除。

不可重复读和脏读的别是,脏读是某一事务读取了另一个事务未提交的脏数据,而不可重复读则是读取了前一事务提交的数据。
  在某些情况下,不可重复读并不是问题,比如我们多次查询某个数据当然以最后查询得到的结果为主。但在另一些情况下就有可能发生问题,例如对于同一个数据A和B依次查询就可能不同,A和B就可能打起来了……

幻读(Phantom Read)

一个事务按相同的查询条件重新读取以前检索过的数据,却发现其他事务插入了满足其查询条件的新数据,这种现象叫“幻读”。

在一个事务中读取到了别的事务插入的数据,导致前后不一致。

如:丙存款100元未提交,这时银行做报表统计account表中所有用户的总额为500元,然后丙提交了,这时银行再统计发现帐户为600元了,造成虚读同样会使银行不知所措,到底以哪个为准。

幻读是指当事务不是独立执行时发生的一种现象。
事务A读取与搜索条件相匹配的若干行。事务B以插入或删除行等方式来修改事务A的结果集,然后再提交。
幻读是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,比如这种修改涉及到表中的“全部数据行”。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入“一行新数据”。那么,以后就会发生操作第一个事务的用户发现表中还存在没有修改的数据行,就好象发生了幻觉一样.一般解决幻读的方法是增加范围锁RangeS,锁定检索范围为只读,这样就避免了幻读。
在数据库定义的四种 隔离级别 中, 最高隔离级别SERIALIZABLE_READ可以保证不出现幻读的问题。

注:Mysql默认的是REPEATABLEREAD级别。另外,随着事务的隔离级别越高,并发执行的性能就越低,所以适当选择级别并结合业务需求来选定级别设置


MySQL锁概述
相对其他数据库而言,MySQL的锁机制比较简单,其最显著的特点是不同的存储引擎支持不同的锁机制。比如,
MyISAM和MEMORY存储引 擎采用的是表级锁(table-level locking);
InnoDB存储引擎既支持行级锁(row-level locking),也支持表级锁,但默认情况下是采用行级锁。
BDB存储引擎采用的是页面锁(page-level locking),但也支持表级锁;
MySQL这3种锁的特性可大致归纳如下。
开销、加锁速度、死锁、粒度、并发性能
l         表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
l         行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
l         页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。
从上述特点可见,很难笼统地说哪种锁更好,只能就具体应用的特点来说哪种锁更合适!仅从锁的角度来说:
表级锁更适合于以查询为主,只有少量按索引条件更新数据的应用,如Web应用;
行级锁则更适合于有大量按索引条件并发更新少量不同数据,同时又有并发查询的应用,如一些在线事务处理 (OLTP)系统。

那么mysql究竟有哪几种类锁呢?

一、按操作划分,可分为DML锁、DDL锁

二、按锁的粒度划分,可分为表级锁、行级锁、页级锁(mysql BDB支持)

三、按锁级别划分,可分为共享锁、排他锁

四、按加锁方式划分,可分为自动锁、显示锁

五、按使用方式划分,可分为乐观锁、悲观锁

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

共享锁 

共享锁又称读锁,是读取操作创建的锁。其他用户可以并发读取数据,但任何事务都不能对数据进行修改(获取数据上的排他锁),直到已释放所有共享锁。

如果事务T对数据A加上共享锁后,则其他事务只能对A再加共享锁,不能加排他锁。获准共享锁的事务只能读数据,不能修改数据。

  共享锁就是只针对update时候加锁,在未对update操作提交之前,其他事务只能够获取最新的记录但不能够update操作。 
  举个例子说吧,假如说事务A读到一条记录,并且修改了该条记录的一个字段的值a改成了b,正在这个时候事务B也读到了这条记录并且获取的A事务修改后的字段值b,事务B想把该字段的值改为c,在此时事务A的修改操作并未提交。那对不起了事务B,因为是事务A先对该记录加锁的,你此时还没有对该条记录上锁的权限哦,不要急哦,等事务A提交之后你就可以改了。

SELECT ... LOCK IN SHARE MODE;

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


排他锁 

排他锁又称写锁,如果事务T对数据A加上排他锁后,则其他事务不能再对A加任任何类型的封锁。获准排他锁的事务既能读数据,又能修改数据。
一开始就对记录上锁了,在本事务未提交之前别的事务无权进行任何操作。 
还是用上个例子吧,事务A查到一个记录,并修改了其中的一个字段a的值为b,这个时候事务B来获取这条记录,我靠,怎么获取不到呢?原来是排他锁在作怪,这是什么情况。原来排他锁在获取记录的时候就对这条记录上了锁,而且别的事务连获取这条记录的权限都没有,更别提要修改了,必须等到事务A提交了事务释放了锁之后别的事务才能够获取到这条记录。

SELECT ... FOR UPDATE;

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

这两种锁各有利弊,具体使用要看你项目的具体业务的。

  • 假如你的项目对并发很高对效率要求很高,那么你该选用共享锁,因为在别的事务对某些记录上锁后在事务未提交之前其他事务是有权限去查看的,但是当出现意外导致事务回滚时候,其他事务会多进行一步操作,那就是重新获取这些对象了,不过这比排队等锁要好多了。
  • 假如你的项目并发数不是很多,同时对整个业务的原子操作要求很高这个时候排他锁是很不错的选择。

意向锁

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

意向共享锁(IS):表示事务准备给数据行加入共享锁,也就是说一个数据行加共享锁前必须先取得该表的IS锁

意向排他锁(IX):类似上面,表示事务准备给数据行加入排他锁,说明事务在一个数据行加排他锁前必须先取得该表的IX锁。

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

对于insert、update、delete,InnoDB会自动给涉及的数据加排他锁(X);对于一般的Select语句,InnoDB不会加任何锁,事务可以通过以下语句给显示加共享锁或排他锁。

共享锁:SELECT ... LOCK IN SHARE MODE;

排他锁:SELECT ... FOR UPDATE;


mysql的锁的级别的区别

mysql锁的级别分为三类:页级锁,表级锁,行级锁。 
那有三个级别的锁,mysql究竟用的是哪个级别的呢?这个还得看你用的是什么mysql的引擎了 
mysql大概有下面几个数据库引擎和对应的锁级别:

  • MyISAM引擎:使用的是表级锁。理解为锁住整个表,可以同时读,写不行。
  • MEMORY/heap:使用的是表级锁。理解为锁住整个表,可以同时读,写不行。
  • BDB:使用的是页级锁,它也支持表级锁。一次锁定相邻的一组记录。
  • InnoDB:使用的是行级锁,它也支持表级锁。单独的一行记录加锁 。

三种锁的特性可大致归纳如下:

  • 1) 表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。分为表共享读锁(共享锁)与表独占写锁(排他锁)。
  • 2) 行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。 行级锁分为共享锁 和 排他锁。
  • 3) 页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。 
    这里我们主要讨论InnoDB存储引擎,也谈论的是行级锁,一般的在秒杀系统中我们会对商品库存使用行级锁,因为秒杀的时候库存是一个很重要的数据,我们在创建数据库的表时可能会出现下面这样的设置:
ENGINE = InnoDB AUTO_INCREMENT=10 DEFAULT CHARACTER SET = utf8 comment='用户表'
  • 1

将引擎设置为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自动加的,不需要用户干预。

对于insert、update、delete,InnoDB会自动给涉及的数据加排他锁(X);对于一般的Select语句,InnoDB不会加任何锁,事务可以通过以下语句给显示加共享锁或排他锁。

共享锁:SELECT … LOCK IN SHARE MODE;

排他锁:SELECT … FOR UPDATE;

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

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

  • 在不通过索引条件查询的时候,InnoDB 确实使用的是表锁,而不是行锁。
  • 由于 MySQL 的行锁是针对索引加的锁,不是针对记录加的锁,所以虽然是访问不同行 的记录,但是如果是使用相同的索引键,是会出现锁冲突的。应用设计的时候要注意这一点。
  • 当表有多个索引的时候,不同的事务可以使用不同的索引锁定不同的行,另外,不论 是使用主键索引、唯一索引或普通索引,InnoDB 都会使用行锁来对数据加锁。
  • 即便在条件中使用了索引字段,但是否使用索引来检索数据是由 MySQL 通过判断不同 执行计划的代价来决定的,如果 MySQL 认为全表扫 效率更高,比如对一些很小的表,它 就不会使用索引,这种情况下 InnoDB 将使用表锁,而不是行锁。因此,在分析锁冲突时, 别忘了检查 SQL 的执行计划,以确认是否真正使用了索引。
行级锁的优缺点

行级锁定的优点:

  • 当在许多线程中访问不同的行时只存在少量锁定冲突。
  • 回滚时只有少量的更改。
  • 可以长时间锁定单一的行。

行级锁定的缺点:

  • 比页级或表级锁定占用更多的内存。
  • 当在表的大部分数据上使用时,比页级或表级锁定速度慢,因为你必须获取更多的锁。如果你在大部分数据上经常进行GROUP BY操作或者必须经常扫描整个表,比其它锁定明显慢很多。

行级锁的死锁

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

在MySQL中,行级锁并不是直接锁记录,而是锁索引。索引分为主键索引和非主键索引两种,如果一条sql语句操作了主键索引,MySQL就会锁定这条主键索引;如果一条语句操作了非主键索引,MySQL会先锁定该非主键索引,再锁定相关的主键索引.在UPDATE、DELETE操作时,MySQL不仅锁定WHERE条件扫描过的所有索引记录,而且会锁定相邻的键值,即所谓的next-key locking。

当两个事务同时执行,一个锁住了主键索引,在等待其他相关索引。另一个锁定了非主键索引,在等待主键索引。这样就会发生死锁。

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

有多种方法可以避免死锁,这里只介绍常见的三种

  • 1、如果不同程序会并发存取多个表,尽量约定以相同的顺序访问表,可以大大降低死锁机会。

  • 2、在同一个事务中,尽可能做到一次锁定所需要的所有资源,减少死锁产生概率;

  • 3、对于非常容易产生死锁的业务部分,可以尝试使用升级锁定颗粒度,通过表级锁定来减少死锁产生的概率;




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值