目录
一、MySQL的逻辑架构与工作流程
二、MySQL存储引擎
1、MyISAM
2、InnoDB
3、MEMORY
4、ARCHIVE
三、索引
1、索引概念
2、索引的类型
3、索引的实现原理
(1)哈希索引
(2)B+Tree索引
B+Tree优点
4、聚集索引和非聚集索引
InnoDB——聚簇索引
MyISAM——非聚簇索引
四、事务
1、概念
2、事务的特征(ACID)
3、并发事务带来的问题
4、事务隔离级别
5、有关事务的命令
五、日志文件
1、重做日志(redo log)
2、回滚日志(undo log)
六、锁机制
1、MyISAM 只支持表锁(粒度大)
2、InnoDB 支持行锁(粒度小) 、表锁
3、悲观锁
4、乐观锁
5、间隙锁
6、意向锁
七、总结
客户端向服务器端发送一个指令,MySQL服务端的连接池中一个进程与他连接。连接之后通过SQL API传输给SQL解析器进行语法、词法、语义解析,解析完成后传输给SQL优化器进行优化,生成一个执行计划。从MySQL中自带的缓存开始查询,如果查找到了,则缓存通过SQL API传递给连接池返回给客户端;如果没有查找到,则通过SET从底层磁盘开始查询,查找到了返回给SQL API。每一次的活动都会有log日志来记录。
MySQL架构总共三层
首先,最上层的服务并不是MySQL独有的,大多数给予网络的客户端/服务器的工具或者服务都有类似的架构。比如:连接处理、授权认证、安全等。
第二层的架构包括大多数的MySQL的核心服务。包括:查询解析、分析、优化、缓存以及所有的内置函数(例如:日期、时间、数学和加密函数)。同时,所有的跨存储引擎的功能都在这一层实现:存储过程、触发器、视图等。
第三层包含了存储引擎。存储引擎负责MySQL中数据的存储和提取。服务器通过API和存储引擎进行通信。这些接口屏蔽了不同存储引擎之间的差异,使得这些差异对上层的查询过程透明化。存储引擎API包含十几个底层函数,用于执行“开始一个事务”等操作。但存储引擎一般不会去解析SQL(InnoDB会解析外键定义,因为其本身没有实现该功能),不同存储引擎之间也不会相互通信,而只是简单的响应上层的服务器请求。
推荐写的比较详细的博客:MySQL逻辑架构及工作流程
存储引擎在MySQL的逻辑架构中位于第三层,负责MySQL中的数据的存储和提取。MySQL存储引擎有很多,不同的存储引擎保存数据、索引的方式和锁机制是不同的。
不支持外键,不支持事务,支持全文索引,表锁,支持B树索引(B+树)。
MyISAM采用的是索引与数据分离的形式,非聚集索引,将数据保存在三个文件中.frm .MYD(存放数据的) .MYI(存放索引的)。
.frm文件
在MYSQL中建立任何一张数据表,在其数据目录对应的数据库目录下都有对应表的.frm文件,.frm文件是用来保存每个数据表的元数据(meta)信息,包括表结构的定义等,.frm文件跟数据库存储引擎无关,也就是任何存储引擎的数据表都必须有.frm文件,命名方式为数据表名.frm,如user.frm. .frm文件可以用来在数据库崩溃时恢复表结构,.frm 文件与操作系统和数据库引擎无关,都有这么个与表名同名文件。
支持外键,支持事务,5.6版本之后支持全文索引,行锁 ,支持B树索引(B+树)。
5.6版本前的MySQL自带的全文索引只能用于MyISAM存储引擎,如果是其它数据引擎,那么全文索引不会生效。5.6版本之后InnoDB存储引擎开始支持全文索引
Innodb采用聚集索引的方式。使用InnoDB时,会将数据表分为.frm 和 idb两个文件进行存储。
支持事务,主要是面向在线事务处理方面的应用,特点是行锁设计,并支持外键。没有主键,没有唯一键,
为每一行生产一个6字节的行id,作为主键。
默认hash索引,但也支持B树索引。
数据存放在内存中,如果发生宕机、断电等情况,内存中的数据就丢失了,适用于临时数据的存储。
为了提供效率,将varchar()类型当成char类型来存储(不用计算要分配多少个字节,固定分配那么多,但是浪费内存)
不支持BLOB 类型(图片存储类型)和TEXT类型(大文本字段)。如果数据中有text和BLOB类型,数据库会把这些数字转换到磁盘上。
不支持索引,只支持insert select操作
使用压缩算法将数据进行压缩后存储,压缩比例一般是1:10,主要提供插入和压缩功能。
适用于不常用数据的存储(例如:日志数据,查询不频繁 )
对比:
该图来自:MySQL表类型和存储引擎
索引是一种特殊的文件(InnoDB数据表上的索引是表空间的一个组成部分),它们包含着对数据表里所有记录的引用指针。更通俗的说,数据库索引好比是一本书前面的目录,能加快数据库的查询速度。
“索引是一个排序的列表,在这个列表中存储着索引的值和包含这个值的数据所在行的物理地址,在数据十分庞大的时候,索引可以大大加快查询的速度,这是因为使用索引后可以不用扫描全表来定位某行的数据,而是先通过索引表找到该行数据对应的物理地址然后访问相应的数据。”
(1)主键索引
即主索引,根据主键建立索引,不允许重复,不允许空值;
(2)普通索引
用表中的普通列构建的索引,没有任何限制
创建普通索引:create index 索引名字 on 表名(列名)
(3)唯一索引
它与前面的普通索引类似,不同的就是:索引列的值必须唯一,但允许有空值。如果是组合索引,则列值的组合必须唯一。
创建唯一索引:create unique index 索引名字 on 表名(列名)
(4)全文索引
用大文本对象的列构建的索引
MySQL从3.23.23版开始支持全文索引和全文检索,FULLTEXT索引仅可用于 MyISAM 表;他们可以从CHAR、VARCHAR或TEXT列中作为CREATE TABLE语句的一部分被创建,或是随后使用ALTER TABLE 或CREATE INDEX被添加。
(5)组合索引
用多个列组合构建的索引,这多个列中的值不允许有空值。满足最左前缀原则,自左向右依次查询。
MySQL支持诸多存储引擎,而各种存储引擎对索引的支持也各不相同,因此MySQL数据库支持多种索引类型,如BTree索引,B+Tree索引,哈希索引,全文索引等等。
只有memory(内存)存储引擎支持哈希索引,哈希索引用索引列的值计算该值的hashCode,然后在hashCode相应的位置存执该值所在行数据的物理位置,因为使用散列算法,因此访问速度非常快,但是一个值只能对应一个hashCode,而且是散列的分布方式,因此哈希索引不支持范围查找和排序的功能。
例如创建一个stu表,id为主键,创建主索引和辅助索引
B+Tree是BTree的一个变种,设d为树的度数,h为树的高度,B+Tree和BTree的不同主要在于:
1、磁盘读写代价更低
一般来说B+Tree比BTree更适合实现外存的索引结构,因为存储引擎的设计专家巧妙的利用了外存(磁盘)的存储结构,即磁盘的最小存储单位是扇区(sector),而操作系统的块(block)通常是整数倍的扇(sector),操作系统以页(page)为单位管理内存,一页(page)通常默认为4K,数据库的页通常设置为操作系统页的整数倍,因此索引结构的节点被设计为一个页的大小,然后利用外存的“预读取”原则,每次读取的时候,把整个节点的数据读取到内存中,然后在内存中查找,已知内存的读取速度是外存读取I/O速度的几百倍,那么提升查找速度的关键就在于尽可能少的磁盘I/O,那么可以知道,每个节点中的key个数越多,那么树的高度越小,需要I/O的次数越少,因此一般来说B+Tree比BTree更快,因为B+Tree的非叶节点中不存储data,就可以存储更多的key。
2、查询速度更稳定
由于B+Tree非叶子节点不存储数据(data),因此所有的数据都要查询至叶子节点,而叶子节点的高度都是相同的,因此所有数据的查询速度都是一样的。
聚集索引:索引的顺序就是数据的物理存储顺序(索引当成数据一部分存储)
非聚集索引:索引顺序与数据物理排列顺序无关(索引与数据分离)
下图可以形象的说明聚簇索引和非聚簇索引的区别
从上图中可以看到聚簇索引的辅助索引的叶子节点的data存储的是主键的值,主索引的叶子节点的data存储的是数据本身,也就是说数据和索引存储在一起,并且索引查询到的地方就是数据(data)本身,那么索引的顺序和数据本身的顺序就是相同的;
而非聚簇索引的主索引和辅助索引的叶子节点的data都是存储的数据的物理地址,也就是说索引和数据并不是存储在一起的,数据的顺序和索引的顺序并没有任何关系,也就是索引顺序与数据物理排列顺序无关。
1、较频繁的作为查询条件的字段应该创建索引
2、唯一性太差的字段不适合单独创建索引,即使频繁作为查询条件
3、更新非常频繁的字段不适合创建索引
4、不会出现在 WHERE 子句中的字段不该创建索引
部分参考博客:深入理解MySQL索引原理和实现——为什么索引可以加速查询?(<--这个博客写的很详细、全面)
事务就是一组SQL语句的集合
MySQL 事务主要用于处理操作量大,复杂度高的数据。比如说,在人员管理系统中,你删除一个人员,你即需要删除人员的基本资料,也要删除和该人员相关的信息,如信箱,文章等等,这样,这些数据库操作语句就构成一个事务!
A 原子性 SQL语句要么全部执行成功,要么全部执行失败。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。(不可中断的操作-原子操作)
C 一致性 保证完整性约束。在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。
I 隔离性 消除事务间的相互影响。数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。
D 持久性 事务一旦提交成功后,事务执行的结果应该在磁盘上永久存储。
没有隔离性的话,会产生这三种错误
(1)脏读 为了防止 让他只能看见开启前的结果
(针对未提交数据)
如果一个事务中对数据进行了更新,但事务还没有提交,另一个事务可以“看到”该事务没有提交的更新结果,这样造成的问题就是,如果第一个事务回滚,那么,第二个事务在此之前所“看到”的数据就是一笔脏数据。
如何解决?让第二个事务只能看见第一个事务执行前的结果。
(2)不可重复读 不能看见事务执行中的结果 只让他看见 执行前和后的结果
但是看见不同的结果 更改隔离级别可以解决这个问题 实现;a对b是透明的 b看不到他的任何操作了 所以只能看见他执行前的操作
(针对其他提交前后,读取数据本身的对比。由于修改操作导致的 )
不可重复读取是指同一个事务在整个事务过程中对同一笔数据进行读取,每次读取结果都不同。如果事务1在事务2的更新操作之前读取一次数据,在事务2的更新操作之后再读取同一笔数据一次,两次结果是不同的。
如何解决?让一个事务的更新操作结果只有在该事务提交之后,另一个事务才可能读取到同一笔数据更新后的结果。
(针对其他提交前后,读取数据条数的对比。由于插入、删除操作导致的)
幻读是指同样一笔查询在整个事务过程中多次执行后,查询所得的结果集是不一样的。幻读针对的是多笔记录。在Read Uncommitted隔离级别下, 不管事务2的插入操作是否提交,事务1在插入操作之前和之后执行相同的查询,取得的结果集是不同的,所以,Read Uncommitted同样无法避免幻读的问题。
在这些事务隔离级别下,还存在哪些问题?
图来源:四种事务的隔离级别
(1)未提交读:一个事务可以读取另一个事务并未提交的更新结果。
(2)不可重复读:在该隔离级别下,一个事务的更新操作结果只有在该事务提交之后,另一个事务才可能读取到同一笔数据更新后的结果。 所以,Read Committed可以避免Read Uncommitted隔离级别下存在的脏读问题, 但,无法避免不可重复读取和幻读的问题。
(3)可重复读:可以保证在整个事务的过程中,对同一笔数据的读取结果是相同的,不管其他事务是否同时在对同一笔数据进行更新,也不管其他事务对同一笔数据的更新提交与否。 Repeatable Read隔离级别避免了脏读和不可重复读取的问题,但无法避免幻读。(mysql默认隔离级别)但其实在这个级别下发现并未出现幻读问题。被另一个机制-间隙锁解决了 ,例如把插入的这条数据加锁,另外一条语句插入的话插入不了。
实现:让a对b是透明的,b看不到他的任何操作了 ,所以只能看见他执行前的操作。
(4)可序列化(串行化):最为严格的隔离级别,所有的事务操作都必须依次顺序执行,可以避免其他隔离级别遇到的所有问题,是最为安全的隔离级别, 但同时也是性能最差的隔离级别,因为所有的事务在该隔离级别下都需要依次顺序执行,所以,并发度下降,吞吐量上不去,性能自然就下来了。 因为该隔离级别极大的影响系统性能,所以,很少场景会使用它。通常情况下,我们会使用其他隔离级别加上相应的并发锁的机制来控制对数据的访问,这样既保证了系统性能不会损失太大,也能够一定程度上保证数据的一致性。
set autocommit = 0; 将事物的自动提交关闭
commit; 提交事务
begin; 开启事务
rollback; 回滚
select @@tx_isolation; 查看事务隔离级别
set tx_isolation= "READ-UNCOMMITTED"; 设置事务的隔离级别为未提交读
系统隔离级别不能修改,更改的是会话的隔离级别 。
确保事务的持久性。redo日志记录事务执行后的状态,用来恢复未写入data file的已成功事务更新的数据。防止在发生故障的时间点,尚有脏页未写入磁盘,在重启mysql服务的时候,根据redo log进行重做,从而达到事务的持久性这一特性。
保证事务的原子性,保存了事务发生之前的数据的一个版本,可以用于回滚。
事务的原子性是由重做日志和回滚日志保证的
事务的持续性由日志里的日志先行保证
锁是计算机协调多个进程或线程并发访问某一资源的机制。
粒度是指数据仓库的数据单位中保存数据的细化或综合程度的级别。细化程度越高,粒度级就越小;相反,细化程度越低,粒度级就越大。
我们首先来创建一个表stu,并插入一行数据,用来下面详细讲述表锁和行锁。
读锁 共享读锁 和读锁兼容 和写锁不兼容 (我在读,你可以读但是不能写)
写锁 独占写锁 不兼容读写锁(我在写,你不能看也不能写)
MyISAM存储引擎的读锁和写锁是互斥的,读写操作是串行的。
MyISAM在执行查询语句(SELECT)前,会自动给涉及的所有表加读锁,在执行更新操作 (UPDATE、DELETE、INSERT等)前,会自动给涉及的表加写锁,这个过程并不需要用户干预,因此,用户一般不需要直接用LOCK TABLE命令给MyISAM表显式加锁。在示例中,显式加锁基本上都是为了演示而已,并非必须如此。
lock table test read或者write;加读锁或写锁
unlock tables;释放锁
MyISAM存储引擎的读锁阻塞写例子:(兼容读锁,双方都只能读、不能写)
MyISAM存储引擎的写锁阻塞读写例子: (独占写锁,只有持有锁的可以读写,没有持有锁的读写都会被阻塞)
读锁 共享锁 可以和读锁共享一行数据 (多个事务只能读数据不能改数据)
写锁 排他锁 排斥除我本身以外的其他锁(InnoDB中select操作不加锁,当事务来处理 ,通过隔离性来保证两个事务不相互影响)。排他锁指的是一个事务在一行数据加上排他锁后,其他事务不能再在其上加其他的锁。
mysql InnoDB引擎默认的修改数据语句:update,delete,insert都会自动给涉及到的数据加上排他锁,select语句默认不会加任何锁类型,如果加排他锁可以使用select …for update语句,加共享锁可以使用select … lock in share mode语句。所以加过排他锁的数据行在其他事务种是不能修改数据的,也不能通过for update和lock in share mode锁的方式查询数据,但可以直接通过select …from…查询数据,因为普通查询没有任何锁机制。
事务可以通过以下语句显式给记录集加共享锁或排他锁:
通过上图可以看到我的数据库默认存储引擎是MyISAM。(mysql版本5.5以下默认的是MyISAM存储引擎,版本5.5以上认的是InnoDB存储引擎。)
举例排他锁:
举例共享锁:
总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。
总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。
版本号机制:在表中加一个版本号,每次修改数据,这个版本号就自动加1。查询不会修改版本号。
间隙锁是把查询结果的范围间隙加锁。保证某个间隙内的数据在锁定情况下不会发生任何变化。
粒度小(行锁)容易发生死锁
所有的意向锁都是兼容的
等意向锁锁完了之后再加读写锁
行锁和表锁共存的问题
考虑这个例子:
事务A锁住了表中的一行,让这一行只能读,不能写。
之后,事务B申请整个表的写锁。
如果事务B申请成功,那么理论上它就能修改表中的任意一行,这与A持有的行锁是冲突的。
数据库需要避免这种冲突,就是说要让B的申请被阻塞,直到A释放了行锁。
数据库要怎么判断这个冲突呢?
step1:判断表是否已被其他事务用表锁锁表
step2:判断表中的每一行是否已被行锁锁住。
注意step2中通过遍历查询,这样的判断方法效率实在不高,因为需要遍历整个表。
于是就有了意向锁。
在意向锁存在的情况下,事务A必须先申请表的意向共享锁,成功后再申请一行的行锁。
在意向锁存在的情况下,上面的判断可以改成
step1:不变
step2:发现表上有意向共享锁,说明表中有些行被共享行锁锁住了,因此,事务B申请表的写锁会被阻塞。
最终结论:
(1)申请意向锁的动作是数据库完成的,就是说,事务A申请一行的行锁的时候,数据库会自动先开始申请表的意向锁,不需要我们程序员使用代码来申请。
(2)IX,IS是表级锁,不会和行级的X,S锁发生冲突。只会和表级的X,S发生冲突
这一段是参考这个博主的 mysql数据库意向锁意义
链接:https://www.jianshu.com/p/38dd4f64ebd1
1、MyISAM
B+树索引(非聚集索引)、表锁、不支持外键、不支持事务、支持全文索引
2、InnoDB
B+树索引(聚集索引)、行锁表锁、支持外键、支持事务、5.6版本之后支持全文索引
3、MEMORY
哈希索引、也支持B树索引、表锁、不支持外键、不支持事务、不支持全文索引
参考:对于脏读,不可重复读,幻读的一点理解,看懂红字很关键
MySQL中的锁(表锁、行锁,共享锁,排它锁,间隙锁)