事务(Transaction),一般是指要做的或所做的事情。在计算机术语中是指访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。事务通常由高级数据库操纵语言或编程语言(如SQL,C++或Java)书写的用户程序的执行所引起,并用形如begin transaction和end transaction语句(或函数调用)来界定。事务由事务开始(begin transaction)和事务结束(end transaction)之间执行的全体操作组成。
简单点说:完成一件事所需要执行的所有操作,如果其中一个操作没成功则回到原点,全部成功则提交。
下面简单介绍下事务的特性:
- 原子性:一个事务就是一个完整的整体,要么成功,要么失败;
- 一致性:事务从一个状态集体变为另一个状态;
- 隔离性:事务之间不存在相互干扰,开始执行不被其他因素影响;
- 持久性:(永久性),提交后结果永久保存,其他操作或者故障不会对其执行结果产生影响。
针对上述特性举个例子:去超市买东西,需要两个环节,选东西,结账带走,这俩步骤缺一不可(原子性),直接拿走东西不付钱或者付钱不拿东西均未完成交易(原子性,一致性);选好东西去结账时,别人不能从自己手里将东西抢走(隔离性);结账完成交易成功,这时你结账给超市的钱丢了或者自己买的东西损坏都不会对之前的交易结果产生影响(持久性)。
下面就是事务的隔离级别了:(提前声明,以下内容均为并发场景,不存在并发则不会存在以下问题)
借用网上一张流传非常广泛的图
没了解过一看有点蒙,下面具体介绍。
先介绍几个名词:
- 脏 读:事务A 两步,执行第一步更新完成数据,B读取该数据,此时A第二步执行失败回滚,B读取数据就会与数据库中 数据不一致,读取内容不正确。
- 不可重复读:事务A 两步,均对同一条数据操作,在两次读取数据数据结果中间,存在事务B对同一条数据执行更新操作,导致事务A两步读取结果不一致。
- 幻 读:事务A两步, 第一步读取存在5条记录,此时B插入2条数据,A第二步读取发现存在7条,两条是之前不存在的,就和出现幻觉一样。
mysql针对以上三个问题提供事务隔离级别,也就是针对以上问题提供的解决办法:
- 读未提交,以上问题均未解决;
- 读已提交:解决脏读问题,其他俩问题依然存在;
- 可重复读:解决脏读、不可重复读问题;
- 可串行化:以上问题全部解决,性能差;
在实际场景中直接设置为串行化性能太差,无法满足实际生产需求,故隔离级别一般设为读已提交,不可重复读和幻读在业务中处理,这就是咱们接下来介绍的锁。
表锁、行锁:mysql常见的锁机制
- 表锁:MyISAM、MEMORY
- 行锁:InnoDB
- 还存在另外一种特殊的页面锁:BDB -<这个存储引擎我工作中没用过
上面两种锁优劣势
表锁:加锁快,不存在死锁,锁定颗粒大,容易出现锁冲突,并发小;
行锁:加锁满,会出现死锁,锁定颗粒小,发生锁冲突概率低,并发高;
读锁(共享锁)、写锁(排它锁)
- 类似的还存在意向共享锁、意向排它锁,意向锁均mysql内部机制,是表锁,实际开发中不涉及
- 开发中会使用读锁,写锁,均行锁。
mysql自带的:
- InnoDB :执行写操作(update,insert,delete)前自动给涉及数据加写锁,select不加任和锁;InnoDB行锁是给索引项加锁来实现行锁的,这就意味着只有命中索引才会使用行锁,不然仍然是表锁;
- MyISAM:执行写操作添加涉及到的表加写锁,读操作前添加读锁(均表锁);
乐观锁、悲观锁
- 悲观锁:对一切事务操作保持保守态度,操作过程中相关数据均处于锁定状态。基本上是基于mysql本身锁机制实现,系统中及时实现也无法保证另外系统的干涉。
- mysql默认为autocommit模式,如果使用悲观锁,需修改为手动提交。
1、登录mysql,查看autocommit状态。 SHOW VARIABLES LIKE 'autocommit'; on :自动提交 1 offf:手动提交 0 2、mysql数据库怎么设置手动提交 把autocommit改成off; set @@autocommit=0; #值设置为0或者 off 都生效 3、mysql数据库怎么设置手动提交,启动数据库时自动设置autocommit=0。 在mysql目录中找到这个文件my.ini 在[mysqld]之后写上:autocommit=0 启动数据库。
- mysql默认为autocommit模式,如果使用悲观锁,需修改为手动提交。
- 乐观锁:相对悲观锁,乐观锁默认操作不会冲突,只有 在提交是才去进行是否冲突检测,如果冲突才会返回信息,让用户决定如何处理(回滚)。一般乐观锁处理方式:
- 数据积累增加一版本(version) 或者标记(sign)。通过对version或者sign的前后操作一致性进行比较,从而确定是否更新。(比如:读取数据时一同获取version值并记录,执行更新操作对version+1,如果读取时获取版本与上次版本不一致则表示过期。)
- 记录最后一次更新时间,与上面类似,如果时间发生变更则数据冲突。
- 总结:乐观锁悲观锁各有利弊。如果读多写少则建议使用乐观锁,可以省去锁消耗的性能。但如果冲突产生很多,反而会不停地回滚,消耗更多的性能,这样子悲观锁就优势了。实践中高并发场景较多,此时乐观锁要优于悲观锁。悲观锁会使每个线程等待时间变长,影响性能,乐观锁则可以一定程度上提高并发。
所有的技术均需结合实际生产环境进行性价比评估,努力寻找最优解,并不存在绝对的最优。