事务及其ACID属性:
事务是由一组sql语句组成的逻辑处理单元,具有4个属性
原子性:事务是一个原子操作单元,其对数据的修改,要么都执行,要么都不执行
一致性:事务开始和完成的时候,数据都必须保持一致。事物的修改必须保持数据的完整性,事务结束后所有的内部数据结构(B+树,双向链表)多必须是正确的
隔离性:数据库系统提供一定的隔离机制,保证事务在不受外界并发操作影响的环境中执行。意味着事务处理过程中的中间状态对外部是不可见的
持久性:事务完成之后,对数据的修改是永久性的
并发事务处理带来的问题:
更新丢失:两个或多个事务同时选择一行并更新该行的数据,最后的更新覆盖了由其他事务做的更新。
脏读:一个事务正在对一条记录进行修改,在这个事务提交之前,其他事务过来了读取了脏数据并进一步做了处理,就会产生未提交的数据依赖关系。
不可重复读:一个数据在读取某些数据后的某个时间,再次读取以前读过的数据,会发现其读出的数据发生了变化或者被删除
幻读:一个事务按相同的查询条件重新读取以前检索过的数据,发现其他事务插入了满足其查询条件的新数据
事务隔离级别:
更新丢失问题通常是应该完全避免的,并不能只靠数据库的事务控制器解决,需要应用程序对要更新的数据加必要的锁来解决,防止更新丢失是应用的责任
脏读,不可重复读,幻读属于数据库读一致性问题,必须由数据库提供一定的事务隔离机制解决
1. 在读取数据前加锁
2.不加锁,通过数据多版本并发控制
四种事务隔离级别:
表锁:开销小,加锁快,不会出现死锁,并发度低,容易锁冲突
行锁:开销大,加锁慢,会出现死锁,并发度高,不容易锁冲突
InnoDB使用两种类型的行锁:
共享锁S(锁的是行):称为读锁,就是说事务1对数据A加了S锁,事务1只能读不能修改A,其他事务也一样只能读不能写,只能加S锁不能加X锁,除非事务1释放S锁
排他锁X(du'de锁的是行):称为写锁,事务1对数据加了X锁,能读能写,其他事务不能读和写,不能加任何锁
意向共享锁IS(锁的是表):通知数据库要加什么表并对表进行加锁,如果需要对A进行加共享锁,先对该表加意向共享锁后再对A加共享锁
意向排他锁IX(锁的是表):同上
意向共享锁和意向排他锁都是由数据库加的
InnoDB行锁是通过给索引加锁来实现的,只有通过索引条件检索数据才是使用行锁,否则使用表锁
间隙锁:
当我们用范围t条件而不是相等条件检索数据时,并请求了共享锁或者排他锁,InnoDB会给符合条件的已有数据记录的索引项加锁,对于在范围但是不存在的记录,会加间隙锁
目的:防止幻读,满足相关隔离级别的要求。因为有可能在读的过程其他事务插入了新的数据,就会产生幻读。
但这会造成严重的锁等待,因此要尽量使用相等条件来访问更新数据,避免使用范围条件
什么时候使用表锁:
对于InnoDB表,在绝大部分情况下都应该使用行级锁,因为事务和行锁往往是我们之所以选择InnoDB表的理由。但在个别特殊事务中,也可以考虑使用表级锁。
第一种情况是:事务需要更新大部分或全部数据,表又比较大,如果使用默认的行锁,不仅这个事务执行效率低,而且可能造成其他事务长时间锁等待和锁冲突,这种情况下可以考虑使用表锁来提高该事务的执行速度。
第二种情况是:事务涉及多个表,比较复杂,很可能引起死锁,造成大量事务回滚。这种情况也可以考虑一次性锁定事务涉及的表,从而避免死锁、减少数据库因事务回滚带来的开销。
当然,应用中这两种事务不能太多,否则,就应该考虑使用MyISAM表了。
避免死锁的方法:
(1)在应用中,如果不同的程序会并发存取多个表,应尽量约定以相同的顺序来访问表,这样可以大大降低产生死锁的机会。
(2)在程序以批量方式处理数据的时候,如果事先对数据排序,保证每个线程按固定的顺序来处理记录,也可以大大降低出现死锁的可能。
(3)在事务中,如果要更新记录,应该直接申请足够级别的锁,即排他锁,而不应先申请共享锁,更新时再申请排他锁,因为当用户申请排他锁时,其他事务可能又已经获得了相同记录的共享锁,从而造成锁冲突,甚至死锁。