数据库的事务
什么是数据库的事务?
数据库事务(transaction)是访问并发可能操作各种数据项的一个数据库操作序列,这次操作要么全部执行,要么全部不执行,是一个不可分割的工作单位,事务由事务开始与事务结束之间执行的全书数据库操作组成。
事务的四大特性是什么?
原子性:原子性是指包含事务的操作要么全部执行成功,要么全部失败回滚。
一致性:一致性是指事务在执行前后状态是一致的
隔离性:一个事务进行的修改在最终提交之前,对其他的事务是不可见的
持久性: 数据一旦提交,其所作的修改将永久地保存在数据库中
数据库并发的一致性问题?
当多个事务并发执行时,可能会出现以下问题:
脏读:事务A更新了数据,但还没有提交,这时事务B读取到事务A更新后的数据,然后事务A回滚了,事务B读取到的数据就成为了脏数据。
不可重复读:事务A对数据进行多次读取,事务B在事务A多次读取的过程中执行了更新操作并提交了,导致事务A多次读取到的数据并不一致。
幻读:事务A在读取数据后,事务B向事务A读取的数据中插入了几条数据,事务A再次读取数据时发现多了几条数据,和之前读取的数据不一致
数据库的隔离级别有哪些?
未提交读:一个事务在提交前,他的修改对其他事务是可见的
提交读:一个事务提交之后,他的修改才能被其他事务看到
可重复读: 在同一个事务中多次读取到的数据是一致的
串行化:需要加锁实现,会强制事务串行执行
隔离级别 脏读 不可重复读 幻读
未提交读 允许 允许 允许
提交读 不允许 允许 允许
可重复读 不允许 不允许 允许
串行化 不允许 不允许 不允许
mysql 默认的隔离级别是 可重复读
隔离级别是如何实现的?
事务的隔离机制主要是依靠锁机制和MVCC(多版本并发控制)实现的,提交读和可重复读可以通过MVCC实现,串行化可以通过锁机制实现
什么是MVCC?
MVCC( multiple version concurrent control )是一种控制并发的方法,主要用来提高数据库的并发性能
在了解MVCC时应该先了解当前读和快照读
当前读:读取的是数据库的最新版本,并且在读取时保证其他事务不会修改当前的记录,所以会对读取的数据加锁。
快照读:不加锁的读取操作即为快照读,使用MVCC来读取快照中的数据,避免加锁带来的性能损耗
可以看到MVCC的作用是在不加锁的情况下,解决数据读写冲突问题,并且解决脏读、幻读、不可重复读等问题,但是不能解决丢失修改问题。
MVCC的实现原理:
版本号:
系统版本号:是一个自增的ID,每开启一个事务,系统版本号都会自增
事务版本号:事务版本号就是事务开始时的系统版本号,可以通过事务版本号的大小判断事务的时间顺序
行记录隐藏的列
DB_ROW_ID:所需空军6byte,隐含的自增ID,用来生产聚簇索引,如果数据表没有指定的聚簇索引,InnoDB会利用这个隐藏的id 创建聚簇索引
DB_TRX_ID:所需空间6byte,最近修改的事务ID,记录创建这条记录或最后一次修改这条记录的事务ID
DB_Roll_PTR: 所需空间7byte,回滚指针,指向这条记录的上一个版本
undo日志
mvcc做使用到的快照会存储到Undo日志中,该日志通过回滚指针将一个一个数据行的所有快照连接起来。
在重复读的隔离级别下,innodb的工作流程:
SELECT
作为查询结果要满足两个条件:
当前事务所要查询的数据行快照的创建版本号必须小于当前事务的版本号,这样做的目的是保证当前事务读取的数据行的快照要么在当前事务开始前就已经存在,要么就是当前事务自身插入或者修改过的
当前事务所要读取的数据行快照的删除版本号必须大雨当前事务的版本号,如果是小于等于的话,表示该数据行快照已经被删除,不能读取
INSERT
将当前系统版本号作为数据行快照的创建版本号
DELETE
将当前系统版本号作为数据行快照的删除版本号
UPDATE
保存当前系统版本号为更新前数据行快照创建的行版本号,并保存当前系统版本号为更新后的数据行快照的删除版本号,其实就是,先删除在插入即为更新
总计一下,MVCC的作用就是在避免枷锁的情况下,最大限度的解决读写并发冲突的问题,他可以实现提交读和可重复读的两个隔离级。
数据库的锁
什么是数据库的锁?
当数据库有并发事务的时候,保证数据访问顺序的机制称之为锁机制
数据库的锁与隔离级别的关系?
隔离级别 实现方式
未提交读 总是读取最新的数据,无需加锁
提交读 读取数据时加共享锁,读取数据后释放共享锁
可重复读 读取数据时加共享锁,事务结束后释放共享锁
串行化 锁定整个范围健,一直持有锁到事务结束
数据库锁的类型有哪些?
按照锁的粒度可以将mysql锁分为三种:
mysql 锁级别 资源开销 加速锁速度 是否会出现死锁 锁的粒度 并发度
表级锁 小 快 不会 大 低
行级锁 大 慢 会 小 高
页面锁 一般 一般 不会 一般 一般
Myisam 默认采用表级锁,innoDB默认采用行级锁
从锁的类别上区别可以分为共享锁和排他锁
共享锁:共享锁又称读锁,简称为S锁,一个事务对一个数据对象加了S锁,可以对这个对象进行读去操作,但不能进行更新操作,并且在加锁期间其他事务只能对这个数据对象加S锁,不能加X锁。
排他锁:排他锁又称为写锁,简称X锁,一个事务对一个数据对象加了X锁,可以对这个对象进行读取和更新操作,加锁期间,其他事务不能对该数据对象晋西加X锁或S锁
mysql 中InnoDB 引擎的行锁模式及其是如何实现的
行锁模式
在存在行锁和表锁的情况下,一个事务想对某个表加X锁时,需要先检查是否有其他事务对这个表加了锁或对这个表的某一行加了锁,对表的每一行都进行检测一次是非常低效率的,为了解决这种问题,实现多粒度锁机制,InnDB还有两种内部使用的意向锁,两种意向锁都是表锁。
意向共享锁:简称IS锁,一个事务打算给数据行加共享锁前必须先获得该表的IS锁
意向排他锁:简称IX锁,一个事务打算给数据行加排他锁前必须先获得该表的IX锁
有了意向锁,一个事务想对某个表加X锁,只要检查是否有其他事务对这个表加了X/IX/S/IS锁即可。
行锁的实现方式:INnDB 行锁主要分三种情况:
对索引加锁
对索引之间的间隙,第一条记录前的间隙或最后一条后的间隙加锁
前两中放入组合,对记录及前面的间隙枷锁
innoDB 行锁的特性:如果不通过索引条件检索数据,那么innoDB将对表中所有记录加锁,实际产生的效果和表锁一样
MVCC不能解决幻读问题,在可重复读隔离级别下,使用MVCC +Next-key Locks可以解决幻读问题
什么是数据库的乐观锁和悲观锁,如何实现?
乐观锁;系统假设数据的更新在大多数时候不会产生冲突的,所以数据库只在更新数据操作提交的时候对数据检测冲突,如果存在冲突,则更新数据失败
乐观锁实现方式:一般通过版本号和CAS 算法实现
悲观锁:假定会发生冲突,屏蔽一切可能违反数据完整性的操作,通俗讲就是每次去拿数据的时候都认为别人会修改,所以每次都在拿数据的时候都会上锁。
悲观锁的实现方式: 通过数据库的锁机制实现,对查询语句添加 for updata
什么死锁?如何避免?
死锁是指两个或者两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而早造成的一种阻塞现象,在mysql 中,Myisam是一次获取所需的全部锁,要么全部满足,要么全部等待,所以不会出现死锁。在Innodb存储引擎中,除了三个SQL组成的事务外,锁都是逐步获得的,所以存在死锁的问题。
如何避免mysql 发生死锁或锁冲突?
如果不通的程序并发存取多个表,尽量以相同的顺序访问表
在程序以批量方式处理数据的时候,如果已经对数据排序,尽量保证每个县城按照固定的顺序来处理记录
在事务中,如果需要更新记录,应直接申请足够级别的排他锁,而不应该先申请共享锁,更新时在申请排他锁,因为在当前用户申请排他锁时,其他事务可能已经获得了相同记录的共享锁,从而造成锁冲突或者死锁。
尽量使用较低的隔离级别
尽量使用索引访问数据,是加锁更加准确,从而减少锁冲突的机会
合理选择事务的大小,小事务发生锁冲突的概率更低
尽量用相等的条件访问数据,可以避免Next-key 锁对并发插入的影响
不要申请超过十几需要的锁级别,查询时尽量不显示加锁
对于一些特定的事务,可以表锁来提高处理速度或减少死锁的概率