事务
事务特性
ACID:原子性、一致性、隔离性和持久性。
- 原子性:最小执行单位,不可中断不可分割,要么成功要么失败。
- 一致性:逻辑上的一致性,所有操作符合现实期望的。
如A给B转账,不论转账的事务操作是否成功,其两者的存款总额不变。
- 隔离性:并发事务之间相互隔离;
- 持久性:事务做出的更改其影响是持久的。
例子: 小明给小红转账1000元;那么小明的账户余额会减少1000元,而小红的账户余额会增加1000元。
事务: 小明给小红转账1000元
原子性:小明的账户余额会减少1000元,而小红的账户余额会减少1000元是不可中断的。也就是说不会存在小明的账户余额减少1000元,而小红的账户余额却没有增加1000元;
一致性:小明和小红账户的总金额不变;
隔离性:同时,铁柱也在给小明转账;两个转账操作是互相隔离的。
持久性:转账之后账户余额是真的变化了(该少的少,该多的多)。
隔离级别
隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
-
即要达到这么一种效果:
-
对于任意两个并发的事务
T1
和T2
,在事务T1
看来,T2
要么在T1
开始之前就已经结束,要么在T1
结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行。
- 如果两个事务不触及相同的数据,它们可以安全地并行(parallel) 运行,因为两者都不依赖于另一个。
- 当一个事务读取由另一个事务同时修改的数据时,或者当两个事务试图同时修改相同的数据时,并发问题(竞争条件)才会出现。出于这个原因,数据库一直试图通过提供 事务隔离(transaction isolation) 来隐藏应用程序开发者的并发问题。
读未提交:在人家进行修改的时候,进去读了;出现脏读;解决方案:写的时候,别人不能读也不能写。(写锁)
读已提交:在你读的时候,别人修改了数据;出现不可重复读;解决方案:读的时候,别人不能写。(读锁)
幻读:一个事务(同一个read view)在前后两次查询同一范围的时候,后一次查询看到了前一次查询没有看到的行
隔离级别 | 脏读 | 不可重读 | 幻读 |
---|---|---|---|
读未提交 read-uncommitted | Y | Y | Y |
读已提交 read-committed | x | Y | Y |
可重复读 | x | x | Y |
可串行化 | x | x | x |
隔离级别 | 问题 | 解释 | 解决方案 |
---|---|---|---|
读未提交 read-uncommitted | 脏读 | 别人正在写的时候,去读相应的信息 | 写的时候不能读(写锁) |
读已提交 read-committed | 不可重复读 | 正在读的过程中,别人跑来修改相应的信息 | 读的时候不能修改(读锁) |
可重复读 | 幻读 | 在进行查询某个范围(如:查询某个表中记录数目),有人插入或删除了记录;幻读专指新插入的行。 | 行锁只能锁住行,即使把所有的行记录都上锁,也阻止不了新插入的记录;MySql使用间隙锁解决该问题 |
可串行化 | 效率低 | 将所有事务排队,一个一个的解决 | – |
一个关于脏读的小笑话:
你看到一桶水,用勺子去舀,在你伸手去舀水时,有个人动作比你快,在往里倒屎,不管他倒没倒完,你舀出来的一定有屎。
MySQL
InnoDB
存储引擎默认支持 REPEATABLE_READ
(可重复读),但由于其使用了Next-Key Lock锁算法,因此可以避免幻读的产生。select @@tx_isolation
可查看隔离级别:
隔离级别的底层实现
锁
排他锁又称写锁:若事务T对数据对象A加上X锁,事务T可以读A也可以修改A,其他事务不能再对A加任何锁,直到T释放A上的锁。这保证了其他事务在T释放A上的锁之前不能再读取和修改A。
共享锁又称读锁:若事务T对数据对象A加上S锁,则事务T可以读A但不能修改A,其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S锁。这保证了其他事务可以读A,但在T释放A上的S锁之前不能对A做任何修改。
可参考操作系统中经典问题:读者写者问题。
表级锁
每次锁定的是一张表的锁机制就是表级别锁定(table-level)。它是MySQL各存储引擎中粒度最大的锁定机制。
使用表级锁定的主要有MyISAM,MEMORY,CSV等一些非事务性存储引擎。
MySQL的表级锁有两种类型:表共享读锁(Table Read Lock)和表独占写锁(Table Write Lock)。
-
锁模式的兼容性:
- 对MyISAM表的读操作,不会阻塞其他用户对同一表的读请求,但会阻塞对同一表的写操作;
- 对MyISAM表的写操作,则会阻塞其他用户对同一表的读和写操作;
MyISAM表的读操作与写操作之间,以及写操作之间是串行的。当一个线程获得对一个表的写锁后,只有持有锁的线程可以对表进行更新操作。其他线程的读、写操作都会等待,直到锁被释放为止。
-
加锁
- 在执行查询语句(select)前,会自动给涉及的所有表加读锁;
-
在执行更新操作(update、delete、insert等)前,会自动给涉及的表加写锁。这个过程并不需要用户干预,因此不需要直接用lock table命令给MyISAM表显式加锁。
三分钟了解Mysql的表级锁——《深究Mysql锁》
行级锁 +
每次锁定的是一行数据的锁机制就是行级别锁定(row-level)。
使用行级锁定的主要有InnoDB存储引擎,以及MySQL的分布式存储引擎NDBCluster
-
算法
- Record Lock(行级锁 ):单个记录上的锁;
- Gap Lock( 间隙锁):锁定一个范围,不包括记录本身。(解决幻读问题而引入)
- Next-key lock:(Record+Gap)锁定一个范围,包含记录本身。
索引
MySQL索引使用的数据结构主要是 B+树和哈希索引。
存储引擎
MySQL中的数据用各种不同的技术存储在文件(或者内存)中。这些技术中的每一种技术都使用不同的存储机制、索引技巧、锁定水平并且最终提供广泛的不同的功能和能力。通过选择不同的技术,你能够获得额外的速度或者功能,从而改善你的应用的整体功能。百度百科
-
InnoDB
- 是Mysql的默认存储引擎,用于事务处理应用程序,支持外键。如果应用对事务的完整性有比较高的要求,在并发条件下要求数据的一致性,数据操作除了插入和查询意外,还包含很多的更新、删除操作,那么InnoDB存储引擎是比较合适的选择。InnoDB存储引擎除了有效的降低由于删除和更新导致的锁定, 还可以确保事务的完整提交和回滚,对于类似于计费系统或者财务系统等对数据准确性要求比较高的系统,InnoDB是最合适的选择。 MyISAM
- 如果应用是以读操作和插入操作为主,只有很少的更新和删除操作,并且对事务的完整性、并发性要求不是很高,那么选择这个存储引擎是非常合适的。 MEMORY
- 将所有数据保存在RAM中,在需要快速定位记录和其他类似数据环境下,可以提供几块的访问。MEMORY的缺陷就是对表的大小有限制,太大的表无法缓存在内存中,其次是要确保表的数据可以恢复,数据库异常终止后表中的数据是可以恢复的。MEMORY表通常用于更新不太频繁的小表,用以快速得到访问结果。 MERGE
- 用于将一系列等同的MyISAM表以逻辑方式组合在一起,并作为一个对象引用他们。MERGE表的优点在于可以突破对单个MyISAM表的大小限制,并且通过将不同的表分布在多个磁盘上,可以有效的改善MERGE表的访问效率。这对于存储诸如数据仓储等VLDB环境十分合适。
InnoDB和MyISAM的区别
- InnoDB支持事务,MyISAM不支持,对于InnoDB每一条SQL语言都默认封装成事务,自动提交,这样会影响速度,所以最好把多条SQL语言放在begin和commit之间,组成一个事务;
特点 | InnoDB | MyISAM |
---|---|---|
事务 | 支持,InnoDB每一条SQL语言都默认封装为事务,自动提交;这样会影响速度,所以最好把多条SQL语言放在begin和commit之间,形成一个事务 | – |
外键 | 支持 | – |
聚集索引 | 是 | – |
锁 | 行级锁 | 表级锁 |
唯一索引 | 必须有唯一索引(如主键)(用户没有指定的话会自己构建一个隐藏列Row_id来充当默认主键) | 可以没有 |
存储文件 | frm:表定义文件,ibd:数据文件 | frm:表定义文件,myd:数据文件,myi:索引文件 |
全文索引 | – | 支持 |
压缩后查询 | – | 支持 |
表的行数 | 不保存表的具体行数,执行select count(*) from table 时需要全表扫描 | MyISAM用一个变量保存了整个表的行数,执行上述语句时只需要读出该变量即可,速度很快(注意不能加有任何WHERE条件 |
MyISAM
InnoDB
如何选择
- 是否支持事务;
- 绝大多数是读操作,选MyISAM;
- 系统崩溃后,MyISAM恢复困难;
大表优化
- 限定数据范围(条件语句)
- 读写分离(生成一个副本(从库),主库负责写,从库负责读)
- 垂直分离( 按照列划分)
- 水平分离(按照行划分)