【Mysql篇】了解Mysql(三)

104 阅读9分钟
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 引擎会在适当的时候,将这个操作记录更新到磁盘里面,而这个更新往往是在系统比较空闲的时候做。