MySql的架构流程
客户端
会先通过连接器
连接,然后查询缓存
中是否有想要的数据,即是否缓存命中
。命中则直接返回数据
,否则进入分析器
和优化器
,分析Sql语句和优化Sql语句,然后执行器
选择相应的引擎
执行。
数据库的事务
事务是一系列的操作,他们要符合ACID特性。
-
原子性
(Atomicity):事务必须是原子工作单元
,对于数据修改,要么全都执行,要么全部不执行。 -
一致性
(Consistency):系统(数据库)总是从一个一致性的状态转移到另一个一致性的状态
,不会存在中间状态。 -
隔离性
(Isolation):一个事务在完全提交之前
,对其他事务是不可见
的。 -
持久性
(Durability):一旦事务提交
,就永远是这样
,哪怕系统崩溃也不会影响这个事务的结果。
在并发情况下事务是否存在问题
事务在并发下存在脏读、不可重复读、虚读
等问题。
-
脏读
:事务A读取到事务B未提交的数据,结果事务B回滚,造成错误。 -
不可重复读
:事务A执行中,读取数据num=10,此时事务B执行完成并提交,修改了num=11。事务A再读取num为11,这种情况叫做不可重复读。 -
虚读
:事务A读取了一个范围的数据(比如10<num<20),读取到3条,结果事务B插入了一条数据成功提交,事务A读取到这个范围变成4条,即虚读。
不可重复读的重点是修改,同样的条件,你读取过的数据,再次读取出来发现值不一样;(主要在于update和delete)
幻读的重点在于新增或者删除,同样的条件,第 1 次和第 2 次读出来的记录数不一样。(主要在于insert)
如何解决 ->在并发情况下事务存在的问题
设置事务的隔离级别
。隔离级别就是多个线程开启各自事务
操作数据库中数据时,数据库系统
要负责隔离操作
,以保证各个线程
在获取数据时的准确性
。MySql中定义了4中隔离级别:
-
未提交读(READ UNCOMMITTED)
:事务A可以读取到事务B未提交的数据。最低级别,存在脏读
的情况。 -
已提交读(READ COMMITTED)
:事务A只能读取到事务B已经提交的数据,解决脏读
的问题,但是存在不可重复读
和虚读
的问题。 -
REPEATABLE READ(可重复读)
:解决
事务A在执行中前后读取数据不一致的问题,即不可重复读
的问题,不会出现刚刚读取num=10,过会再读取num变为11的情况。但是会存在虚读
的问题,即事务A读取一个范围的数据量可能会发生变化造成“幻觉”。 -
SERIALIZABLE(可串行化)
:这是最高的隔离级别,可以解决上面提到的所有问题,因为他强制
将所以的操作串行执行
,这会导致并发性能极速下降
,因此也不是很常用。
在MySQL数据库中,支持上面四种隔离级别,默认的为Repeatable read (可重复读);而在Oracle数据库中,只支持Serializable (串行化)级别和Read committed (读已提交)这两种级别,其中默认的为Read committed级别。
MySql如何控制隔离级别
(上面的难道还不够?)是通过排它锁
和共享锁
。
-
排它锁
被加锁
的对象只能被持有锁
的事务读取和修改
,其他
事务无法
在该对象上加其他锁
,也不
能读取和修改
该对象。 -
共享锁
被加锁
的对象可以被持锁
事务读取
,但是不
能被修改
,其他事务
也可以在上面再加共享锁
。
在对不论什么数据进行读操作之前要申请并获得S锁(共享锁,其他事务能够继续加共享锁,但不能加排它锁),在进行写操作之前要申请并获得X锁(排它锁,其他事务不能再获得不论什么锁)。加锁不成功,则事务进入等待状态,直到加锁成功才继续运行。
这里其实还可以分一二三四级封锁协议;
- 一级封锁协议是:事务在对需要修改的数据上面(就是在发生修改的瞬间)对其加共享锁(其他事务不能更改,但是可以读取-导致“脏读”),直到事务结束才释放。
- 二级封锁协议是:事务对需要更新的数据上加 排他锁(直到事务结束), 防止其他事务读取未提交的数据。事务对当前被读取的数据上面加共享锁,一旦读完该行,立即释放该行的共享锁 。二级封锁协议除防止了“脏读”数据,但是不能避免不可重复读,幻读。
- 三级封锁协议是:二级封锁协议加上事务在读取数据的瞬间必须先对其加共享锁,但是直到事务结束才释放,这样保证了可重复读(既是其他的事务职能读取该数据,但是不能更新该数据)。三级封锁协议除防止了“脏”数据和不可重复读,但是这种情况不能避免幻读情况。
- 四级封锁协议是对三级封锁协议的增强,其实现机制也最为简单,直接对事务中所读取或者更改的数据所在的表加表锁,也就是说,其他事务不能读 该表中的任何数据。这样所有的脏读,不可重复读,幻读,都得以避免!
数据库的锁
对数据的操作其实只有两种,也就是读和写,而数据库在实现锁时,也会对这两种操作使用不同的锁;InnoDB 实现了标准的行级锁,也就是共享锁(Shared Lock)和排它锁(Exclusive Lock)。
共享锁(读锁),允许事务读一行数据。
排它锁(写锁),允许事务删除或更新一行数据。
它们的名字也暗示着各自的另外一个特性,共享锁之间是兼容的,而互斥锁与其他任意锁都不兼容,如下图
Lock锁根据粒度主要分为表锁、页锁和行锁。不同的存储引擎拥有的锁粒度都不同。
悲观锁和乐观锁
悲观锁和乐观锁是一种思想,一种处理方式,不可和上面的锁机制(表锁,行锁,排他锁,共享锁)混为一谈。
- 悲观锁:即对于数据的处理持悲观态度,总认为会发生并发冲突,获取和修改数据时,别人会修改数据。所以在整个数据处理过程中,需要将数据锁定。悲观锁的实现,通常依靠数据库提供的锁机制实现,比如mysql的排他锁,select … for update来实现悲观锁。
- 乐观锁:顾名思义,就是对数据的处理持乐观态度,乐观的认为数据一般情况下不会发生冲突,只有提交数据更新时,才会对数据是否冲突进行检测。如果发现冲突了,则返回错误信息给用户,让用户自已决定如何操作。乐观锁的实现不依靠数据库提供的锁机制,需要我们自已实现,实现方式一般是记录数据版本,一种是通过版本号,一种是通过时间戳。
给表加一个版本号或时间戳的字段,读取数据时,将版本号一同读出,数据更新时,将版本号加1。当我们提交数据更新时,判断当前的版本号与第一次读取出来的版本号是否相等。如果相等,则予以更新,否则认为数据过期,拒绝更新,让用户重新操作。
数据库一个连接多久,每次都要释放吗(数据库的池化思想。)
数据库连接是一种有限的昂贵的资源,对数据库连接的管理能影响到整个应用程序的伸缩性和健壮性,数据库连接池正式针对这个问题提出来的。数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个。(数据库连接池思想和线程池思想一样)
常用的三种连接池:
- C3p0连接池:开源的JDBC连接池,实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展。目前使用它的开源项目有Hibernate、Spring等。单线程,性能较差,适用于小型系统,代码600KB左右。
- Dbcp连接池:由Apache开发的一个Java数据库连接池项目, Tomcat使用的连接池组件就是DBCP。预先将数据库连接放在内存中,应用程序需要建立数据库连接时直接到连接池中申请一个就行,用完再放回。单线程,并发量低,性能不好,适用于小型系统。
- Druid连接池:Druid不仅是一个数据库连接池,还包含一个ProxyDriver、一系列内置的JDBC组件库、一个SQL Parser。
MySql数据丢失了怎么办(持久化机制)
这个我记得在InnoDB中有个redo 日志是用来保证 MySQL 持久化功能的。MySql的操作是要写入到日志中 ,并不会直接刷新到硬盘上进行持久化。如果我们每一次的操作都要写入到硬盘中再更新,整个过程IO成本、查找成本都很高。日志即起到一个中间转折的作用,当有一条记录需要更新的时候,InnoDB 引擎就会先把记录写到 redo log(粉板)里面,并更新内存,这个时候更新就算完成了。同时,InnoDB 引擎会在适当的时候,将这个操作记录更新到磁盘里面,而这个更新往往是在系统比较空闲的时候做。