事务能够是一个很是简单的SQL构成,也能够是一组复杂的SQL语句构成。事务是访问而且更新数据库中数据的一个单元,在事务中的操做,要么都修改,要么都不作修改,这就是事务的目的,也是事务模型区别于其余模型的重要特征之一。 事务的原子性:原子是不可分割的,事务不可分割(没有commit数据不能被读到).java 事务的持久性:在commit以后,不能丢数据.(就是在提交后,数据必须落盘redo落盘).python 事务的隔离性:在数据库里面,各个事务之间不能互相影响.mysql 事务的一致性:事务先后,不能违反mysql的约束.sql |
1.原子性(Atomicity)数据库
原子性是指是事务是不可分割的一部分,一个事务内的任务要么所有执行成功,要么所有不执行,不存在执行一部分的状况。 能够将整个取款流程当作原子操做,要么取款成功,要么取款失败。缓存 1. 咱们可使用取钱的例子,来说解这一特性安全 2. 登陆ATM机器session 3. 从远程银行的数据库中,获取帐户的信息并发 4. 用户在ATM上输入欲取出的金额mvc 5. 从远程银行的数据库中,更新帐户信息 6. ATM机器出款 7. 用户取钱 整个取钱操做,应该视为原子操做,要么都作,要么都不作,不能用户钱还没出来,可是银行卡的钱已经被扣除了。使用事务模型能够保证该操做的一致性 |
是指事务的完整性约束没有被破坏,若是遇到了违反约束的状况,数据库会被自动回滚。一致性是指数据库从一种状态转变为另外一种状态。在开始事务和结束事务后,数据库的约束性没有被破坏。例如,数据库表中的用户ID列为惟一性约束,即在表中姓名不能重复. |
事物的隔离性要求每一个事务的操做,不会受到另外一个事务的影响。隔离状态执行事务,使他们好像是系统在给定时间内执行的惟一操做.这种属性有时称为串行化. |
事务一旦提交,那么结果就是持久性的,不会被回滚。即便发生宕机,数据库也能够作数据恢复。 |
说明:隔离性经过锁实现,原子性、一致性、持久性经过数据库的redo和undo来完成
支持事务的数据库:InnoDB、NDBCluster、TokuDB
不支持事务的数据库:MyISAM、MEMORY
5.事物的实现
重作日志(redo)用来实现事务的持久性,有两部分组成,一是内存中的重作日志,一个是硬盘上的重作日志文件。innodb是支持事务的存储引擎,经过日志先行WAL,来实现数据库的事务的特性,在一个事务提交的时候,并非直接对内存的脏数据进行落盘,而是先把重作日志缓冲中的日志写入文件,而后再提交成功。这样保证了即便在断电的状况下,依然能够依靠redo log进行数据的恢复与重作。只要是提交的事务,在redo中就会有记录,数据库断电后重启,数据库会对提交的事务,可是没有写入硬盘的脏数据,利用redo来进行重作。 还要一个保证事务的是undo,undo有两个做用: 1.实现事务的回滚 2.实现mvcc的快照读取 redo是物理逻辑日志,计算页的物理修改操做. undo是逻辑记录,记录了行的操做内容. 两阶段提交:先写redo -buffer再写binlog 并落盘最后落盘redo. |
6.lsn号
LSN(log sequence number)日志序列号 查看LSN信息: show engine innodb status\G; LSN实际上对应日志文件的偏移量,新的LSN=旧的LSN + 写入的日志大小. 日志文件刷新后,LSN不会进行重置 Log sequence number:当前系统LSN最大值,新的事务日志LSN将在此基础上生成(LSN1+新日志的大小) Log flushed up to:当前已经写入日志文件的LSN Pages flushed up to:当前最旧的脏页数据对应的LSN,写Checkpoint的时候直接将此LSN写入到日志文件 Last checkpoint at:当前已经写入Checkpoint的LSN |
redo做用 Redo介绍: 1. DML操做致使的页面变化,均须要记录Redo日志(物理日志) 2. 在页面修改完成以后,在脏页刷出磁盘以前,写入Redo日志; 3. 日志先行(WAL),日志必定比数据页先写回磁盘; 4. 聚簇索引/二级索引/Undo页面修改,均须要记录Redo日志; 为了管理脏页,在 Buffer Pool 的每一个instance上都维持了一个flush list,flush list 上的 page 按照修改这些page 的LSN号进行排序。所以按期作redo checkpoint点时,选择的 LSN 老是全部 bp instance 的 flush list 上最老的那个page(拥有最小的LSN)。因为采用WAL的 策略,每次事务提交时须要持久化 redo log 才能保证事务不丢。而延迟刷脏页则起到了合并屡次修改的效果,避免频繁写数据文件形成的性能问题。 REDO的做用:提升性能和作crash recovery 提升性能: 1. 日志用来记录buffer pool中页的page修改的,每次数据提交只要写redo日志就能够,不须要每次都写脏页。 2. 一般一个数据页是16KB,若是不写日志,每次的写入仍是16kb,即便修改不多数据,仍然要所有落盘,性能影响很是严重。 3. 若是没有日志,每次都会刷脏页,脏页的位置致使的IO是随机IO,而redo的数据页的大小是512字节,这样很是契合硬盘的块大小,能够进行顺序IO,这样能够保证顺序IO,同时能够大大提升IOPS 作crash recovery: 1. 数据库重启后,利用redo log进行数据库恢复工做,比对redolog LSN和数据页的LSN,若是数据页LSN低于REDO LOG LSN就会进行数据页实例恢复 |
1. 用于回滚事务 2. 保证mvcc多版本高并发控制 innodb把undo分为两类 1. 新增undo 2. 修改undo 分类依据就是是否须要作purge操做. insert在事务执行完成后,回滚记录就能够丢掉了。可是对于更新和删除操做而言,在完成事务后,还须要为MVCC提供服务,这些日志就被放到一个history list,用于MVCC以及等待purge。 undo日志的正确性是经过redo来保证的,因此在数据库恢复的时候,须要先恢复redo,在全部数据块都保证一致性的状况下,在进行undo的逻辑操做。 |
9.检查点(checkpoint)
检查点就是落脏页的点,本质是一个特殊的lsn. 检查点解决的问题: 1. 缩短数据库恢复时间 2. 缓冲池不够用的时候,刷新脏页到磁盘 3. 重作日志不够用的时候,刷新脏页 当数据库发生宕机的时候,数据库不须要恢复全部的页面,由于检查点以前的页面都已经刷新回磁盘了。故数据库只须要对检查点之后的日志进行恢复,这就大大减小了恢复时间。 检查点的类型: 检查点分为两种类型,一种是sharp检查点,一种是fuzzy检查点 sharp checkpoint落盘条件: 1. 关闭数据库的时候设置 innodb_fast_shutdown=1,在关闭数据库的时候,会刷新全部脏页到数据库内。 fuzzy checkpoint在数据库运行的时候,进行页面的落盘操做,不过这种模式下,不是所有落盘,而是落盘一部分数据。 Fuzzy落盘的条件: 1. master thread checkpoint: master每一秒或者十秒落盘 2. sync check point: redo 不可用的时候,这时候刷新到磁盘是从脏页链表中刷新的。 3. Flush_lru_list check point : 刷新flush list的时候 落盘的操做是异步的,所以不会阻塞其余事务执行。 检查点的做用: 缩短数据库的恢复时间 缓冲池不够用的时候,将脏页刷新到磁盘 重作日志不可用的时候,刷新脏页(循环使用redo文件,当旧的redo要被覆盖的时候,须要刷新脏页,形成检查点) |
10.两阶段提交
MySQL二阶段提交流程: 事务的提交主要分三个主要步骤: 1.Storage Engine(InnoDB) transaction prepare阶段:存储引擎的准备阶段,写redo-buffer 此时SQL已经成功执行,并生成xid信息及redo和undo的内存日志。 2.Binary log日志提交:写binlog并落盘. write()将binary log内存日志数据写入文件系统缓存。 fsync()将binary log文件系统缓存日志数据永久写入磁盘。 3.Storage Engine(InnoDB)内部提交:落盘redo日志. 修改内存中事务对应的信息,而且将日志写入重作日志缓冲。 调用fsync将确保日志都从重作日志缓冲写入磁盘。 一旦步骤2中的操做完成,就确保了事务的提交,即便在执行步骤3时数据库发送了宕机。 即binlog落盘成功,就算redo未落盘成功,那么事务也算是提交成功了. binlog落盘条件:参数sync_binlog: 0每秒落盘,1每次commit落盘 n 每n个事物落盘 此外须要注意的是,每一个步骤都须要进行一次fsync操做才能保证上下两层数据的一致性。步骤2的fsync参数由sync_binlog控制,步骤2的fsync由参数innodb_flush_log_at_trx_commit控制。(双1配置) 两阶段提交:先写redo -buffer再写binlog 并落盘最后落盘redo-buffer. 最终:mysql在落盘日志的时候,先落盘binlog,再落盘redo. |
当事务在binlog阶段crash,此时日志尚未成功写入到磁盘中,启动时会rollback此事务。 当事务在binlog日志已经fsync()到磁盘后crash,可是InnoDB没有来得及commit,此时MySQL数据库recovery的时候将会从二进制日志的Xid(MySQL数据库内部分布式事务XA)中获取提交的信息从新将该事务重作并commit使存储引擎和二进制日志始终保持一致。 总结起来讲就是若是一个事物在prepare log阶段中落盘成功,并在MySQL Server层中的binlog也写入成功,那这个事务一定commit成功。 |
12.事物隔离级别
隔离级别 事物 问题 缩写 READ UNCOMMITED 未提交读 脏读 RU READ COMMITED 提交读 幻读 RC REPEATEABLE READ 可重复读 RR SERIALIZABLE 序列化 |
使用RR级别的话,能够保证数据库没有幻读,可是在该模式下,会增长锁竞争,形成数据库并发能力的降低。在RC模式下,没有next_lock的存在,即便在没有索引的状况下,也很难形成大规模的锁表而致使的死锁问题。
13.自动提交参数设置
Mysql会自动提交 (mysql在mysql客户端是自动提交,可是java python里面不自动提交) 取消自动提交: set autocommit=off; |
14.查看mysql隔离级别
show variables like '%iso%';
|
15.修改事物隔离级别
设置全局参数: set global transaction isolation level read uncommitted; Set global transaction isolation level read committed; Set global transaction isolation level repeatable read; 设置会话级别参数: set session transaction isolation level read uncommitted; Set session transaction isolation level read committed; Set session transaction isolation level repeatable read; 查看全局的MySQL GLOBAL的配置的隔离级别。 global参数设置后,须要新开启session才能生效. |
RU --------- 产生脏读问题 RC ---------- 解决脏读问题,但产生幻读和不可重复读问题. RR --------- 避免幻读和不可重复读问题.,待容易锁等待. SERIALIZABLE -----------序列化,串行读写. 在 REPEATEABLE READ下,其余事务对于数据的修改(update,delete)不会影响本事务对于数据的读取,会避免幻读的产生,幻读就是在一个事务内,读取到了不一样的数据行数结果。 数据越安全,相对来讲,数据库的并发能力越弱(并不表明整体性能越弱)。 脏读(Drity Read):事务T1修改了一行数据,事务T2在事务T1提交以前读到了该行数据。 不可重复读(Non-repeatable read): 事务T1读取了一行数据。 事务T2接着修改或者删除了该行数据,当T1再次读取同一行数据的时候,读到的数据时修改以后的或者发现已经被删除。(针对update/delete操做). 在READ COMMITED下,未被提交的事务不会被读到,只有被提交的事务的数据,才会被读取到。(执行两次相同的SQL获得了不一样的结果。),这就形成了幻读和不可重复读问题. 幻读(Phantom Read): 事务T1读取了知足某条件的一个数据集,事务T2插入了一行或者多行数据知足了T1的选择条件,致使事务T1再次使用一样的选择条件读取的时候,获得了比第一次读取更多的数据集。(针对insert操做). 幻读和可重复读的区别: 幻读更多的是针对于insert来讲,即在一个事务之中,前后的两次select查询到了新的数据,新的数据来自于另外一个事务的insert,通常称之为幻读,经过gap_lock(间隙锁)来防止产生幻读(虽然record能够避免数据行被修改,可是却没法阻止insert,gap_lock锁定索引间隙,防止了在事务查询的范围内的insert状况) 在ru隔离级别下形成脏读,在rc隔离级别下形成幻读和不可重复读. 可重复读:通常是针对于update和delete来讲,可重复读采用了mvcc多版本控制来实现数据查询结果自己的不变。 |
5.事物隔离级别示例
1.ru(READ-UNCOMMITED 未提交读)Ru级别形成了脏读: session2能够读取到session1的没有提交的事务的数据(内存中没有提交的脏页). 脏读的发生至少在RU下,而目前几乎全部的数据库几乎都在RC级以上的隔离界别上。 脏读实例: 两个session修改隔离级别: set session transaction isolation level read uncommitted; session1 session2 begin; begin; insert into t1 values(null,'testru') select * from test1.t1;#查询到了testru数据,形成了脏读 commit; commit; ######################################################## 2.rc(read committed 已提交读)Rc级别解决了脏读.但会形成不可重复读和幻读.二者发生方式同样,但因为解决方法不一样而区分. 解决了脏读实例:(针对select.) 修改隔离级别: Set session transaction isolation level read committed; session1 session2 begin; begin; insert into test1.t1 values(null,'testrc'); select * from test1.t1;#没有查到 commit; select * from test1.t1; 查询到了 commit; ################################################################ 不可重复读:针对于update/delete来讲. 幻读:针对于insert来讲的. 幻读实例: Set session transaction isolation level read committed; ##################################################### session1 session2 begin; begin; select * from test1.t1;--7条 insert into t1 values() commit; select * from test1.t1 --8 commit; ##################################################### 不可重复读实例: Set session transaction isolation level read committed; session1 session2 begin; begin; select * from test1.t1 where name=’aa’;--找到1条 Update t1 set name=’bb’ where name=’aa’; commit; select * from test1.t1 where name=’aa’ --没有找到. commit; ################################################################### 加锁实例1:(加锁只针对指定的行,不影响update/insert操做) Set session transaction isolation level read committed; session1 session2 begin; begin; update test1.t1 set name='bbb' ; insert into test1.t1 values(null,'test_rr') 加了锁,但插入成功. commit; select * from test1.t1 1000w+1 Commit; ############################################################################# 3.rr(repeatable read 可重复读)RR隔离级别:可重复读 .repeatable read 在RR模式下GAP_LOCK是默认开启的. 避免幻读实例:(insert) set session transaction isolation level repeatable read; session1 session2 begin; begin; select * from test1.t1; insert into t1 values(null,'testrr'); commit; select * from test1.t1;查询不到testrr commit; select * from test1.t1;查询到testrr #################################################### 避免不可重复读实例:(update /delete) set session transaction isolation level repeatable read; session1 session2 begin; begin; select * from test1.t1; update test1.t1 set name='testrr_upd' where name='testrr' commit; select * from test1.t1;查询不到testrr_upd
commit; select * from test1.t1;查询到testrr_upd ################################################################### 锁等待实例:(必定会锁,update后,除了select,其余操做如insert/update都会被锁.) set session transaction isolation level repeatable read; session1 session2 begin; begin; update test1.t1 set name='ccc'; insert into test1.t1 values(null,'test_rr')插入被阻塞,进入锁等待状态 commit; commit; select * from test1.t1 1000w+1 ################################################################### |
MVCC在MySQL的InnoDB中的实现原理: 基于UNDO的多版本快照日志 MySQL InnoDB存储引擎,实现的是基于多版本的并发控制协议——MVCC (Multi-Version Concurrency Control) (注:与MVCC相对的,是基于锁的并发控制,Lock-Based Concurrency Control)。 1.MVCC的好处:读不加锁,读写不冲突.读不加锁,读写不冲突。在读多写少的OLTP应用中,读写不冲突是很是重要的,极大的增长了系统的并发性能,这也是为何现阶段,几乎全部的RDBMS,都支持了MVCC。 2.读的类型:快照读和当前读.在MVCC并发控制中,读操做能够分红两类:快照读 (snapshot read)与当前读 (current read)。 快照读: 读取的是记录的可见版本 (有多是历史版本),不用加锁。例如 select 就为快照读. 当前读:读取的是记录的最新版本,而且,当前读返回的记录,都会加上锁,保证其余事务不会再并发修改这条记录。例如删除/更新/删除操做。 过程: 在MVCC读取数据的过程当中,会先对目前的事务和数据行的事务号进行对比,若是发现事务行的事务版本号,已经增加,则说明该行数据已经被其余事务修改,那么就须要根据undo,读取undo内的历史版本的相关逻辑操做信息,而后根据逻辑操做信息构建符合当前查询的数据版本,而后返回结果集(在RC和RR模式下,对于UNDO构建数据块的版本选择不一样) 经过MVCC,虽然每行记录都须要额外的存储空间,更多的行检查工做以及一些额外的维护工做,但能够减小锁的使用,大多数读操做都不用加锁,读数据操做很简单,性能很好,而且也能保证只会读取到符合标准的行,也只锁住必要行。 3.一致性非锁定读一致性的非锁定读就是INNODB存储引擎经过行多版本控制的方式来读取当前执行时间数据库中的数据。若是读取的行正在执行DELETE或者UPDATE,这个时候读取操做不会等待所在行的锁的释放。INNODB这个时候会读取一个快照数据。(若读的数据加锁了,则读取其前一个版本的undo日志。) 一致性非锁定读取的原理是这样的: Innodb经过隐藏的回滚指针保存前一个版本的undo日志,经过当前记录加上undo日志能够构造出记录的前一个版本,从而实现同一记录的多个版本。 快照数据就是当前数据行的历史版本,每一个行记录可能有多个版本 在RC模式下,MVCC会一直读取最新的快照数据。 在RR模式下,MVCC会读取本事务开始时候的快照数据。 对于一致性非锁定读取,即便被读取的行已经SELECT ⋯ FOR UPDATE,也是能够进行读取的。
4.一致性锁定读取有些用户须要采用锁定读取的方式来进行读取保证数据的一致性。 手动在查询中添加锁 查询中使用S锁: select * from test1.t1 where id=1 lock in share mode; 查询中添加X锁: select * from test1.t1 where id=1 for update; 关于锁等待的参数:参数支持范围为Session和Global,而且支持动态修改 事务等待获取资源等待的最长时间,超过这个时间还未分配到资源则会返回应用失败。 设置锁等待时间参数: innodb_lock_wait_timeout 30 --单位秒. |
Mysql锁加在索引上. 锁的做用:将并行的事务变成串行的. 从锁的颗粒度来讲,锁分:表锁, 页锁, 行锁。 MySQL中锁的概念能够等同于:并发控制,序列化,隔离性. 这种用隔离性来描述锁,就是由于是事务ACID特性中的I,而锁就是用来实现事务一致性和隔离性的一种经常使用技术。 当数据库事务并发各自运行的时候,每一个事务的运行不受到其余事务的影响。 简单的加锁技术就是对对象加上一个锁,若访问该事务的时候,发现已经有锁,则等待该事务锁的释放。 经过多粒度锁定,保证了数据库中事务的并发性。 1.意向锁意向锁:打算向这个表里的数据加锁,会提早在表级别加一个意向锁,加在聚簇索引的根节点. 1. 揭示下一层级请求的锁的类型 2. IS:事物想要得到一张表中某几行的共享锁 3. IX:事物想要得到一张表中某几行的排他锁 4. InnoDB存储引擎中意向锁都是表锁 假如此时有 事物tx1 须要在 记录A 上进行加 X锁 : 1. 在该记录所在的数据库上加一把意向锁IX 2. 在该记录所在的表上加一把意向锁IX 3. 在该记录所在的页上加一把意向锁IX 4. 最后在该记录A上加上一把X锁 假如此时有 事物tx2 须要对 记录B (假设和记录A在同一个页中)加 S锁 : 1. 在该记录所在的数据库上加一把意向锁IS 2. 在该记录所在的表上加一把意向锁IS 3. 在该记录所在的页上加一把意向锁IS 4. 最后在该记录B上加上一把S锁 加锁是从上往下,一层一层 进行加的. 意向锁存在乎义: · 意向锁是为了实现多粒度的锁,表示在数据库中不但能实现行级别的锁,还能够实现页级别的锁,表级别的锁以及数据库级别的锁 · 若是没有意向锁,当你去锁一张表的时候,你就须要对表下的全部记录都进行加锁操做,且对其余事物刚刚插入的记录(游标已经扫过的范围)就无法在上面加锁了,此时就没有实现锁表的功能。 IX,IS是表级锁,不会和行级的X,S锁发生冲突。只会和表级的X,S发生冲突,行级别的X和S按照普通的共享、排他规则便可。因此只要写操做不是同一行,就不会发生冲突。 InnoDB 没有数据库级别的锁,也没有页级别的锁(InnoDB只能在表和记录上加锁),因此InnoDB的意向锁只能加在表上,即InnoDB存储引擎中意向锁都是表锁. 2.行锁(X和S锁)对于行锁,根据其做用类型,能够分为两类: 共享锁(S lock) ,容许读取一个数据,同时容许其余事务对该事务进行更改。 排他锁(X lock),容许删除或者更新一条数据,同时不容许其余事务对该事务进行操做。 锁兼容:当一行获取S锁的时候,也能够获取另外一个事务的S锁,这称之为锁兼容. |