tips:容易理解的模型性能往往性能不好,性能好的模型往往不容易理解
什么事事务?
事务看起来很简单,就三个命令begin transaction,commit,rollback,但是如果没有对这个的原理了解的话,就不会取舍。
事务的简介
事务的核心是锁与并发,同步控制。
优势:方便理解
劣势:性能较低
计算机就像是一个标准的打字机,cpu单位时间内只能做一件事,要么读,要么算,要么写,每个cpu只在单位时间做一件事。
version=version+1;一共为三步,先取出数据,加1,再写回去。而事务就是在读写算外再额外提供了一个同步块。
数据库的事务本质来说跟应用做的synchronize 和lock等同步是一样的,没有本质的差异,多个人操作同一块数据的时候,得保证只有一个线程进入事务。
事务单元
一段业务代码的逻辑,希望它就像我写出来的那样去运行,运行这段逻辑的过程中的时候,外边的人不参与这段逻辑运行,我有几个操作看起来是原子操作,不论有多少步,对于应用来说都是一步,通过一个bebgin transaction comit rollcbck 之间的操作都应该加同步访问。
多个事务单元的情况,都有史密斯,其他事务都得排队,这是一种事务单元一种非常重要的访问控制的方式:锁的方式
一组事务单元
Bob给Joe100块
Joe给Simith100块
Simith给Bob100块
事务单元之间有很多不同的关系,事务和事务之间有哪些关系,如果需要有一种方式来表述,那就是happen before的四种可能性。
事务单元之间的happen-before关系四种可能性,也就是事务冲突的可能性分别为
读写
写读
读读
写写
所有事务冲突的可能性可以抽象为这四种关系
在这之上还有个问题:
如何能够以最快的速度完成这些事务单元之间的关系?又能保证上面四种操作的逻辑顺序?有没有可能尽可能的让事务单元不出现冲突?
假设一个事务耗时1秒,60个事务串行要60秒,但如果并行的化只要1秒。
事务之间没有关联是最快的。
阿木大二定律,如果事务之间没有相关性,所有的事务都可以并行,系统的性能理论上是最优的。
要保证系统之间的这四种happen before的关系在逻辑上是有序的又想办法保证性能又要保证看起来一样,也就是提高系统的易用性的同时,又尽可能不损失系统的性能,尤其是不损失系统的吞吐。
除了转账这样的操作,有一些操作也是一个事务单元这也是一个事务单元
商品要建立一个基于GMT_modified的索引,alter table add index ...,原来的数据读一遍,在新的表写一遍,在读使用一张表的时候,要让原来的表不变,这样才能数据一致
从数据库中读取一行记录
向数据库中写入一行记录,同时更新这行记录的所有索引。
删除整张表
所以说数据库有很多的事务单元。
如何实现事务单元?
Two Phase lock,两段提交协议。
所有的数据库操作CRUD都是先读再写的操作,比如insert和delete,update都是先查再写的操作,每次查出数据时候数据库会再上面加锁。
对于insert读取数据,判断数据又没有,没有写入,又的话返回错误,如果并没有读,只有写那就没有事务操作。
比如最高级别的排它锁,都不会被其他人读到,无论有多少次更新,中间有多少个中间状态其他人都看不到。
当做完中间状态提交后,锁就解开了,所有系统都恢复了,就都可以看到。对于转账,要么开始我有钱你没钱,要么结束是你有钱我没钱。而中间我减钱和你加钱的过程中被锁隐藏掉了,这就是数据库中做事务最标准的方法,而X锁U锁读写锁很多锁都是对标准的排他锁的优化。
具体的事务实现思路
事务的实现方式-排队法
一大堆数据单线程处理是事务最简单最重要的解决方案
单线程来处理事务,例如nosql的REDIS。Redis的核心思路,所有数据都在内存里,单线程处理所有的put、get是效率最高的。
因为多线程是CPU模拟多线程的情况,多线程有上下文切换,在内存的情况下,而没有上下文切换效率最高,这个方案是最好的方案。
第二种简单的方案:排它锁
第三种方案:读写锁
对于读读场景的优化,一个只读的事务,比如只做查询。readwriteLock在读多写少的情况下可以搞定。
第三种方式:MVCC
MVCC是后来orcale提出来的
读是并行,写是串行
这是最优的解法,本质是copy on write。之前所有的版本都在里面,读的时候申请一个版本号,如果版本号比上面的小就可以获得,读读不阻塞的前提下,读写和读读也不阻塞。
什么时候事务需要多线程?
这在于下层使用的存储
内存操作的时候,IOPS非常高的系统,内存的申请和销毁非常快,内存可以动态申请大小,内存还可以随时动态开辟销毁一段空间。
磁盘IOPS很低,吞吐量高。SAS是几百M 传统的磁盘100~150M,意味着必须将大量的读和写攒成一个batch一起提交性能最高。
一个事务读算写在内存里很快完成,内存IOPS是磁盘的1000到1万倍,如果用内存的方式会来操作磁盘会系统性能上不去。所以要将大量的请求攒到一起提交。
方式:
异步,我的请求和线程不绑定,线程不block,处理你完了可以处理别人,也是一种多线程的场景,将大量不同人的请求提交到一个buffer里,再由buffer统一再读或者写。磁盘网络慢速设备,多线程或者=异步场景特别多,面对磁盘网络要面对使用多线程来处理这些事情。
事务基本的调优原则
不影响业务应用的情况下,尽可能减少锁的覆盖范围
Myisam 表锁->InnoDb 行锁
原位锁 -> MVCC多版本,也是某种意义上的减少锁的范围
增加锁上可并行的线程数
读锁写锁分离,允许并行读取数据。
选择正确锁类型
悲观锁 适合并发争抢中比较严重的场景
乐观锁 适合并发争抢中不严重的场景
乐观锁悲观锁定义:
悲关锁:系统一个请求进来后,请求的线程切换出去,正在处理数据的线程处理完后,notify被等待的线程,然后让线程回到这里重新竞争这把锁
乐观锁:进来后发现有人再做操作,不切换出去,不切换上下文,循环,对方把锁打开了,你进来,你持有这把锁,其他的人再做自旋
很多时候业务也会允许要悲观锁和乐观锁。
最重要的差异
悲观锁,请求读的时候发现有锁,请求去等待,锁处理完了,然后notify所有的等待,等待通知。
乐观锁,发现对方有锁,自旋,拿到后让其他请求自旋,等一段时间问你一次。
为什么悲观锁 适合并发争抢中比较严重的场景而乐观锁 适合并发争抢中不严重的场景?
因为每次切换出去,需要CPU开销,要清除内存页和寄存器,然后才能在外面等。
小结:
事务的ACID
原子性
一致性
隔离性(MVCC/SNAPSHO IOSLATION)
持久性(持久性保证策略)
单机事务的典型异常应对策略
事务的调优原则
。。。未完