赞
踩
本文是加深了解mysql 的笔记,比较长,大家可以收藏慢慢看。
开胃:先串一下 mysql 逻辑架构
查询语句执行过程:
正文开始
提供了对数据库 ACID事务 的支持
提供了 行级锁和外键的约束。InnoDB的行锁也是分为行级 「共享读锁(S锁)「和」排它写锁(X锁)」,原理特点和MyISAM的表级锁两种模式是一样的。
设计的目标就是 处理大数据容量 的数据库系统
会出现死锁,但是 InnoDB的支持的并发性能比MyISAM好, 行锁的粒度最小,一定的方法和措施可以解决死锁的发生,极大的发挥InnoDB的性能
InnoDB中引入了 间隙锁 的概念来决解出现 幻读 的问题,也引入事务的特性,通过事务的 四种隔离级别 ,来降低锁冲突,提高并发性能
Innodb则是聚集索引,索引段和数据段在同一个文件中的不同段,查到索引后可以直接取出数据。
若想显式的给表加行级读锁和写锁,可以执行下面的sql语句
// 给查询sql显示添加读锁
select ... lock in share mode;
// 给查询sql显示添加写锁
select ... for update;
「 for update ,使用非索引查询,直接就是使用的表级锁」 「sql是否执行索引还得看Mysql的执行计划,对于一些小表的操作,可能就直接使用全表扫描」
for update 总结:
死锁在InnoDB中才会出现死锁,MyISAM是不会出现死锁,因为MyISAM支持的是表锁,一次性获取了所有的锁,其它的线程只能排队等候。InnoDB默认支持行锁,获取锁是分步的,并不是一次性获取所有的锁,因此在锁竞争的时候就会出现死锁的情况。
要解决死锁问题,在程序的设计上,当发现程序有高并发的访问某一个表时,尽量对该表的执行操作串行化,或者锁升级,一次性获取所有的锁资源。
然后也可以设置参数innodb_lock_wait_timeout
,超时时间,并且将参数innodb_deadlock_detect
打开,当发现死锁的时候,自动回滚其中的某一个事务。
当我们使用范围条件查询而不是等值条件查询的时候,InnoDB就会给符合条件的范围索引加锁,在条件范围内的记录就叫做"间隙(GAP)"
Mysql中的不可重复是已经解决了幻读问题,它通过引入间隙锁的实现来解决幻读,通过给符合条件的间隙加锁,防止再次查询的时候出现新数据产生幻读的问题
比如在区间(1,3]U[3,5)之间加了锁,是不能够新增数据行,新增num=2和num=4会失败失败,但是在这个区间以外的数据行是没有加锁的,可以新增数据行
根据索引的有序性,普通索引是可以出现重复值,Mysql在满足where条件的情况下,给(1,3]U[3,5)
区间加上了锁不允许插入num=3的数据行,这样就解决了幻读。
多版本控制: 指的是一种提高并发的技术。最早的数据库系统,只有读读之间可以并发,读写,写读,写写都要阻塞。
引入多版本之后,只有写写之间相互阻塞,其他三种操作都可以并行,这样大幅度提高了 InnoDB 的并发度。
在内部实现中,与 Postgres 在数据行上实现多版本不同,InnoDB 是在 undolog 中实现的,通过 undolog 可以找回数据的历史版本。找回的数据历史版本可以提供给用户读。(按照隔离级别的定义,有些读请求只能看到比较老的数据版本),也可以在回滚的时候覆盖数据页上的数据。在 InnoDB 内部中,会记录一个全局的活跃读写事务数组,其主要用来判断事务的可见性。
一致性视图只会在 RR(REPEATABLE-READ) 与 RC(READ-COMMITTED) 下才会生成,对于 RR 来说,一致性视图会在第一个查询语句的时候生成。而对于 RC 来说,每个查询语句都会重新生成视图。
MySQL 使用 MVCC 机制,可以读取之前版本数据。这些旧版本记录不会且也无法再去修改,就像快照一样。所以我们将这种查询称为快照读。
当然并不是所有查询都是快照读,select .... for update/ in share mode
这类 加锁查询只会查询当前记录最新版本数据。我们将这种查询称为当前读。
如果设置了主键,那么InnoDB会选择主键作为聚集索引、如果没有显式定义主键,则InnoDB会选择第一个不包含有NULL值的唯一索引作为主键索引、如果也没有这样的唯一索引,则InnoDB会选择内置6字节长的ROWID作为隐含的聚集索引(ROWID随着行记录的写入而主键递增)。
MyISAM中默认写优先于去操作,因此MyISAM一般不适合运用于大量读写操作的程序中。
// 显式的添加表级读锁
LOCK TABLE 表名 READ
// 显示的添加表级写锁
LOCK TABLE 表名 WRITE
// 显式的解锁(当一个事务commit的时候也会自动解锁)
unlock tables;
「当一个线程获取到表级写锁后,只能由该线程对表进行读写操作,别的线程必须等待该线程释放锁以后才能操作」
MyISAM存储引擎中,虽然读写操作是串行化的,但是它也支持并发插入,这个需要设置内部变量concurrent_insert
的值。
MyISAM存储引擎中,「假如同时一个读请求,一个写请求过来的话,它会优先处理写请求」,因为MyISAM存储引擎中认为写请求比读请求重要。
这样就会导致,「假如大量的读写请求过来,就会导致读请求长时间的等待,或者"线程饿死",因此MyISAM不适合运用于大量读写操作的场景」,这样会导致长时间读取不到用户数据,用户体验感极差。
当然可以通过设置 low-priority-updates
参数,设置请求链接的优先级,使得Mysql优先处理读请求。
所有的数据都在内存中,数据的处理速度快,但是安全性不高
索引是 数据结构+算法:
mysql 的存储文件如下:
在MyISAM中,索引(含叶子节点)存放在单独的.myi文件中,叶子节点存放的是数据的物理地址偏移量(通过偏移量访问就是随机访问,速度很快)
innodb 存储文件如下:
.frm 文件:存储表结构
.idb 文件:index + data (存储索引和数据)
mysql 的 IO 流程:
磁盘-内核内存-应用内存
查询方式:
B+tree性质:
1.)n棵子tree的节点包含n个关键字,不用来保存数据而是保存数据的索引。
2.)所有的叶子结点中包含了全部关键字的信息,及指向含这些关键字记录的指针,且叶子结点本身依关键字的大小自小而大顺序链接。
3.)所有的非终端结点可以看成是索引部分,结点中仅含其子树中的最大(或最小)关键字。
4.)B+ 树中,数据对象的插入和删除仅在叶节点上进行。
5.)B+树有2个头指针,一个是树的根节点,一个是最小关键码的叶节点。
.frm 文件:存储表结构
.idb 文件:index + data (存储索引和数据)
磁盘-内核内存-应用内存
磁头活塞运动,盘片转动(寻道),每次寻道 为10ms。磁盘局部性原理,每次读取为需要的数据和相邻数据 (预读),每次预读一页(4k)mysql 中 innodb 中默认的一页等于系统的 4页 (16k)。所以mysql 为 寻道(10ms)-旋转-预读(4K*k)索引,有些数据结构存在的价值是提高检索效率 (二叉树、红黑树、b树、b+树)
数据结构:
从数组+链表进行各种演变(面向对象的理解)
算法:逻辑上的约束
理论性的数据结构。特定场景,会失去特性退化为一个链表
变色,自旋
在特定场景时,索引效率与树深度有关系,树深度越深(树深log2n),IO次数也多。因为每次读取16k数据,如果红黑树的话会比较浪费空间。在大规模数据存储的时候,红黑树往往出现由于树的深度过大而造成磁盘IO读写过于频繁,进而导致效率低下的情况
树的高度降低,又矮又胖的二分结构。b树节点下有数据,所以同样的存储大小(16k)中存储的节点多少,会受到节点下数据大小的影响。节点下数据越大,B树的度会越少。每次IO读取数据大小相同,节点变大,就增加了磁盘IO次数(磁盘IO一次读出的数据量大小是固定的,单个数据变大,每次读出的就少,IO次数增多,一次IO多耗时),而B+树除了叶子节点其它节点并不存储数据,节点小,磁盘IO次数就少
跟B树相比,只有叶子结点存放数据,减小枝干结点的空间大小,可以增大树的度。innodb中页的默认大小是16KB,如果不存储数据,那么就会存储更多的键值,相应的树的阶数(节点的子节点树)就会更大,且叶子结点间有链指针,方便数据范围查找。
B+树索引的所有数据均存储在叶子节点,而且数据是按照顺序排列的,链表连着的。那么B+树使得范围查找,排序查找,分组查找以及去重查找变得异常简单。
非聚簇索引叶子结点为什么不直接存储数据?
为什么推荐使用整型自增的主键?
整型:方便b+树的key 值比较排序。
自增:方便数据再内存的顺序写入。
HASH(key,value)这种方式对 范围查询支持得不是很好 。底层的数据结构是**哈希表,可以理解为用hashMap存储数据。hash 索引结构的特殊性,其 检索效率非常高,索引的检索可以一次定位,不像B-Tree 索引需要从根节点到枝节点,最后才能访问到页节点这样多次的IO访问,所以 Hash 索引的查询效率要远高于 B-Tree 索引。
(1)Hash 索引仅仅能满足”=”,”IN”和”<=>”查询,不能使用范围查询。
由于 Hash 索引比较的是进行 Hash 运算之后的 Hash 值,所以它只能用于等值的过滤,不能用于基于范围的过滤,因为经过相应的 Hash 算法处理之后的 Hash 值的大小关系,并不能保证和Hash运算前完全一样。
(2)Hash 索引无法被用来数据的排序操作。
由于 Hash 索引中存放的是经过 Hash 计算之后的 Hash 值,而且Hash值的大小关系并不一定和 Hash 运算前的键值完全一样,所以数据库无法利用索引的数据来避免任何排序运算;
(3)Hash 索引不能利用部分索引键查询。
对于组合索引,Hash 索引在计算 Hash 值的时候是组合索引键合并后再一起计算 Hash 值,而不是单独计算 Hash 值,所以通过组合索引的前面一个或几个索引键进行查询的时候,Hash 索引也无法被利用。
(4)Hash 索引在任何时候都不能避免表扫描。
前面已经知道,Hash 索引是将索引键通过 Hash 运算之后,将 Hash运算结果的 Hash 值和所对应的行指针信息存放于一个 Hash 表中,由于不同索引键存在相同 Hash 值,所以即使取满足某个 Hash 键值的数据的记录条数,也无法从 Hash 索引中直接完成查询,还是要通过访问表中的实际数据进行相应的比较,并得到相应的结果。
(5)Hash 索引遇到大量Hash值相等的情况后性能并不一定就会比B-Tree索引高。
对于选择性比较低的索引键(hash冲突),如果创建 Hash 索引,那么将会存在大量记录指针信息存于同一个 Hash 值相关联。这样要定位某一条记录时就会非常麻烦,会浪费多次表数据的访问,而造成整体性能低下。
MyISAM是非聚集索引,而Innodb则是聚集索引。MyISAM索引和数据的存储是分开的(不同的文件),索引中最终检索到的是数据的物理地址偏移量。而InnoDB中,索引段和数据段在同一个文件中的不同段,查到索引后可以直接取出数据。
一般来说,在WHERE和JOIN中出现的列需要建立索引,但也不完全如此,因为MySQL只对<,<=,=,>,>=,BETWEEN,IN,以及使用 LIKE 谓词时,只有前方一致的匹配才能用到索引。
实际的情况,需要控制IN查询的范围。
Select
之后Where
之前的那些字段。寻找查询字段
所匹配的索引,并得到一个可用索引结果集。根据最小估算扫描行数优先原则
,可以得到最优的索引文件读取指针
去获取数据块,效果还不如从头到位把整个表都扫描一边,也省去了去查找索引和频繁重定向读取指针带来的开销。默认索引选择算法
)where age > 8
),并且使用这个常量值与对应索引筛选出的记录数占了总数的大部分。优化器认为扫这么大的数据还不如扫全表了,所以选择了扫描全表。当且仅当使用了等于号
只要该列有匹配的索引,一定会命中索引。如果不是等于号则会退化到扫描全表** 默认索引选择算法
**——当查询语句的搜索条件没有命中任何索引时,Mysql索引优化器会考量查询语句中的目标字段(select后面,where前面的部分),目标字段除去主键外,如果恰好是某个索引(包括组合索引)对应列的子集,那么该索引也会被使用。如果满足的索引有多个,将会使用索引记录数最少的索引。这个算法在[3]中得到了旁证。
1.使用limit对查询结果的记录进行限定
2.避免select *,将需要查找的字段列出来
3.使用连接(join)来代替子查询
4.拆分大的 delete或 insert语句
5.可通过开启慢查询日志来找出较慢的SQL
6.不做列运算:SELECT id WHERE age + 1 = 10,任何对列的操作都将导致表扫描,它包括数据库教程函数、计算表达式等等,查询时要尽可能将操作移至等号右边
7.sql语句尽可能简单:一条sql只能在一个cpu运算;**大语句拆小语句,减少锁时间;**一条大sql可以堵死整个库
8.OR改写成IN:OR的效率是n级别,IN的效率是log(n)级别,in的个数建议控制在200以内
9.不用函数和触发器,在应用程序实现
10.避免%xxx式,模糊查询
11.尽量减少少用 JOIN
12.使用同类型进行比较,比如用’123’和’123’比,123和123比
13.尽量避免在WHERE子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描
14.对于连续数值,使用BETWEEN不用IN:SELECT id FROM t WHERE num BETWEEN 1 AND 5
15.列表数据不要拿全表,要使用LIMIT来分页,每页数量也不要太大
当前节内容 可以参照 基础-多线程-线程安全-锁章节内容
在我们平时执行select语句的时候就会隐式的加读锁,执行增、删、改的操作时就会隐式的执行加写锁。
表锁
页锁
行锁
粒度最大的锁 ,开销小,加锁快,不会出现死锁
由于粒度太大,因此造成锁的冲突几率大,并发性能低。Mysql的「MyISAM储存引擎就支持表锁」(参照存储引擎MyISAM),MyISAM的表锁模式有两种:「表共享读锁**」和「表独占写**锁」。
当一个线程获取到MyISAM表的读锁的时候,会阻塞其他用户对该表的写操作,但是不会阻塞其它用户对该用户的读操作。相反的,当一个线程获取到MyISAM表的写锁的时候,就会阻塞其它用户的读写操作对其它的线程具有排它性。
页锁的粒度是 介于行锁和表锁之间的一种锁,因为页锁是在BDB中支持的一种锁机制,也 很少没人提及和使用,所以这里制作概述,不做详解。
粒度最小的锁机制,行锁的加锁开销性能大,加锁慢,并且会出现死锁,行锁的锁冲突的几率低,并发性能高
行锁是 InnoDB默认的支持的锁机制,MyISAM不支持行锁,这个也是InnoDB和MyISAM的区别之一。
行锁在使用的方式上可以划分为:「共享读锁(S锁)「和」排它写锁(X锁)」。
共享读锁:当一个事务对Mysql中的一条数据行加上了S锁,当前事务不能修改该行数据只能执行读操作,其他事务只能对该行数据加S锁不能加X锁。
排它写锁: 若是一个事务对一行数据加了X锁,该事务能够对该行数据执行读和写操作,其它事务不能对该行数据加任何的锁,既不能读也不能写。
共享读锁,「当一个线程获取到表级读锁后,该线程只能读取数据不能修改数据,其它线程也只能加读锁,不能加写锁」。
独占写锁:「当一个线程获取到表级写锁后,只能由该线程对表进行读写操作,别的线程必须等待该线程释放锁以后才能操作」。
mysql 乐观锁需要程序员自己去实现的锁机制」,最常见的乐观锁实现就锁机制是 「使用版本号实现」和 "通过时间戳"实现。乐观锁最常采用的是CAS算法,会有ABA 问题、只能做一个原子操作问题和循环时间过长问题。乐观锁适合读操作多的场景,不加锁的特点能够使其读操作的性能大幅提升
Mysql的 「悲观锁的实现是基于Mysql自身的锁机制实现,而乐观锁需要程序员自己去实现的锁机制」,最常见的乐观锁实现就锁机制是 「使用版本号实现」
MySQL 主从复制是指数据可以从一个MySQL数据库服务器主节点复制到一个或多个从节点。MySQL 默认采用异步复制方式,这样从节点不用一直访问主服务器来更新自己的数据,数据的更新可以在远程连接上进行,从节点可以复制主数据库中的所有数据库或者特定的数据库,或者特定的表。
原理:
也就是说:
一主一从
主主复制
一主多从
多主一从
联级复制
主从复制分了五个步骤进行:
一个服务器开放N个链接给客户端来连接的,这样有会有大并发的更新操作, 但是从服务器的里面读取binlog的线程仅有一个,当某个SQL在从服务器上执行的时间稍长 或者由于某个SQL要进行锁表就会导致,主服务器的SQL大量积压,未被同步到从服务器里。这就导致了主从不一致, 也就是主从延迟。
mysql5.7之后使用MTS并行复制技术,永久解决复制延时问题:https://blog.csdn.net/w892824196/article/details/106937133
分区和分表相似,都是按照规则分解表。不同在于分表将大表分解为若干个独立的实体表,而分区是将数据分段划分在多个位置存放,可以是同一块磁盘也可以在不同的机器。分区后,表面上还是一张表,但数据散列到多个位置了。app读写的时候操作的还是大表名字,db自动去组织分区的数据。
mysql数据库中的数据是以文件的形势存在磁盘上的,默认放在/mysql/data下面(可以通过my.cnf中的datadir来查看),一张表主要对应着三个文件,一个是frm存放表结构的,一个是myd存放表数据的,
一个是myi存表索引的。如果一张表的数据量太大的话,那么myd,myi就会变的很大,查找数据就会变的很慢,这个时候我们可以利用mysql的分区功能,
在物理上将这一张表对应的三个文件,分割成许多个小块,这样呢,我们查找一条数据时,就不用全部查找了,只要知道这条数据在哪一块,然后在那一块找就行了。
如果表的数据太大,可能一个磁盘放不下,这个时候,我们可以把数据分配到不同的磁盘里面去
分区的二种方式
a,横向分区
什么是横向分区呢?就是横着来分区了,举例来说明一下,假如有100W条数据,分成十份,前10W条数据放到第一个分区,第二个10W条数据放到第二个分区,依此类推。也就是把表分成了十分,根用merge来分表,有点像哦。取出一条数据的时候,这条数据包含了表结构中的所有字段,也就是说横向分区,并没有改变表的结构。
b,纵向分区
什么是纵向分区呢?就是竖来分区了,举例来说明,在设计用户表的时候,开始的时候没有考虑好,而把个人的所有信息都放到了一张表里面去,这样这个表里面就会有比较大的字段,如个人简介,而这些简介呢,也许不会有好多人去看,所以等到有人要看的时候,在去查找,分表的时候,可以把这样的大字段,分开来。
使用起来和不分区是一样的,看起来只有一个数据库,其实有多个分区文件,比如我们要插入一条数据,不需要指定分区,MySQL会自动帮我们处理,对应用是透明的无需修改代码
1.可以让单表存储更多的数据
2.分区表的数据更容易维护,可以通过清楚整个分区批量删除大量数据,也可以增加新的分区来支持新插入的数据。另外,还可以对一个独立分区进行优化、检查、修复等操作
3.部分查询能够从查询条件确定只落在少数分区上,速度会很快
4.分区表的数据还可以分布在不同的物理设备上,从而高效利用多个硬件设备
5.可以使用分区表赖避免某些特殊瓶颈,例如InnoDB单个索引的互斥访问、ext3文件系统的inode锁竞争
6.可以备份和恢复单个分区
1.一个表最多只能有1024个分区
2.如果分区字段中有主键或者唯一索引的列,那么所有主键列和唯一索引列都必须包含进来
3.分区表无法使用外键约束
4.NULL值会使分区过滤无效
5.所有分区必须使用相同的存储引擎
1.RANGE分区:基于属于一个给定连续区间的列值,把多行分配给分区
2.LIST分区:类似于按RANGE分区,区别在于LIST分区是基于列值匹配一个离散值集合中的某个值来进行选择
3.HASH分区:基于用户定义的表达式的返回值来进行选择的分区,该表达式使用将要插入到表中的这些行的列值进行计算。这个函数可以包含MySQL中有效的、产生非负整数值的任何表达式
4.KEY分区:类似于按HASH分区,区别在于KEY分区只支持计算一列或多列,且MySQL服务器提供其自身的哈希函数。必须有一列或多列包含整数值
子分区是分区表中每个分区的再次分割,子分区既可以使用HASH希分区,也可以使用KEY分区。这 也被称为复合分区(*composite partitioning*)。
1,如果一个分区中创建了子分区,其他分区也要有子分区
2,如果创建了了分区,每个分区中的子分区数必有相同
3,同一分区内的子分区,名字不相同,不同分区内的子分区名子可以相同
sharding-jdbc(当当)
Mycat
TDDL(淘宝)
Oceanus(58同城数据库中间件)
vitess(谷歌开发的数据库中间件)
Atlas(Qihoo 360)
https://mp.weixin.qq.com/s/OM3oNqu8jIbmkdsvEhtL4g
传统数据库存在着先天性的弊端,但是NoSQL数据库又无法将其替代。如果传统数据易于扩展,可切分,就可以避免单机(单库)的性能缺陷。
MyCat的目标就是:低成本地将现有的单机数据库和应用平滑迁移到“云”端,解决数据存储和业务规模迅速增长情况下的数据瓶颈问题。
从定义和分类来看,它是一个开源的分布式数据库系统,是一个实现了MySQL协议的服务器,前端用户可以把它看作是一个数据库代理,用MySQL客户端工具和命令行访问,而其后端可以用MySQL原生协议与多个MySQL服务器通信,也可以用JDBC协议与大多数主流数据库服务器通信,其核心功能是分表分库,即将一个大表水平分割为N个小表,存储在后端MySQL服务器里或者其他数据库里。
MyCat发展到目前的版本,已经不是一个单纯的MySQL代理了,它的后端可以支持MySQL、SQL Server、Oracle、DB2、PostgreSQL等主流数据库,也支持MongoDB这种新型NoSQL方式的存储,未来还会支持更多类型的存储。而在最终用户看来,无论是那种存储方式,在MyCat里,都是一个传统的数据库表,支持标准的SQL语句进行数据的操作,这样一来,对前端业务系统来说,可以大幅降低开发难度,提升开发速度
本节参考:https://mp.weixin.qq.com/s/HJYkDheKbCdANj91zRDx2g
事务特点, 原子性(Atomicity),一致性(Consistency),隔离型(Isolation)以及持久性(Durability)。事务是要做到可靠性以及并发处理
实现事务功能的三个技术,分别是日志文件(redo log 和 undo log),锁技术以及MVCC
redo log叫做重做日志,是用来实现事务的持久性。该日志文件由两部分组成:重做日志缓冲(redo log buffer)以及重做日志文件(redo log),前者是在内存中,后者在磁盘中。
当事务提交之后会把所有修改信息都会存到该日志中。redo log是用来恢复数据的 用于保障,已提交事务的持久化特性
mysql 为了提升性能不会把每次的修改都实时同步到磁盘,而是会先存到Boffer Pool(缓冲池)里头,把这个当作缓存来用。然后使用后台线程去做缓冲池和磁盘之间的同步。
那么问题来了,如果还没来的同步的时候宕机或断电了怎么办?还没来得及同步磁盘。这样会导致丢部分已提交事务的修改信息!
所以引入了redo log来记录已成功提交事务的修改信息,并且会把redo log持久化到磁盘,系统重启之后在读取redo log恢复最新数据。
start transaction;
select balance from bank where name="zhangsan";
// 生成 重做日志 balance=600
update bank set balance = balance - 400;
// 生成 重做日志 amount=400
update finance set amount = amount + 400;
commit;
undo log 叫做回滚日志,用于记录数据被修改前的信息。他正好跟前面所说的重做日志所记录的相反,重做日志记录数据被修改后的信息。undo log主要记录的是数据的逻辑变化,为了在发生错误时回滚之前的操作,需要将之前的操作都记录下来,然后在发生错误时才可以回滚。
每次写入数据或者修改数据之前都会把修改前的信息记录到 undo log。
undo log 记录事务修改之前版本的数据信息,因此假如由于系统错误或者rollback操作而回滚的话可以根据undo log的信息来进行回滚到没被修改前的状态。
undo log是用来回滚数据的用于保障 未提交事务的原子性
MVCC (MultiVersion Concurrency Control) 叫做多版本并发控制。
InnoDB的 MVCC ,是通过在每行记录的后面保存两个隐藏的列来实现的。这两个列, 一个保存了行的创建时间,一个保存了行的过期时间,当然存储的并不是实际的时间值,而是系统版本号。
MVCC在mysql中的实现依赖的是undo log与read view
通过读写锁,可以做到读读可以并行,但是不能做到写读,写写并行 事务的隔离性就是根据读写锁来实现的!!!
共享锁(shared lock),又叫做"读锁"
读锁是可以共享的,或者说多个读请求可以共享一把锁读数据,不会造成阻塞。
排他锁(exclusive lock),又叫做"写锁"
写锁会排斥其他所有获取锁的请求,一直阻塞,直到写入完成释放锁。
原子性,持久性,隔离性折腾半天的目的也是为了保障数据的一致性!总之,ACID只是个概念,事务最终目的是要保障数据的可靠性,一致性。
一个事务必须被视为不可分割的最小工作单位,一个事务中的所有操作要么全部成功提交,要么全部失败回滚,对于一个事务来说不可能只执行其中的部分操作,这就是事务的原子性。
所谓回滚操作就是当发生错误异常或者显式的执行rollback语句时需要把数据还原到原先的模样,所以这时候就需要用到undo log来进行回滚,接下来看一下undo log在实现事务原子性时怎么发挥作用的
为了做到同时成功或者失败,当系统发生错误或者执行rollback操作时需要根据undo log 进行回滚。
回滚操作就是要还原到原来的状态,undo log记录了数据被修改前的信息以及新增和被删除的数据信息,根据undo log生成回滚语句,比如:
事务一旦提交,其所作做的修改会永久保存到数据库中,此时即使系统崩溃修改的数据也不会丢失。
为了提升**性能(IO 瓶颈)**InnoDB提供了缓冲池(Buffer Pool),Buffer Pool中包含了磁盘数据页的映射,可以当做缓存来使用:
事务开始之后就产生redo log,redo log的落盘并不是随着事务的提交才写入的,而是在事务的执行过程中,便开始写入redo log文件中。
上面这种缓冲池的措施虽然在性能方面带来了质的飞跃,但是当MySQL系统宕机,断电的时候可能会丢数据!!!
因为我们的数据已经提交了,但此时是在缓冲池里头,还没来得及在磁盘持久化,所以我们急需一种机制需要存一下已提交事务的数据,为恢复数据使用。于是 redo log就派上用场了。下面看下redo log是什么时候产生的。
既然redo log也需要存储,也涉及磁盘IO为啥还用它?
在SQL标准里定义了四种隔离级别,每一种级别都规定一个事务中的修改,哪些是事务之间可见的,哪些是不可见的。
Mysql 隔离级别有以下四种(级别由低到高):
脏读、幻读、不可重复读
脏读 :所谓的脏读,其实就是读到了别的事务回滚前的脏数据。比如事务B执行过程中修改了数据X,在未提交前,事务A读取了X,而事务B却回滚了,这样事务A就形成了脏读。
不可重复读 :不可重复读字面含义已经很明了了,比如事务A首先读取了一条数据,然后执行逻辑的时候,事务B将这条数据改变了,然后事务A再次读取的时候,发现数据不匹配了,就是所谓的不可重复读了。即一个事务范围内两个相同的查询却返回了不同数据。 数据变了
幻读 :小的时候数手指,第一次数十10个,第二次数是11个,怎么回事?产生幻觉了?(数据多了)
幻读也是这样子,事务A首先根据条件索引得到10条数据,然后事务B改变了数据库一条数据,导致也符合事务A当时的搜索条件,这样事务A再次搜索发现有11条数据了,就产生了幻读。
隔离性是要管理多个并发读写请求的访问顺序。这种顺序包括串行或者是并行。原子性和持久性是为了要实现数据的可性保障靠。可靠性性高的,并发性能低(比如 Serializable),可靠性低的,并发性能高(比如 Read Uncommited)
在READ UNCOMMITTED隔离级别下,事务中的修改即使还没提交,对其他事务是可见的。事务可以读取未提交的数据,造成脏读。因为读不会加任何锁,所以在读的过程中修改数据,所以会造成其他读事务的脏读。好处是可以提升并发处理性能,能做到读写并行。
一个事务在他提交之前的所有修改,对其他事务都是不可见的。其他事务能读到已提交的修改变化。在很多场景下这种逻辑是可以接受的。
InnoDB在 READ COMMITTED,采用了读写分离机制,写数据使用排它锁,读取数据不加锁而是使用了MVCC机制。该级别会产生不可重读以及幻读问题。
为什么会产生不可重复读?
这跟 READ COMMITTED 级别下的MVCC机制有关系,在该隔离级别下每次 select的时候新生成一个版本号,所以每次select的时候读的不是一个副本而是不同的副本。
在一个事务内的多次读取的结果是一样的。这种级别下可以避免,脏读,不可重复读等查询问题。mysql 有两种机制可以达到这种隔离级别的效果,分别是采用读写锁以及MVCC
读写锁实现:可重复读,只要没释放读锁,在次读的时候还是可以读到第一次读的数据。
采用MVCC实现: 因为多次读取只生成一个版本,读到的自然是相同数据。但是在该隔离级别下仍会存在幻读的问题
在隔离级别下除了不会造成数据不一致问题,最严格的级别,事务串行执行,资源消耗最大;
通过回滚,以及恢复,和在并发环境下的隔离做到一致性。
binlog 和 redo log:
MySQL是多存储引擎的,不管使用那种存储引擎,都会有binlog,而不一定有redo log,简单的说,binlog是MySQL Server层的,redo log是InnoDB层的。
CrashSafe指MySQL服务器宕机重启后,能够保证:
Innodb通过Redo Log和Undo Log可以保证以上两点。为了保证严格的CrashSafe,必须要在每个事务提交的时候,将redo Log写入硬件存储。这样做会牺牲一些性能,但是可靠性最好。为了平衡两者,InnoDB提供了一个innodb_flush_log_at_trx_commit 系统变量,用户可以根据应用的需求自行调整。innodb_flush_log_at_trx_commit = 取值 0|1|2:
0 – 每N(默认为1)秒将Redo Log Buffer的记录写入Redo Log文件,并且将文件刷入硬件存储1次。N由innodb_flush_log_at_timeout控制。
1 – 每个事务提交时,将记录从Redo Log Buffer写入Redo Log文件,并且将文件刷入硬件存储。
2 – 每个事务提交时,仅将记录从Redo Log Buffer写入Redo Log文件。Redo Log何时刷入硬件存储由操作系统和innodb_flush_log_at_timeout决定。这个选项可以保证在MySQL宕机,而操作系统正常工作时,数据的完整性。
通过redo日志将所有已经在存储引擎内部提交的事务应用redo log恢复,所有已经 prepare 但是没有commit的transactions 将会应用 undo log做 rollback。(此处可看6.2)然后客户端连接时就能看到已经提交的数据存在数据库内,未提交被回滚地数据需要重新执行。
MySQL为了保证master和slave的数据一致性,就必须保证binlog和InnoDB redo日志的一致性(因为备库通过二进制日志重放主库提交的事务,而主库binlog写入在commit之前,如果写完binlog主库crash,再次启动时会回滚事务。但此时从库已经执行,则会造成主备数据不一致)。所以在开启Binlog后,如何保证binlog和InnoDB redo日志的一致性呢?为此,MySQL引入二阶段提交(two phase commit or 2pc),MySQL内部会自动将普通事务当做一个XA事务(内部分布式事物)来处理:
自动为每个事务分配一个唯一的ID(XID)。
COMMIT会被自动的分成Prepare和Commit两个阶段。
Binlog会被当做事务协调者(Transaction Coordinator),Binlog Event会被当做协调者日志。
Binlog在2PC中充当了事务的协调者(Transaction Coordinator)。由Binlog来通知InnoDB引擎来执行prepare,commit或者rollback的步骤。事务提交的整个过程如下:
事务的提交主要分为两个主要步骤:
此时SQL已经成功执行,并生成xid信息及redo和undo的内存日志。然后调用prepare方法完成第一阶段,papare方法实际上什么也没做,将事务状态设为TRX_PREPARED,并将redo log刷磁盘。
2.1 记录协调者日志,即Binlog日志。
如果事务涉及的所有存储引擎的prepare都执行成功,则调用TC_LOG_BINLOG::log_xid方法将SQL语句写到binlog(write()将binary log内存日志数据写入文件系统缓存,fsync()将binary log文件系统缓存日志数据永久写入磁盘)。此时,事务已经铁定要提交了。否则,调用ha_rollback_trans方法回滚事务,而SQL语句实际上也不会写到binlog。
2.2 告诉引擎做commit。
最后,调用引擎的commit完成事务的提交。会清除undo信息,刷redo日志,将事务设为TRX_NOT_STARTED状态。
PS:记录 Binlog是在InnoDB引擎Prepare(即Redo Log写入磁盘)之后,这点至关重要。
由上面的二阶段提交流程可以看出,一旦步骤2中的操作完成,就确保了事务的提交,每个步骤都需要进行一次fsync操作才能保证上下两层数据的一致性。参数由sync_binlog=1控制,由参数innodb_flush_log_at_trx_commit=1控制,俗称“双1”,是保证CrashSafe的根本。
事务的两阶段提交协议保证了无论在任何情况下,事务要么同时存在于存储引擎(redo log)和binlog中,要么两个里面都不存在,这就保证了主库与从库之间数据的一致性。如果数据库系统发生崩溃,当数据库系统重新启动时会进行崩溃恢复操作,存储引擎中处于prepare状态的事务会去查询该事务是否也同时存在于binlog中,如果存在就在存储引擎内部提交该事务(因为此时从库可能已经获取了对应的binlog内容),如果binlog中没有该事务,就回滚该事务。
例如:当崩溃发生在写入到binlog时,明显处于prepare状态的事务还没来得及写入到binlog中,所以该事务会在存储引擎内部进行回滚,这样该事务在存储引擎和binlog中都不会存在;当崩溃发生在调用引擎的commit时,处于prepare状态的事务存在于binlog中,那么该事务会在存储引擎内部进行提交,这样该事务就同时存在于存储引擎和binlog中。
MySQL内部两阶段提交需要开启innodb_support_xa=true,默认开启。这个参数就是支持分布式事务两段式事务提交。redo和binlog数据一致性就是靠这个两段式提交来完成的,如果关闭会造成事务数据的丢失。
在进行恢复时事务要提交还是回滚,是由Binlog来决定的。事务的Xid_log_event在binlog中存在,就要提交, 事务的Xid_log_event在binlog中不存在,就要回滚。基本有下面是几种情况:
总结起来说就是如果一个事务在prepare阶段中落盘成功,并在MySQL Server层中的binlog也写入成功,那这个事务必定commit成功。
恢复的过程非常简单:
上面提到单个事务的二阶段提交过程,能够保证存储引擎和binlog日志保持一致,但是在并发的情况下,多个事务并发提交怎么保证InnoDB层事务日志和MySQL数据库二进制日志的提交的顺序一致?如果Binary Log和存储引擎顺序不一致会造成什么影响?
事务按照T1、T2、T3顺序开始执行,将二进制日志(按照T1、T2、T3顺序)写入日志文件系统缓冲,调用fsync()进行一次group commit将日志文件永久写入磁盘,但是存储引擎提交的顺序为T2、T3、T1。当T2、T3提交事务之后,若通过在线物理备份进行数据库恢复来建立复制时,因为在InnoDB存储引擎层会检测事务T3在上下两层都完成了事务提交,不需要在进行恢复了,此时主备数据不一致(搭建Slave时,change master to的日志偏移量记录T3在事务位置之后
在早期的MySQL 5.6版本之前,通过prepare_commit_mutex锁以串行的方式来保证MySQL数据库上层二进制日志和Innodb存储引擎层的事务提交顺序一致,通过这个prepare_commit_mutex锁,将redo log和binlog刷盘串行化。串行化的目的也仅仅是为了保证redo log和Binlog一致,继而无法实现group commit,牺牲了性能。
BLGC 是为了解决 并发的情况下,多个事务并发提交保证redo log 和MySQL数据库二进制日志的提交的顺序一致和 group commit,批量的将redo log和binlog刷入磁盘。
BLGC(Binary Log Group Commit),并把事务提交过程分成三个阶段,Flush stage、Sync stage、Commit stage。
MySQL 5.6 BLGC技术出现后,在这种情况下,不但MySQL数据库上层binlog写入是group commit的,InnoDB存储引擎层也是group commit的。此外还移除了原先的锁prepare_commit_mutex,从而大大提高了数据库的整体性。其事务的提交(commit)过程分成三个阶段,Flush stage、Sync stage、Commit stage。
Binlog组提交的基本思想是,引入队列机制保证Innodb commit顺序与binlog落盘顺序一致,并将事务分组,组内的binlog刷盘动作交给一个事务进行,实现组提交目的。在MySQL数据库上层进行提交时首先按顺序将其放入一个队列中,队列中的第一个事务称为leader,其他事务称为follow,leader控制着follow的行为。
从上图可以看出,每个阶段都有一个队列,每个队列有一个mutex(锁)保护,约定进入队列第一个线程为leader,其他线程为follower,所有事情交由leader去做,leader做完所有动作后,通知follower刷盘结束。BLGC就是将事务提交分为了3个阶段,FLUSH阶段,SYNC阶段和COMMIT阶段。
将每个事务的二进制日志写入内存中。
持有Lock_log mutex [leader持有,follower等待]。
获取队列中的一组binlog(队列中的所有事务)。
将binlog buffer到I/O cache。
通知dump线程dump binlog。
将内存中的二进制日志刷新到磁盘,若队列中有多个事务,那么仅一次fsync操作就完成了二进制日志的写入,这就是BLGC。
释放Lock_log mutex,持有Lock_sync mutex[leader持有,follower等待]。
将一组binlog 落盘(sync动作,最耗时,假设sync_binlog为1)。
leader根据顺序调用存储引擎层事务的提交,Innodb本身就支持group commit,因此修复了原先由于锁prepare_commit_mutex导致group commit失效的问题。
释放Lock_sync mutex,持有Lock_commit mutex[leader持有,follower等待]。
遍历队列中的事务,逐一进行innodb commit。
释放Lock_commit mutex。
唤醒队列中等待的线程。
说明:由于有多个队列,每个队列各自有mutex保护,队列之间是顺序的,约定进入队列的第一个线程为leader,因此FLUSH阶段的leader可能是SYNC阶段的follower,但是follower永远是follower。
当有一组事务在进行commit阶段时,其他新事物可以进行Flush阶段,从而使group commit不断生效。当然group commit的效果由队列中事务的数量决定,若每次队列中仅有一个事务,那么可能效果和之前差不多,甚至会更差。但当提交的事务越多时,group commit的效果越明显,数据库性能的提升也就越大。
binlog_max_flush_queue_time(MySQL 5.7.9版本失效)参数,控制二进制日志组提交中Flush阶段中等待的时间。默认是0,就是二进制日志组提交中Flush阶段中等待的时间,即使之前的一组事务完成提交,当前一组的事务也不马上进入Sync阶段,而是至少需要等待一段时间,这样做的好处是group commit的事务数量更多, 然而这也可能会导致事务的响应时间变慢。
注:binlog_max_flush_queue_time在MySQL的5.7.9及之后版本不再生效)参数,MySQL等待binlog_group_commit_sync_delay毫秒直到达, 或到binlog_group_commit_sync_no_delay_count事务个数时,将进行一次组提交。
由于其诞生早、结构严谨、高可用、高性能等特点,使其在传统数据库应用中大杀四方,金融、通信、能源、运输、零售、制造等各个行业的大型公司基本都是用了Oracle,早些年的时候,世界500强几乎100%都是Oracle的用户
主要在传统行业的数据化业务中,比如:银行、金融这样的对可用性、健壮性、安全性、实时性要求极高的业务;零售、物流这样对海量数据存储分析要求很高的业务。此外,高新制造业如芯片厂也基本都离不开Oracle;电商也有很多使用者,如京东(正在投奔Oracle)、阿里巴巴(计划去Oracle化)
MySQL的最初的核心思想,主要是开源、简便易用.
MySQL基本是生于互联网,长于互联网。其应用实例也大都集中于互联网方向,MySQL的高并发存取能力并不比大型数据库差,同时价格便宜,安装使用简便快捷,深受广大互联网公司的喜爱。
参考:
MySQL事务的实现原理:https://mp.weixin.qq.com/s/79HhQsZRzzuskP5p5LNONA
100道MySQL数据库经典面试题解析:https://mp.weixin.qq.com/s/iW0FfhP0vsyABoEiZUqsMQ
数据库连接池终于搞对了,这次直接从100ms优化到3ms:https://mp.weixin.qq.com/s/Bx8OmkRb7SlSWZ_wIOl2LA
今天跟大家聊聊分区表的底层原理: https://mp.weixin.qq.com/s/X0Pl-OC3jldiHgUlB3DTTA
Mysql 索引使用规则和设计优化:https://mp.weixin.qq.com/s/y32nKY46Zi6O_5M021E2ZA
总结三种 MySQL 大表优化方案:https://mp.weixin.qq.com/s/t4PcRPOwb2zsLz8slsVdsg
MySQL 中Redo与Binlog顺序一致性问题:https://www.cnblogs.com/mao3714/p/8734838.html
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。