一、数据库系统和操作系统之间的关系
一般来讲,数据库是运行在操作系统之上的一套软件,大部分数据库要依赖或者部分依赖于操作系统提供的文件系统来对磁盘进行读写操作,也有少数数据库能直接对物理磁盘进行读写操作。它们之间的关系可以用下图很好的说明:
二、数据库系统的结构
1、狭义理解:DBMS(Database Management System,数据库管理系统) + DB(Database,数据库)2、数据库管理系统(DBMS)的主要职能有数据库的定义和建立、数据库的操作、数据库的控制、数据库的维护、故障恢复和数据通信。
3、数据库(DB)是数据在磁盘上存储的实体。
三、事务的概念及存在的意义
事务的概念:数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成。事务存在的意义:
1、为数据库操作序列提供了一个从失败中恢复到正常状态的方法,同时提供了数据库即使在异常状态下
仍能保持一致性的方法。
2、当多个应用程序在并发访问数据库时,可以在这些应用程序之间提供一个隔离方法,以防止彼此的操作互相干扰。
四、事务的四大特性(ACID)
1、原子性(Atomic)
原子性,是每个事务本身的一个特性,顾名思义,就是不可分割,因为Atom的本义就是不可分割,这个英语单词的词源来自于希腊语,古希腊人认为物质是不可无限分割的,英国物理学家、化学家道尔顿在提出原子论(1803年)的时候,就认为物质有个最小组成单位,所以才用了“不可分割”这个词来命名它。而事务有原子性,就意味着包含在某个特定事务中的各条操作是不能分开执行的,否则,就不能执行成功。2、一致性(Consitency)
一致性,所谓一致性,一般指的是数据层面的。也就是说,在某个事务执行之后,保存在数据库中的数据要与事务执行之前要保持完整性方面的一致。举个简单而经典的栗子,A向B转账500元这个事务,执行之后的数据结果就应该是A的账户金额减少了500元,而B的账户金额增加了500元,而绝不可能是除此之外的其他任何结果。这就是事务要求的一致性。3、隔离性(Isolation)
隔离性,是描述事务与事务之间的关系的一个概念,具体来讲,也就是任何两个事务之间在某种程度上是保持隔离的,它们之间互不干扰,互相之间处于一种“不可见”的状态。比如,有两个事务A和B,如果事务A正在对数据库中的某张表(或者表中的某条记录)进行增删改的操作,那么直到事务A结束之前,事务B都是无法通过查询来看到事务A的这些增删改操作结果的,更无法对该条记录进行更新操作。4、持久性(Durability)
持久性,从字面意思理解,就是在某个事务中进行的操作,是可以真实地改变数据库中的数据,并持久地存储在计算机的存储设备中的。
个人觉得,事务的这四大特性并非是一种平级关系,相反,它们存在依赖关系,其中的一致性从某种程度上可以说就是通过原子性和隔离性来实现的。所以它们应该有这样一个关系图:
五、事务机制的实现逻辑是什么?
1、MySQL的存储引擎
a、什么是存储引擎?
存储引擎说白了就是如何存储数据、如何为存储的数据建立索引和如何更新、查询等技术的实现方法。因为在关系数据库中数据的存储是以表的形式存储的,所以存储引擎也可以称为表类型(即存储和操作此表的类型)。b、MySQL支持哪些存储引擎?
MyISAM引擎(仅支持表级锁,不支持事务,但在MySQL中应用比较广泛)MEMORY引擎(仅支持表级锁,存储于内存中)
BDB引擎(支持页面锁和表级锁,已逐渐被InnoDB取代)
InnoDB引擎(支持行级锁和表级锁,默认使用行级锁,提供事务机制)
archive引擎
2、数据库中“锁”的概念:
是指存储引擎对于多线程执行场景中同一个资源的访问限制及同步机制处理。简单来说,就是指当多个线程(或者说多个事务)同时对数据库中的某张表或某条数据进行操作时(即并发处理),就有可能产生类似于多线程中的资源竞争的问题,此时就要加上一类保护机制对数据的访问和操作进行一定的限制,这类机制同样被称作“锁”。
3、数据库中锁的分类:
按维度分类(MySQL):
1、表级锁 (Table Lock),开销小,加锁快,不会出现死锁,但发生冲突的概率高,并发度低;2、行级锁 (Row Lock),开销大,加锁慢,会出现死锁,发生冲突的概率最小,并发度最高;
3、页面锁 (Page Lock),开销和加锁时间介于前二者之间,会出现死锁,并发度一般。
按概念分类:
1、悲观锁 (Optimistic Lock),每次去拿数据的时候都认为别人会修改数据,所以都会上锁,这样别人如果想拿该数据就会遇到阻塞,直到解锁为止;2、乐观锁 (Pessimistic Lock),每次去拿数据的时候都认为别人不会修改数据,所以不会上锁,但是到了更新数据的时候会判断在此期间别人有没有更新这个数据,适用于多读操作的应用场景,一般
用时间戳字段来实现;
按功能分类(以InnoDB存储引擎为例,其他引擎还有不同功能的锁):
1、共享锁 (Shared Lock, S),在InnoDB中为行级锁,允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。2、排它锁 (Exclusive Lock, X),在InnoDB中为行级锁,允许获得排它锁的事务更新数据,阻止其他事务取得相同数据集的共享锁和排他锁。
3、意向共享锁 (Intent Shared Lock, IS),事务打算给数据行加行共享锁,事务在给一个数据行加共享锁前必须先取得该表的意向共享锁。IS是表级锁。
4、意向排它锁 (Intent Exclusive Lock, IX),事务打算给数据行加行排它锁,事务在给一个数据行加排它锁前必须先取得该表的意向排它锁。IX也是表级锁。
锁的分类 | X | IX | S | IS |
---|---|---|---|---|
X | 冲突 | 冲突 | 冲突 | 冲突 |
IX | 冲突 | 兼容 | 冲突 | 兼容 |
S | 冲突 | 冲突 | 兼容 | 兼容 |
IS | 冲突 | 兼容 | 兼容 | 兼容 |
注:如果事务B对某行数据请求上锁的模式,与事务A对该数据已上锁的模式是“冲突”的,则事务B要等待事务A对该行数据释放锁之后,才能进行上锁。
4、并发情况下,不加锁可能会出现的问题:
a、更新丢失(Lost Update):打个很简单的比方,用两个不同的编辑器同时打开同一个文件分别进行编辑,后进行保存操作的编辑器会对先进行保存操作的编辑器中编辑的内容进行覆盖。b、脏读(Dirty Reads):如果事务A正在对某条记录进行写操作,但并未提交,而事务B在此时能够读取到这种并未提交的数据,并根据这种并未提交的数据进行了某个操作,然而事务A最终放弃了提交,这将造成数据前后不一致,事务B的这种读取被称作“脏读”。
c、不可重复读取(NonRepeatable Reads):事务A在对某条记录进行读取之后,并未提交而是继续进行其他操作,而此时事务B对该条数据进行了修改,当事务A再回来对该条数据进行读取时,会发现该条数据发生了改变。
d、幻读(Phantom Reads):事务A按照相同条件查询以前查询过的数据,发现由其他事务插入了满足条件的数据。
5、事务功能是如何实现的?
a、对于串行处理过程中出现的由事务突然终止或发生错误,而导致的数据不一致性问题,解决办法就是回滚日志(undo log)。回滚日志并不能将数据库物理地恢复到执行语句或者事务之前的样子;它是逻辑日志,当回滚日志被使用时,它只会按照日志逻辑地将数据库中的修改撤销掉看,可以理解为,我们在事务中使用的每一条 INSERT 都对应了一条 DELETE,每一条 UPDATE 也都对应一条相反的 UPDATE 语句。而且回滚日志是存储于磁盘上的,如果事务A未完成,而数据库管理系统退出,再次登录的时候也能根据回滚日志进行回滚操作。
b、对并发处理造成的类似“资源竞争”的情况,因此需要事务与事务之间具有一定的“隔离性”,实现这种隔离性主要有两种办法:1、在读取数据之前,对其上锁,阻止其他事务对数据进行修改;
2、不加锁,但是通过一定机制生成一个被请求的数据的快照(Snapshot,类似于百度快照的效果,是一个简单的副本),并用这个快照提供给用户进行读取,这种技术叫做数据多版本并发控制(MultiVersion Concurrency Control, MVCC)。
c、事务之间的隔离级别:事务之间“隔离”越严格,并发产生的“资源竞争”影响越小,但是“隔离”实际是相当于将原本的并发操作变得串行化,会使得访问效率降低,而且有些时候我们对“幻读”、“不可重复读取”要求并不严格,这就需要考虑制定事务之间的“隔离级别”,以便兼顾并发性能和数据一致性。ISO/ANSI SQL92定义了四中事务隔离级别,这些隔离级别允许出现不同的“副作用”,可以根据应用的业务逻辑需求进行选择。
事务的隔离级别对应的“副作用”之间的关系表
隔离级别 | 数据读取一致性 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|---|
未提交读(Read uncommitted) | 最低级别,只能保证不读取物理上损坏的数据 | 是 | 是 | 是 |
已提交读(Read committed) | 语句级 | 否 | 是 | 是 |
可重复读(Repeatable read) | 事务级 | 否 | 是 | 是 |
可序列化(Serializable) | 最高级别,事务级 | 否 | 否 | 否 |
(a)选择合理的事务大小,小事务发生锁冲突的几率更小;
(b)对于一些特定的事务,可以使用表锁来提高处理速度或减少发生死锁的几率;
(c)尽量使用较低隔离级别;
(d)不同的事务(或线程)在访问同一组表时,尽量约定为相同的访问顺序,对一张表而言,尽可
能以固定的顺序存取表中的行,这样可以大大减少死锁的机会;
本文参考一些blog和书籍资料,这里就不一一罗列出来,在此对这些blog和书籍的作者们表示感谢!