数据库事务
事务(Transaction)是并发控制的基本单位。所谓的事务,它是一个操作序列,这些操作要么都执行,要么都不执行,它是一个不可分割的工作单位。
(数据库引擎innoDB是支持事务的(默认每一条sql语句为一个事务),MyISAM不支持事务。)
在关系数据库中,一个事务可以是一条SQL语句、一组SQL语句或整个程序。
开始事务:BEGIN TRANSACTION(事务)
提交事务:COMMIT TRANSACTION(事务)
回滚事务:ROLLBACK TRANSACTION(事务)
事务的四大属性:ACID
1.原子性(Atomicity): 事务开始后的所有操作,要么全部做完,要么全部不做,不可以停滞在中间环节。事务执行过程中若出错,会回滚到事务开始前的状态,所有的操作就像没有发生一样。事务是一个不可分割的整体。
2.一致性(Consistency): 事务开始前和结束后,数据库的完整性约束没有被破坏。
3.隔离性(Isolation): 同一时间,只允许一个事务请求同一数据,不同的事务之间彼此没有任何干扰。(一个事务内部的操作及正在操作的数据必须封装起来,不被其它企图进行修改的事务看到)
4.持久性(Durability): 事务完成后,事务对数据库的所有更新将被保存到数据库,不能回滚。
原子性是事务隔离的基础,隔离性和持久性是手段,最终目的是为了保持数据的一致性。
事务的并发问题(丢失更新、脏读、不可重复读、幻读)
1、丢失更新:如果两个事务A和B都要更新数据库一个字段,并同时获得相同数据,然后在各自事务中同时修改了该数据,那么先提交的事务A更新会被后提交事务B的更新给覆盖掉,这种情况事务A的更新就被覆盖掉了、丢失了。
2、脏读:事务A读取了事务B更新(但未提交)的数据,然后B回滚操作,那么A读取到的数据是脏数据;
3、不可重复读:事务 A 中多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果不一致。
4、幻读:第一个事务对一个表中的数据进行了修改,比如这种修改涉及到表中的“全部数据行”。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入“一行新数据”。那么,以后就会发生操作第一个事务的用户发现表中还存在没有修改的数据行,就好象发生了幻觉一样。
小结:不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表。
四种锁
从数据库系统的角度来看,锁分为共享锁和排他锁,从程序员的角度看,锁分为悲观锁和乐观锁。
共享锁(S锁, 读锁)
共享锁锁定的资源可以被其它用户读取,但其它用户不能修改它。在SELECT 命令执行时,SQL Server 通常会对对象进行共享锁锁定。通常加共享锁的数据页被读取完毕后,共享锁就会立即被释放。
排他/独占锁(X锁,写锁)
独占锁锁定的资源只允许进行锁定操作的程序使用,其它任何对它的操作均不会被接受。执行数据更新命令,即INSERT、UPDATE 或DELETE 命令时,SQL Server 会自动使用独占锁。但当对象上有其它锁存在时,无法对其加独占锁。独占锁一直到事务结束才能被释放。
共享锁可以一层套一层得上锁,但排他锁只能加一个。
乐观锁
总是认为不会产生并发问题,每次去取数据的时候总认为不会有其他线程对数据进行修改,因此不会上锁,但是在更新时会判断其他线程在这之前有没有对数据进行修改,一般会使用版本号机制或CAS操作实现。
version方式:一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。
悲观锁
总是假设最坏的情况,每次取数据时都认为其他线程会修改,所以都会加锁(读锁、写锁、行锁等),当其他线程想要访问数据时,都需要阻塞挂起。可以依靠数据库实现,如行锁、读锁和写锁等,都是在操作之前加锁,在Java中,synchronized的思想也是悲观锁。
根据范围,锁还可以划分成行级锁和表锁。
四个隔离级别
READ UNCOMMITTED(读未提交数据):允许事务读取未被其他事务提交的变更数据,会出现脏读、不可重复读和幻读问题。
READ COMMITTED(读已提交数据):只允许事务读取已经被其他事务提交的变更数据,可避免脏读,仍会出现不可重复读和幻读问题。
REPEATABLE READ(可重复读):确保事务可以多次从一个字段中读取相同的值,在此事务持续期间,禁止其他事务对此字段的更新,可以避免脏读和不可重复读,仍会出现幻读问题。
SERIALIZABLE(序列化/串行化):它要求事务序列化执行,事务只能一个接着一个地执行,但不能并发执行。确保事务可以从一个表中读取相同的行,在这个事务持续期间,禁止其他事务对该表执行插入、更新和删除操作,可避免所有并发问题,但性能非常低。
隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。对于多数应用程序,可以优先考虑把数据库系统的隔离级别设为Read Committed,它能够避免脏读取,而且具有较好的并发性能。尽管它会导致不可重复读、虚读和第二类丢失更新这些并发问题,在可能出现这类问题的个别场合,可以由应用程序采用悲观锁或乐观锁来控制。
MySQL支持四种事务隔离级别,其中REPEATABLE READ为默认事务隔离级别。