事务ACID属性

原子性(Atomicity)

事务是一个原子操作单元,其对数据的修改,要么全都执行,要么全都不执行。

一致性(Consistent)

在事务开始和完成时,数据都必须保持一致状态。这意味着所有相关的数据规则都必须应用于事务的修改,以保持数据的完整性。

隔离性(Isolation)

数据库系统提供一定的隔离机制,保证事务在不受外部并发操作影响的“独立”环境执行。这意味着事务处理过程中的中间状态对外部是不可见的,反之亦然。

持久性(Durable)

事务完成之后,它对于数据的修改是永久性的,即使出现系统故障也能够保持。

并发事务的问题

更新丢失(Lost Update)或脏写

当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,由于每个事务都不知道其他事务的存在,就会发生丢失更新问题——-最后的更新覆盖了由其他事务所做的更新

脏读(Dirty Reads)

一个事务正在对一条记录做修改,在这个事务完成并提交前,这条记录的数据就处于不一致的状态;这时,另一个事务也来读取同一条记录,如果不加控制,第二个事务读取了这些“脏”数据,并据此作进一步的 处理,就会产生未提交的数据依赖关系。这种现象被形象的叫做“脏读”。
一句话:事务A读取到了事务B已经修改但尚未提交的数据,还在这个数据基础上做了操作。此时,如果B事务回滚,A读取的数据无效,不符合一致性要求。

T1 T2
begin
select money from account where id=’A’; #返回1200 begin
update account money=1500 where id=’A’;
select money from account where id=’A’;#返回1500
rollback;

事务T2修改了一行记录,但是没有提交。然后事务T1读取到了未提交的数据,如果事务T2回滚其更改的数据或者再次更新,那么在事务T1中看到的记录可能就是错误的。事务T1读取到了A余额为1500的记录,但是事务T2执行了回滚操作,这时并不存在A余额为1500记录。

不可重复读(Non-Repeatable Reads)

一个事务在读取某些数据后的某个时间,再次读取以前读过的数据,却发现其读出的数据已经发生了改变、或某些记录已经被删除了!这种现象就叫做“不可重复读”。
一句话:事务A内部的相同查询语句在不同时刻读出的结果不一致,不符合隔离性
发生在数据被修改的情况,在修改一条数据后,事务提交之前,其他事务可以看到修改后的数据结果。

T1 T2
begin
select money from account where id=’A’;#返回 1200 begin
update account money=1500 where id=’A’;
commit;
select money from account where id=’A’;#返回 1500
commit;

事务T2对记录做了修改并提交成功,这意味着修改的记录对其他事务是可见的,因此事务T1两次读取的money值不同。

幻读(Phatom Reads)

⼀个事务按相同的查询条件重新读取以前检索过的数据,却发现其他事务插⼊了满⾜其查询条件的新数据,这种现象就称为“幻读”。
⼀句话:事务A读取到了事务B提交的新增数据,不符合隔离性
发⽣在数据新增的情况,在新增了⼀条数据后,事务提交之前,其他事务可以感知到并对这条新增的数据进⾏操作。

T1 T2
begin
select money from account where id=’A’;#返回1200 begin
insert into account(id,money) values (A,800);
commit;
select money from account where id=’A’;#返回 1200,800
commit;

那如果事务T2中是删除了符合的记录而不是插入新记录,那事务T1中之后再根据条件读取的记录变少了,这种现象在MySQL中不属于幻读,幻读强调的是一个事务按照某个相同条件多次读取纪录时,后读取时读到了之前没有读到的记录,对于先前已经读到的记录,之后又读取不到这种情况,其实这相当于对每一条记录都发生了不可重复读的现象。幻读:只是重点强调了读取到了之前读取没有获取到的记录

事务隔离级别

“脏读”、“不可重复读”和“幻读”,其实都是数据库读一致性问题,必须由数据库提供一定的事务隔离机制来解决。舍弃一部分隔离性来换取一部分性能在这里就能体现在:设立一些隔离级别,隔离级别越低,越严重的问题就越可能发生。有一帮人,并不是设计MySQL的人指定了一个SQL标准,在标准中设立了4个隔离级别:

  • Read uncommitted:读未提交;
  • Read committed:读已提交;
  • Repeatable read:可重复读;
  • Serializable:串行化;

SQL标准中规定,针对不同的隔离级别,并发事务可以发生不同严重程度的问题,具体情况如下:

隔离级别 脏读(Dirty Reads) 不可重复读(Non-Repeatable Reads) 幻读(Phatom Reads)
读未提交(Read uncommitted)
读已提交(Read committed) ×
可重复读(Repeatable read) × ×
可串行化(Serializable) × × ×

数据库的事务隔离越严格,并发副作用越小,但付出的代价也就越大,因为事务隔离实质上就是使事务在一定程度 上“串行化”进行,这显然与“并发”是矛盾的。
同时,不同的应用对读一致性和事务隔离程度的要求也是不同的,比如许多应用对“不可重复读”和“幻读”并不 敏感,可能更关心数据并发访问的能力。
常看当前数据库的事务隔离级别:show variables like ‘tx_isolation’;
设置事务隔离级别:set tx_isolation=’REPEATABLE-READ’;
Mysql默认的事务隔离级别是可重复读,用Spring开发程序时,如果不设置隔离级别默认用Mysql设置的隔离级别,如果Spring设置了就用已经设置的隔离级别。
读已提交可重复读 级别使用 MVCC 来保证隔离性,在可串行化级别中,会在 select 中也加锁,在事务提交之前,其他的事务对这条数据无法读也无法写。
当一条数据可能存在并发问题的时候,由于MySQL默认是可重复读的隔离级别,所以在程序中修改数据是读不到其他事务修改的结果的,即使其他事务已经提交了,所以在修改一条会存在竞争的数据时,会使用如下写法,在修改时会使用最新的数据修改。

  1. update test_table set balance = balance - 50 where id = 10;

通常在并发情况下也会在java程序中进行加锁控制。

锁是计算机协调多个进程或线程并发访问某一资源的机制。
在数据库中,除了传统的计算资源(如CPU、RAM、I/O等)的争用以外,数据也是一种供需要用户共享的资 源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发 访问性能的一个重要因素。
锁分类

  • 从性能上分为乐观锁(用版本对比来实现)和悲观锁
  • 从对数据库操作的类型分,分为读锁和写锁(都属于悲观锁)
    • 读锁(共享锁,S锁(Shared)):针对同一份数据,多个读操作可以同时进行而不会互相影响
    • 写锁(排它锁,X锁(eXclusive)):当前写操作没有完成前,它会阻断其他写锁和读锁
  • 从对数据操作的粒度分,分为表锁和行锁

    表锁

    每次操作锁住整张表。开销⼩,加锁快;不会出现死锁;锁定粒度⼤,发⽣锁冲突的概率最⾼,并发度最低;⼀般⽤在整表数据迁移的场景。
    ⼿动增加表锁:lock table 表名称 read(write),表名称2 read(write);
    查看表上加过的锁:show open tables;
    删除表锁:unlock tables;
    增加表锁后,当前session和其他session都可以读该表,当前session对该表的增删改查都没有问题,其他session对该表的所有操作被阻塞。
    MyISAM表的读操作(加读锁),不会阻寒其他进程对同⼀表的读请求,但会阻赛对同⼀表的写请求。只有当读锁释放后,才会执⾏其它进程的写操作。
    对MylSAM表的写操作(加写锁),会阻塞其他进程对同⼀表的读和写操作,只有当写锁释放后,才会执⾏其它进程的读写操作。
    ⽆索引⾏锁会升级为表锁,锁主要是加在索引上,如果对⾮索引字段更新,⾏锁可能会变表锁,InnoDB的⾏锁是针对索引加的锁,不是针对记录加的锁。并且该索引不能失效,否则都会从⾏锁升级为表锁。

    行锁

    每次操作锁住一行数据。开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度最 高。
    InnoDB与MYISAM的最大不同有两点:
    InnoDB支持事务(TRANSACTION)
    InnoDB支持行级锁
    MyISAM在执⾏查询语句SELECT前,会⾃动给涉及的所有表加读锁,在执⾏update、insert、delete操作会⾃动给涉及的表加写锁。
    InnoDB在执⾏查询语句SELECT时(⾮串⾏隔离级别),不会加锁。但是update、insert、delete操作会加⾏锁。
    简⽽⾔之,就是读锁会阻塞写,但是不会阻塞读。⽽写锁则会把读和写都阻塞
    锁定某⼀⾏还可以⽤lock in share mode(共享锁) 和 for update(排它锁)。
    例如:select*fromtest_innodb_lockwherea=2forupdate;这样其他session只能读这⾏数据,修改则会被阻塞,直到锁定⾏的session提交。

    间隙锁(Gap Lock)

    间隙锁,锁的就是两个值之间的空隙。Mysql默认级别是repeatable-read,没有办法解决幻读问题,间隙锁在某些情况下可以解决幻读问题。
    image.png
    那么间隙就有id为(3,10),(10,20),(20,正⽆穷)这三个区间,
    例如在Session_1下⾯执⾏update account set name=’zhuge’ where id>8 and id<18;,则其他Session没法在这个**范围所包含的所有⾏记录(包括间隙⾏记录)以及⾏记录所在的间隙**⾥插⼊或修改任何数据,即id在(3,20]区间都⽆法修改数据,注意最后那个20也是包含在内的。间隙锁是在可重复读隔离级别下才会⽣效。
    如果20是表中的最后⼀个id,那么如果锁住20之后的⾏,那么会锁住id⼤于20的所有⾏,锁住的间隙是20到正⽆穷,即使没有数据,此时不能再插⼊数据了。

    临键锁(Next-key Locks)

    Next-Key Locks是⾏锁与间隙锁的组合。像上⾯那个例⼦⾥的这个(3,20]的整个区间可以叫做临键锁。

    死锁

    查看近期死锁⽇志信息:show engine innodb status\G;
    ⼤多数情况mysql可以⾃动检测死锁并回滚产⽣死锁的那个事务,但是有些情况mysql没法⾃动检测死锁。

    问题一:什么是脏读?幻读?不可重复读?

    脏读(Drity Read):某个事务已更新一份数据,另一个事务在此时读取了同一份数据,由于某些原因,前一个RollBack了操作,则后一个事务所读取的数据就会是不正确的。
    不可重复读(Non-repeatable read):在一个事务的两次查询之中数据不一致,这可能是两次查询过程中间插入了一个事务更新的原有的数据。
    幻读(Phantom Read):在一个事务的两次查询中数据笔数不一致,例如有一个事务查询了几列(Row)数据,而另一个事务却在此时插入了新的几列数据,先前的事务在接下来的查询中,就会发现有几列数据是它先前所没有的。

    问题二:创建线程有几种不同的方式?

    有4种方式可以用来创建线程:
    1、继承Thread类
    2、实现Runnable接口
    3、应用程序可以使用Executor框架来创建线程池实现Runnable接口这种方式更受欢迎,因为这不需要继承Thread类。在应用设计中已经继承了别的对象的情况下,这需要多继承(而Java不支持多继承),只能实现接口。同时,线程池也是非常高效的,很容易实现和使用。
    4、还有一种方式是实现Callable接口

    问题三:什么是死锁?怎么解决?

    死锁是指两个或多个事务在同一资源上相互占用,并请求锁定对方的资源,从而导致恶性循环的现象。
    常见的解决死锁的方法
    a)如果不同程序会并发存取多个表,尽量约定以相同的顺序访问表,可以大大降低死锁机会。
    b)在同一个事务中,尽可能做到一次锁定所需要的所有资源,减少死锁产生概率;
    c)对于非常容易产生死锁的业务部分,可以尝试使用升级锁定颗粒度,通过表级锁定来减少死锁产生的概率;
    如果业务处理不好可以用分布式事务锁或者使用乐观锁