-
2017-10-29 22:46:29
工作中需要借鉴MySQL对于select的具体实现,在网上搜了很久,几乎都是介绍原理的,对于实现细节都没有介绍,无奈之下只得自己对着源码gdb。结合以前对于sql解析的了解,对mysql select的具体实现有了大致的了解,总结一下。
如果要gdb单步调试,需要在编译MySQl时加上debug选项,参见这篇博客.编译好以后就可以用gdb启动了。如果希望mysql运行时有日志输出,可以指定输出文件的路径和日志类型:
--debug=d,info,error,query,enter,general,where:O,/tmp/mysqld.trace
日志对MySQl内部逻辑的了解还是挺有用的。MySQl在设计时,采用了这样的思路:针对主要应用场景选择一个或几个性能优异的核心算法作为引擎,然后努力将一些非主要应用场景作为该算法的特例或变种植入到引擎当中。具体而言,MySQL的select查询中,核心功能就是JOIN查询,因此在设计时,核心实现JOIN功能,对于其它功能,都通过转换为JOIN来实现。
比如
select id, name from student;
,MySQL在执行时,也会转换为JOIN来操作。用gdb单步跟踪后可以看出MySQL的执行过程大致如下:
- 收到请求后分配线程处理;
- sql解析,MySQL解析完sql以后,会生成很多item类。item类是sql解析和执行中最重要的类之一,对于它的介绍可以参见这里。
- 执行sql,可以看到
JOIN::exec
,MySQL是将任何select都转换为JOIN来处理的。
以sql:
select A.id, B.score from student A left join subject B on A.id=B.id where A.age > 10 and B.score > 60;
为例来说明上面的步骤3的具体过程。首先,MySQL在执行sql之前,会对sql进行优化处理,具体是在
JOIN::optimise
函数中完成。MySQL针对JOIN的优化做的非常好,因此才会将其他操作都转换为性能实现的非常好的JOIN操作。对于上面的sql,MySQL在执行时,会将join的key也转换为一个where条件:A.id=B.id
来执行,那么经过处理后,上面的sql就有了3个where条件:-
A.age > 10
; -
A.id = B.id
; -
B.score > 60
;
预处理完以后开始执行,即
JOIN::exec
函数,首先会调用send_fields
函数,将最终结果的信息返回,然后调用do_select
。MySQL的join是采用nested loop join,可以参见这篇博客。在do_select
函数中,通过调用sub_select
函数来具体实现join功能。在上面的例子中,需要完成2个join:先join表A,再join表B(这里请注意,不是涉及几个表,就需要join几个表,MySQL的join优化还是挺强大的,具体解释见后)。在MySQL进行sql解析时,会生成一个需要join的表的list,后面会挨个对该list的表进行join操作。
继续gdb,在
sub_select
函数中,可以看到这样一行代码:(*join_tab->read_first_record)(join_tab)
这个就是读取表A的第一行结果,可以看join_tab
里面的信息有表A的名字。接下来就是很关键的一个函数:evaluate_join_record
,这个函数主要做2件事:- 将当前已经拿到的信息进行where条件计算,判断是否需要继续往下走;
- 递归JOIN;
还是以上面的sql为例,首先执行第一个join,此时会遍历表A的每一行结果,每遍历一个结果,会进行where条件的判断。这里需要注意:当前的where条件判断只会判断已经读出来的列,由于此时只读出来表A的数据,因此现在只能对第一个where条件,即
A.age > 10
进行判断,如果满足,则递归调用join:sql_select.cc: 11037 rc=(*join_tab->next_select)(join, join_tab+1, 0);
,这里的next_select函数就是sub_select
,MySQL就是这样来实现递归操作的。如果不满足,则不会递归join,而是继续到下一行数据,从而达到剪枝的目的。继续跟下去,此时通过上面的
next_select
递归的又调用到sub_select
上,同样会走上面的逻辑,即先read_first_record
,然后evaluate_join_record
,这里由于表A和表B的数据都有了,于是可以对上面后面2个where条件:A.id = B.id
和B.score > 60
进行判断了。到此,所有的where条件都已经判断完毕,如果当前行对3个where条件都满足,就可以将结果输出。以上就是select实现的大体过程,主要有2点,一个是join是采用递归实现的,另一个是每读一个表的数据,会将当前的where条件进行计算,剪枝。还有一个细节没有提到:MySQL是如何进行where条件判断的?或者说,MySQL是如何进行表达式计算的?
答案就是前面提到的item类。当MySQL在解析时,会将sql解析为很多item,同时也会建立各个item之间的关系。对于表达式,会生成一棵语法树。比如表达式:
B.score > 60
,此时会生成3个item:B.score
、>
和60
,其中B.score
和60
分别是>
的左右孩子,这样,求表达式的值时,就是求>
的val_int()
,然后就会递归的调用左右子树的val_int()
,再做比较判断即可。还有一个问题:如何求
B.score
的val_int()
?对于此问题的答案我没有具体看过,根据之前一个同事的sql实现方式,我是这样推测的:B.score
是数据表中的真实值,因此它的值肯定是通过去表中获取。在item类中,有一个函数:fix_field
,它是用于告诉外界,去哪里获取此item的值,往往在sql执行的预处理阶段调用。于是在预处理时,告诉该item去某个固定buffer读取结果,同时,每当从表中读出一行数据时,将该数据保存在该buffer中,这样就可以将两者关联起来。这个部分纯属个人推测,感兴趣的同学可以自己根据源码看看。再回到之前提到的一点,如果我们将sql稍微改一下:
select A.id, B.score from student A left join subject B on A.id=B.id where B.score > 60;
,即去掉第一个where条件,此时会发生什么?答案是,MySQL会做一个优化,将sql转换为
select B.id, B.score from subject B where B.score > 60
,这样就不需要A同B join的逻辑了。实际上最开始我在gdb时就用的这条sql,结果死活看不到递归调用sub_select
的场景,还以为原理不对,后来才发现是MySQL优化捣的乱。更多相关内容 -
MySQL索引底层实现原理
2021-01-27 13:11:53MySQL官方对索引的定义为:索引(Index)是帮助MySQL高效获取数据的数据结构。提取句子主干,就可以得到索引的本质:索引是数据结构。我们知道,数据库查询是数据库的最主要功能之一。我们都希望查询数据的速度能尽... -
MySQL DISTINCT 的基本实现原理详解
2020-09-09 02:37:20主要介绍了MySQL DISTINCT 的基本实现原理详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 -
MySQL事务及Spring隔离级别实现原理详解
2020-09-08 21:15:52主要介绍了MySQL事务及Spring隔离级别实现原理详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 -
深入学习MySQL事务:ACID特性的实现原理
2021-02-24 04:40:18本文将首先介绍MySQL事务相关的基础概念,然后介绍事务的ACID特性,并分析其实现原理。MySQL博大精深,文章疏漏之处在所难免,欢迎批评指正。事务(Transaction)是访问和更新数据库的程序执行单元;事务中可能包含... -
mysql行级锁实现原理是什么
2021-01-19 03:24:12mysql行级锁实现原理:1、InnoDB行锁是通过给索引项加锁来实现的,这一点mysql和oracle不同;2、InnoDB这种行级锁决定,只有通过索引条件来检索数据,才能使用行级锁,否则,直接使用表级锁。mysql行级锁实现原理:...mysql行级锁实现原理:1、InnoDB行锁是通过给索引项加锁来实现的,这一点mysql和
oracle不同;2、InnoDB这种行级锁决定,只有通过索引条件来检索数据,才能使用行级锁,否则,
直接使用表级锁。
mysql行级锁实现原理:
锁是在执行多线程时用于强行限定资源访问的同步机制,数据库锁根据锁的粒度可分为行级锁,
表级锁和页级锁
行级锁
行级锁是mysql中粒度最细的一种锁机制,表示只对当前所操作的行进行加锁,行级锁发生冲突
的概率很低,其粒度最小,但是加锁的代价最大。行级锁分为共享锁和排他锁。
特点:
开销大,加锁慢,会出现死锁;锁定粒度最小,发生锁冲突的概率最大,并发性也高;
实现原理:
InnoDB行锁是通过给索引项加锁来实现的,这一点mysql和oracle不同,后者是通过在数据库中
对相应的数据行加锁来实现的,InnoDB这种行级锁决定,只有通过索引条件来检索数据,才能使用行
级锁,否则,直接使用表级锁。
特别注意:
使用行级锁一定要使用索引
举个栗子:
创建表结构CREATE TABLE `developerinfo` (
`userID` bigint(20) NOT NULL,
`name` varchar(255) DEFAULT NULL,
`passWord` varchar(255) DEFAULT NULL,
PRIMARY KEY (`userID`),
KEY `PASSWORD_INDEX` (`passWord`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
插入数据INSERT INTO `developerinfo` VALUES ('1', 'liujie',
'123456');
INSERT INTO `developerinfo` VALUES ('2', 'yitong', '123');
INSERT INTO `developerinfo` VALUES ('3', 'tong',
'123456');
(1)通过主键索引来查询数据库使用行锁
打开三个命令行窗口进行测试命令行窗口1命令行窗口2命令行窗口3
mysql> set autocommit = 0;Query OK,
0 rows affectedmysql> select * from developerinfo where userid = '1' for
update;+--------
+--------+----------+| userID | name | passWord |+--------+--------
+----------+| 1 | liujie | 123456 |+--------
+--------+----------+1 row in setmysql> set autocommit = 0;Query OK,
0 rows affected
mysql> select * from developerinfo where userid = '1' for
update;
等待mysql> set autocommit = 0;
Query OK, 0 rows
affected
mysql> select * from developerinfo where userid = '3' for
update;
+--------+------+----------+
| userID | name | passWord |
+--------
+------+----------+
| 3 | tong | 123456 |
+--------
+------+----------+
1 row in set
mysql>
commit;Query OK,
0 rows affectedmysql> select * from
developerinfo where userid = '1' for update;+--------+--------
+----------+| userID | name | passWord |+--------+--------
+----------+| 1 | liujie | 123456 |+--------
+--------+----------+1 row in set
(2)查询非索引的字段来查询数据库使用行锁
打开两个命令行窗口进行测试命令行窗口1命令行窗口2
mysql> set autocommit=0;
Query OK, 0 rows affected
mysql>
select * from developerinfo where name = 'liujie' for update;
+--------
+--------+----------+
| userID | name | passWord |
+--------+--------
+----------+
| 1 | liujie | 123456 |
+--------
+--------+----------+
1 row in setmysql> set
autocommit=0;Query OK, 0 rows affectedmysql> select * from developerinfo
where name = 'tong' for update;
等待
mysql> commit;
Query OK, 0 rows affectedmysql> select * from developerinfo where name = 'liujie' for
update;
+--------+--------+----------+
| userID | name | passWord |
+--------+--------+----------+
| 1 | liujie | 123456 |
+--------+--------+----------+
1 row in set
(3)查询非唯一索引字段来查询数据库使用行锁锁住多行
mysql的行锁是针对索引假的锁,不是针对记录,所以可能会出现锁住不同记录的场景
打开三个命令行窗口进行测试命令行窗口1命令行窗口2命令行窗口3
mysql> set autocommit=0;
Query OK, 0 rows affected
mysql>
select * from developerinfo where password = '123456
' for update;
+--------+--------+----------+
| userID | name | passWord |
+--------
+--------+----------+
| 1 | liujie | 123456 |
|
3 | tong | 123456 |
+--------+--------+----------
+
2 rows in setmysql> set autocommit =0 ;
Query OK, 0 rows affected
mysql> select * from developerinfo where userid =
'1' for update;
等待mysql> set autocommit = 0;
Query OK, 0 rows
affected
mysql> select * from developerinfo where userid = '2
' for
update;
+--------+--------+----------+
| userID | name | passWord |
+--------+--------+----------+
| 2 | yitong | 123
|
+--------+--------+----------+
1 row in set
commit;mysql> select * from developerinfo where userid = '1' for
update;
+--------+--------+----------+
| userID | name | passWord |
+--------+--------+----------+
| 1 | liujie | 123456 |
+--------+--------+----------+
1 row in set
(4)条件中使用索引来操作检索数据库时,是否使用索引还需有mysql通过判断不同执行计划来
决定,是否使用该索引,如需判定如何使用explain来判断索引,请听下回分解更多相关免费学习推荐:mysql教程(视频)
-
面试刷题29:mysql事务隔离实现原理?
2020-12-14 14:09:27我是李福春,今天的问题是,mysql是如何实现事务隔离的?在实际开发中应该如何正确的使用事务? ACID特性 事务首先具备ACID特性,即 Atomic原子性, Consistency一致性 Isolation隔离性 durability持久性; 事务... -
MySQL学习(七):Innodb存储引擎索引的实现原理详解
2020-09-09 04:13:18主要介绍了Innodb存储引擎索引的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧 -
MySQL的MVCC及实现原理
2021-02-07 09:58:03MVCC在MySQL InnoDB中的实现主要是为了提高数据库并发性能,用更好的方式去处理读-写冲突,做到即使有读写冲突时,也能做到不加锁,非阻塞并发读 什么是当前读和快照读? 在学习MVCC多版本并发控制之前,我们必须...目录
MySql学习专栏
3. MySQL5.7开启binlog日志,及数据恢复简单示例
一、前提概要
1. 什么是MVCC
MVCC,全称Multi-Version Concurrency Control,即多版本并发控制。MVCC是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问,在编程语言中实现事务内存。
MVCC在MySQL InnoDB中的实现主要是为了提高数据库并发性能,用更好的方式去处理读-写冲突,做到即使有读写冲突时,也能做到不加锁,非阻塞并发读。
2. 什么是当前读和快照读
在学习MVCC多版本并发控制之前,我们必须先了解一下,什么是MySQL InnoDB下的当前读和快照读?
当前读
像select 语句 lock in share mode(共享锁), select 语句 for update ; update, insert ,delete(排他锁)这些操作都是一种当前读,为什么叫当前读?就是它读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。
快照读
像不加锁的select * from 操作就是快照读,即不加锁的非阻塞读,不涉及其他锁之间的冲突;快照读的前提是隔离级别不是串行级别,串行级别下的快照读会退化成当前读;之所以出现快照读的情况,是基于提高并发性能的考虑,快照读的实现是基于多版本并发控制,即MVCC,可以认为MVCC是行锁的一个变种,但它在很多情况下,避免了加锁操作,降低了开销;既然是基于多版本,即快照读可能读到的并不一定是数据的最新版本,而有可能是之前的历史版本。
说白了MVCC就是为了实现读(select)-写冲突不加锁,而这个读指的就是快照读, 而非当前读,当前读实际上是一种加锁的操作,是悲观锁的实现。
3. 当前读,快照读和MVCC的关系
准确的说,MVCC多版本并发控制指的是 “维持一个数据的多个版本,使得读写操作没有冲突” 这么一个概念。仅仅是一个理想概念
而在MySQL中,实现这么一个MVCC理想概念,我们就需要MySQL提供具体的功能去实现它,而快照读就是MySQL为我们实现MVCC理想模型的其中一个具体非阻塞读功能。而相对而言,当前读就是悲观锁的具体功能实现。
要说的再细致一些,快照读本身也是一个抽象概念,再深入研究。MVCC模型在MySQL中的具体实现则是由 3个隐式字段,undo日志 ,Read View 等去完成的,具体可以看下面的MVCC实现原理
MVCC能解决什么问题,好处是?
数据库并发场景有三种,分别为:
读-读:不存在任何问题,也不需要并发控制
读-写:有线程安全问题,可能会造成事务隔离性问题,可能遇到脏读,幻读,不可重复读
写-写:有线程安全问题,可能会存在更新丢失问题,比如第一类更新丢失,第二类更新丢失
4. MVCC带来的好处是
多版本并发控制(MVCC)是一种用来解决读-写冲突的无锁并发控制,也就是为事务分配单向增长的时间戳,为每个修改保存一个版本,版本与事务时间戳关联,读操作只读该事务开始前的数据库的快照。
所以MVCC可以为数据库解决以下问题:
在并发读写数据库时,可以做到在读(select)操作时不用阻塞写操作,写操作也不用阻塞读操作,提高了数据库并发读写的性能
同时还可以解决脏读,幻读,不可重复读等事务隔离问题,但不能解决更新丢失问题
总之,MVCC就是因为大牛们,不满意只让数据库采用悲观锁这样性能不佳的形式去解决读-写冲突问题,而提出的解决方案,所以在数据库中,因为有了MVCC,所以我们可以形成两个组合:
MVCC + 悲观锁
MVCC解决读写冲突,悲观锁解决写写冲突
MVCC + 乐观锁
MVCC解决读写冲突,乐观锁解决写写冲突
这种组合的方式就可以最大程度的提高数据库并发性能,并解决读写冲突,和写写冲突导致的问题
二、MVCC的实现原理
MVCC的目的就是多版本并发控制,在数据库中的实现,就是为了解决读写冲突,它的实现原理主要是依赖记录中的 3个隐式字段,undo日志 ,Read View 来实现的。所以我们先来看看这个三个point的概念:
隐式字段
每行记录除了我们自定义的字段外,还有数据库隐式定义的DB_TRX_ID,DB_ROLL_PTR,DB_ROW_ID等字段
- DB_TRX_ID
6byte,最近修改(修改/插入)事务ID:记录创建这条记录/最后一次修改该记录的事务ID
- DB_ROLL_PTR
7byte,回滚指针,指向这条记录的上一个版本(存储于rollback segment里)
- DB_ROW_ID
6byte,隐含的自增ID(隐藏主键),如果数据表没有主键,InnoDB会自动以DB_ROW_ID产生一个聚集索引
如上图☝,DB_ROW_ID是数据库默认为该行记录生成的唯一隐式主键,DB_TRX_ID是当前操作该记录的事务ID,而DB_ROLL_PTR是一个回滚指针,用于配合undo日志,指向上一个旧版本
undo log日志主要分为两种
insert undo log
代表事务在insert新记录时产生的undo log, 只在事务回滚时需要,并且在事务提交后可以被立即丢弃
update undo log
事务在进行update或delete时产生的undo log; 不仅在事务回滚时需要,在快照读(select,当读的过程中有写的事务开始和提交,会造成读数据的脏读、不可重复读、幻读等)时也需要;所以不能随便删除,只有在快速读或事务回滚不涉及该日志时,对应的日志才会被purge线程统一清除。
purge
从前面的分析可以看出,为了实现InnoDB的MVCC机制,更新或者删除操作都只是设置一下老记录的deleted_bit,并不真正将过时的记录删除。
为了节省磁盘空间,InnoDB有专门的purge线程来清理deleted_bit为true的记录。为了不影响MVCC的正常工作,purge线程自己也维护了一个read view(这个read view相当于系统中最老活跃事务的read view);如果某个记录的deleted_bit为true,并且DB_TRX_ID相对于purge线程的read view可见,那么这条记录一定是可以被安全清除的。
对MVCC有帮助的实质是update undo log ,undo log实际上就是存在rollback segment中旧记录链,它的执行流程如下:
一、 比如一个有个事务插入person表插入了一条新记录,记录如下,name为Jerry, age为24岁,隐式主键是1,事务ID和回滚指针,我们假设为NULL
二、 现在来了一个事务1对该记录的name做出了修改,改为Tom
在事务1修改该行(记录)数据时,数据库会先对该行加排他锁
然后把该行数据拷贝到undo log中,作为旧记录,既在undo log中有当前行的拷贝副本
拷贝完毕后,修改该行name为Tom,并且修改隐藏字段的事务ID为当前事务1的ID, 我们默认从1开始,之后递增,回滚指针指向拷贝到undo log的副本记录,既表示我的上一个版本就是它
事务提交后,释放锁
三、 又来了个事务2修改person表的同一个记录,将age修改为30岁
在事务2修改该行数据时,数据库也先为该行加锁
然后把该行数据拷贝到undo log中,作为旧记录,发现该行记录已经有undo log了,那么最新的旧数据作为链表的表头,插在该行记录的undo log最前面
修改该行age为30岁,并且修改隐藏字段的事务ID为当前事务2的ID, 那就是2,回滚指针指向刚刚拷贝到undo log的副本记录
事务提交,释放锁
从上面,我们就可以看出,不同事务或者相同事务的对同一记录的修改,会导致该记录的undo log成为一条记录版本线性表,既事务链,undo log的链首就是最新的旧记录,链尾就是最早的旧记录。
三、Read View(读视图)
什么是Read View,说白了Read View就是事务进行快照读(select * from)操作的时候生产的读视图(Read View),在该事务执行的快照读的那一刻,会生成事务系统当前的一个快照,记录并维护系统当前活跃事务(未提交事务)的ID(当每个事务开启时,都会被分配一个ID, 这个ID是递增的,所以最新的事务,ID值越大)。
所以我们知道 Read View主要是用来做可见性判断的, 即当我们某个事务执行快照读的时候,对该记录创建一个Read View读视图,把它比作条件用来判断当前事务能够看到哪个版本的数据,既可能是当前最新的数据,也有可能是该行记录的undo log里面的某个版本的数据。
ReadView中主要包含4个比较重要的内容:
read view中活跃就是指未提交的事务
1. m_ids:表示在生成ReadView时当前系统中活跃的读写事务的事务id列表。
2. min_trx_id:表示在生成ReadView时当前系统中活跃的读写事务中最小的事务id,也就是m_ids中的最小 值。
3. max_trx_id:表示生成ReadView时系统中应该分配给下一个事务的id值。
4. creator_trx_id:表示生成该ReadView的快照读操作产生的事务id。
注意max_trx_id并不是m_ids中的最大值,事务id是递增分配的。比方说现在有id为1, 2, 3这三个事务,之后id为3的事务提交了。那么一个新的读事务在生成ReadView时, m_ids就包括1和2, min_trx_id的值就是1,max_trx_id的值就是4。
基于RR可重复读隔离级别,实现的基本原理
在select读数据的过程中,m_ids首次发现未提交的事务信息不会因在查找过程中其他事务id提交而把该事务id排除在外,直至查询到该事务链中最后提交的事务
读已提交的隔离级别
m_ids:保存事务系统中的活跃的事务id,基于m_ids中的id信息在事务链中直到查找到非活跃的事务id(已提交的事务,不管事务提交是否在read view生成后),此时就认为是该事务id查询的信息
RC隔离级别是在执行sql时生成read view,RR隔离级别是在事务开始生成read view
有了这个ReadView,这样在访问某条记录时,只需要按照下边的步骤判断记录的某个版本是否可见:
- 如果被访问版本的trx_id属性值与ReadView中的creator_trx_id值相同,意味着当前事务在访问它自己修改过的记录,所以该版本可以被当前事务访问。
- 如果被访问版本的trx_id属性值小于ReadView中的min_trx_id值,表明生成该版本的事务在当前事务生成ReadView前已经提交,所以该版本可以被当前事务访问。
- 如果被访问版本的trx_id属性值大于ReadView中的max_trx_id值,表明生成该版本的事务在当前事务生成ReadView后才开启,所以该版本不可以被当前事务访问。
- 如果被访问版本的trx_id属性值在ReadView的min_trx_id和max_trx_id之间,那就需要判断一下 trx_id属性值是不是在m_ids列表中,如果在,说明创建ReadView时生成该版本的事务还是活跃 的,该版本不可以被访问;如果不在,说明创建ReadView时生成该版本的事务已经被提交,该版本可以被访问。
四、RC,RR级别下的InnoDB快照读有什么不同
正是
Read View
生成时机的不同,从而造成RC,RR级别下快照读的结果的不同- 在RR级别下的某个事务的对某条记录的第一次快照读会创建一个快照及Read View, 将当前系统活跃的其他事务记录起来,此后在调用快照读的时候,还是使用的是同一个Read View,所以只要当前事务在其他事务提交更新之前使用过快照读,那么之后的快照读使用的都是同一个Read View,所以对之后的修改不可见;
- 即RR级别下,快照读生成Read View时,Read View会记录此时所有其他活动事务的快照,这些事务的修改对于当前事务都是不可见的。而早于Read View创建的事务所做的修改均是可见
- 而在RC级别下的,事务中,每次快照读都会新生成一个快照和Read View, 这就是我们在RC级别下的事务中可以看到别的事务提交的更新的原因
总之在RC隔离级别下,是每个快照读都会生成并获取最新的Read View;而在RR隔离级别下,则是同一个事务中的第一个快照读才会创建Read View, 之后的快照读获取的都是同一个Read View。
参考文档
-
Mysql主从同步的实现原理
2020-09-09 19:14:19主要介绍了Mysql主从同步的实现原理,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧 -
MySQL分组查询Group By实现原理详解
2020-09-10 02:44:28在MySQL 中,GROUP BY 的实现同样有多种(三种)方式,其中有两种方式会利用现有的索引信息来完成 GROUP BY,另外一种为完全无法使用索引的场景下使用。下面我们分别针对这三种实现方式做一个分析 -
Mysql中事务ACID的实现原理详解
2020-09-09 03:36:56主要给大家介绍了关于Mysql中事务ACID实现原理的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用Mysql具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧 -
MySQL 分库分表的实现原理及演示案例
2018-04-17 21:25:28MySQL 分库分表的实现原理及演示案例,非常不错,可以看看 -
MySQL事物实现原理之组提交(group commit).pdf
2021-03-26 16:43:46MySQL事物实现原理之组提交(group commit).pdf -
深入理解MySQL索引原理和实现——为什么索引可以加速查询?
2021-01-27 13:11:50说到索引,很多人都知道“索引是一个排序的列表,在这个列表中存储着索引的值和包含这个值的数据...本文内容涉及MySQL中索引的语法、索引的优缺点、索引的分类、索引的实现原理、索引的使用策略、索引的优化几部分。 -
了解MySQL(超详细的MySQL工作原理 体系结构)
2020-08-11 18:32:15了解MySQL(超详细的MySQL工作原理 体系结构) MySQL体系结构 MySQL内存结构 MySQL文件结构 innodb体系结构 了解MySQL前你需要知道的 引擎是什么: MySQL中的数据用各种不同的技术存储在文件(或者内存)中。...了解MySQL(超详细的MySQL工作原理 体系结构)
-
1.MySQL体系结构
-
2.MySQL内存结构
-
3.MySQL文件结构
-
4.innodb体系结构
一、了解MySQL前你需要知道的
-
引擎是什么:
MySQL中的数据用各种不同的技术存储在文件(或者内存)中。这些技术中的每一种技术都使用不同的存储机制、索引技巧、锁定水平并且最终提供广泛的不同的功能和能力。通过选择不同的技术,你能够获得额外的速度或者功能,从而改善你的应用的整体功能。
当我们理解了引擎这个概念,自然而然就知道引擎层的作用就提供各种不同引擎给你选择,然后用你选出来的引擎去处理sql语句
二、MySQL体系结构
MySQL 最重要、最与众不同的特性是它的存储引擎架构,这种架构的设计将查询处理 (Query Processing)及其他系统任务(Server Task)和数据的存储/提取相分离。这种 处理和存储分离的设计可以在使用时根据性能、特性,以及其他需求来选择数据存储 的方式。
-
由图,可以看出MySQL最上层是连接组件。下面服务器是由连接池、管理工具和服务、SQL接口、解析器、优化器、缓存、存储引擎、文件系统组成。-
用户:进行数据库连接的人。
-
支持接口:是第三方语言提供和数据库连接的接口,常见的有jdbc,odbc,c的标准api函数等等。
-
管理工具和服务:系统管理和控制工具,例如备份恢复、Mysql复制、集群等(见图)
-
连接层:提供与用户的连接服务,用于验证登录服务。
—> 连接池:由于每次建立建立需要消耗很多时间,连接池的作用就是将这些连接缓存下来,下次可以直接用已经建立好的连接,提升服务器性能。
- 服务层:完成大多数的核心服务功能。有sql接口,解析器parser,优化器optimizer,查询缓存 cache/buffer 。
—>SQL接口:接受用户的SQL命令,并且返回用户需要查询的结果。比如select * from就是调用SQL Interface
—>解析器: SQL命令传递到解析器的时候会被解析器验证和解析。解析器是由Lex和YACC实现的,是一个很长的脚本。其功能是:
a.将SQL语句分解成数据结构,并将这个结构传递到后续步骤,以后SQL语句的传递和处理就是基于这个结构的。
b.如果在分解构成中遇到错误,那么就说明这个sql语句是不合理的。
—>优化器:查询优化器,SQL语句在查询之前会使用查询优化器对查询进行优化。他使用的是“选取-投影-联接”策略进行查询。举一个例子:
select * from users where uname='admin';
1.这个select查询先根据where语句进行选取,而不是先将表全部查询出来以后再进行uname过滤。(选取)
2.这个select查询先根据*进行属性投影,而不是将属性全部取出以后再进行过滤。(投影)
3.将这两个查询条件联接起来生成最终查询结果。(联接)
—>缓存器: 查询缓存,如果查询缓存有命中的查询结果,查询语句就可以直接去查询缓存中取数据。
通过LRU算法将数据的冷端溢出,未来得及时刷新到磁盘的数据页,叫脏页。
这个缓存机制是由一系列小缓存组成的。比如表缓存,记录缓存,key缓存,权限缓存等 简而言之, 服务层执行过程:sql语句通过sql接口,服务器如果缓存cache有命中查询结果,直接读取数据。如果没有命中查询结果,由解析器进行sql语句的解析,预处理,经过优化器进行优化后提交给引擎层。通俗地说—>服务层告诉引擎层要做什么。
-
引擎层: 提供各种存储引擎,真正的负责MySQL中数据的存储和提取。常见有innodb myisam 。innodb支持全文索引,事务(高并发),行锁,myisam 性能优先。 mysql中查询引擎语句:show engines.
通俗地说—>引擎层会转发服务层解析出来的sql语句告诉存储层要做什么(增删改查)并且告诉存储层要以何种方式做(innodb myisam等等)。
-
存储层: 数据存储层,主要是将数据存储在运行于裸设备的文件系统之上,并完成与存储引擎的交互。
SQL的执行流程:数据库通常不会被单独使用,而是由其它编程语言通过SQL支持接口调用MySQL。由MySQL处理并返回执行结果。首先,其它编程语言通过SQL支持接口调用MySQL,MySQL收到请求后,会将该请求暂时放在连接池,并由管理服务与工具进行管理。当该请求从等待队列进入到处理队列时,管理器会将该请求传给SQL接口,SQL接口接收到请求后,它会将请求进行hash处理并与缓存中的数据进行对比,如果匹配则通过缓存直接返回处理结果;否则,去文件系统查询:由SQL接口传给后面的解析器,解析器会判断SQL语句是否正确,若正确则将其转化为数据结构。解析器处理完毕后,便将处理后的请求传给优化器控制器,它会产生多种执行计划,最终数据库会选择最优的方案去执行。确定最优执行计划后,SQL语句交由存储引擎处理,存储引擎将会到文件系统中取得相应的数据,并原路返回。
在我们专业老师的pdf资料里面每一层也讲得很细 见下
创建新表时如果不指定存储引擎,那么系统就会使用默认存储引擎,MySQL5.5 之前的 默认存储引擎是 MyISAM,5.5 之后改为了 InnoDB。 MySQL 中同一个数据库,不同的表格可以选择不同的存储引擎。
-
MyISAM 不支持事务、也不支持外键,其优势是访问的速度快,对事务完整性 没有要求或者以 SELECT、INSERT 为主的应用。每个 MyISAM 在磁盘上存储成 三个文件。第一个文件的名字以表的名字开始,扩展名指出文件类型。.frm 文件存储表定义。数据文件的扩展名为.MYD (MYData)。索引文件的扩展名是.MYI (MYIndex)
-
InnoDB 存储引擎提供了具有提交、回滚和崩溃恢复能力的事务安全。但是对比 MyISAM 的存储引擎,InnoDB 写的处理效率差一些,并且会占用更多的磁盘空 间以保存数据和索引。InnoDB:所有的表都保存在同一个数据文件中,InnoDB 表的大小只受限于操作系统文件的大小限制。Myisam 只缓存索引,不缓存真实数据;Innodb 不仅缓存索引还要缓存真实数据,对内存要求较高,而且内存大小对性能有决定性的影响。
-
MEMORY 存储引擎使用存在于内存中的内容来创建表。MEMORY 类型的表访问非常的快,因为它的数据是放在内存中的,并且默认使用 HASH 索引,但是 一旦服务关闭,表中的数据就会丢失。主要用于那些内容变化不频繁的代码表或者作为统计操作的中间结果表。
三、MySQL内存结构
MySQL中内存大致分为:全局内存(Global buffer)、线程内存(Thread buffer) 两大部分。
全局内存:缓冲池里面有数据缓存、索引缓存、锁信息、插入缓存等等。此外还有重做日志缓存、额外的内存池。
线程内存:Master Thread、IO Thread、Purage Thread、Page Cleaner Thread。CheckPoint技术:缓冲池的设计目的为了协调CPU速度与磁盘速度的鸿沟。因此跟新或者删除的时候直接操作的是内存的数据,先写入重做日志,然后再修改内存池里面的数据,最后定时刷新到磁盘上。
四、MySQL文件结构
-
参数文件:启动MySQL实例的时候,指定一些初始化参数,比如:缓冲池大小、数据库文件路径、用户名密码等。
my.cnf读取优先级是从左自右的顺序,但是当默认读取路径都有配置文件时,最后读
取的参数的值,会覆盖前面读取的参数的值。/etc/my.cnf /etc/mysql/my.cnf /usr/local/mysql/etc/my.cnf~/.my.cnf
-
日志文件:比如:错误日志、二进制日志、慢查询日志、查询日志等等。
例如:通过show variables like "error_log"来查看错误日志存放内容。
-
socket文件:当用UNIX域套接字方式进行连接的时候需要的文件。
-
pid文件:MySQL实例的进程ID文件。
-
表结构文件:用来存放MySQL表结构定义文件。
.frm后缀命名的文件都是表结构文件,和存储引擎类型无关。所有的表都会生成一个.frm文件;
-
存储引擎文件:存储引擎正在存储了记录和索引等数据。
表空间可以在逻辑上管理多个数据文件,而这些数据文件又可以分布在不同磁盘中这就使得一个表的数据、索引等信息可以被记录在多个磁盘中。
(1)共享表空间:共享表空间文件以.ibdata*来命名; 共享表空间下,innodb所有数据保存在一个单独的表空间里面,而这个表空间可以由很多个文件组成,一个表可以跨多个文件存在,所以其大小限制不再是文件大小的限制,而是其自身的限制。从Innodb的官方文档中可以看到,其表空间的最大限制为64TB,也就是说,Innodb的单表限制基本上也在64TB左右了,当然这个大小是包括这个表的所有索引等其他相关数据。
共享表空间主要存放double write、undo log(undo log没有独立的表空间,需要存放在共享表空间)(2)独立表空间:每个表拥有自己独立的表空间用来存储数据和索引。
(3)查看数据库是否启用独立表空间:
show variables like ‘innodb_file_per_table’;查看,innodb_file_per_table=ON,表示启用了独立表空间;(4)使用独立表空间的优点:
a.如果使用软链接将大表分配到不同的分区上,易于管理数据文件
b.易于监控解决IO资源使用的问题;
c.易于修复和恢复损坏的数据;
d.相互独立的,不会影响其他innodb表;
e.导出导入只针对单个表,而不是整个共享表空间;
f.解决单个文件大小的限制;
g.对于大量的delete操作,更易于回收磁盘空间;
h.碎片较少,易于整理optimize table;
i.易于安全审计;
j.易于备份
如果在innodb表已创建后设置innodb_file_per_table,那么数据将不会迁移到单独的表空间上,而是续集使用之前的共享表空间。只有新创建的表才会分离到自己的表空间文件。(5)共享表空间的数据文件配置:
innodb_data_file_path参数:设置innoDB共享表空间数据文件的名字和大小,例如innodb_data_file_path=ibdata1:12M:autoextend(初始大小12M,不足自增)
innodb_data_home_dir参数:innodb引擎的共享表空间数据文件的存放目录
目前主要是使用独立表空间,但是共享表空间也是需要的,共享表空间主要存放double write、undo log等。五、InnoDB表存储结构
表空间∶表空间可看做是InnoDB存储引擎逻辑结构的最高层。
段 :表空间由各个段组成,常见的段有数据段、索引段、回滚段等。
区 :由64个连续的页组成,每个页大小为16kb,即每个区大小为1MB。页:每页16kb,且不能更改。常见的页类型有∶数据页、Undo页、系统页、事务数据页、插入缓冲位图页、插入缓冲空闲列表页、未压缩的二进制大对象页、压缩的二进制大对象页。
行 :InnoDB存储引擎是面向行的(row-oriented),每页最多允许存放7992行数据。
a.每页=16Kb(页类型:数据页、undo页、系统页、事务数据页、插入缓冲位图页、插入缓冲空闲列表页、未压缩的二进制大对象页、压缩的二进制大对象页)
b.区=64个连续的页=64*16Kb=1MB
制作不易 欢迎大家点赞收藏!!!
-
-
MySQL数据库优化之索引实现原理与用法分析
2020-12-15 01:45:07本文实例讲述了MySQL数据库优化之索引实现原理与用法。分享给大家供大家参考,具体如下: 索引 什么是索引 索引用来快速地寻找那些具有特定值的记录,所有MySQL索引都以B-树的形式保存。如果没有索引,执行查询时... -
MySQL MyISAM默认存储引擎实现原理
2020-09-08 21:56:00主要介绍了MySQL MyISAM默认存储引擎实现原理,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 -
MySQL事务的实现原理
2020-09-14 10:00:19文章目录MySQL事务的实现原理1. redo log 实现持久性2. bin log3. undo log 实现原子性4. MVCC实现隔离性4.1 事务的隔离级别4.2 read view (快照)4.3 隔离级别5. MVCC是如何解决幻读的?6. MySQL InnoDB的锁6.1 共享... -
MySQL事务实现原理.pdf
2021-03-26 16:40:48redo log与undo log介绍 mysql锁技术以及MVCC基础 事务的实现原理 -
MySQL流行中间件比较及实现原理
2017-05-11 10:21:17数据库中间件的一个文档分享 -
【MySQL笔记】正确的理解MySQL的MVCC及实现原理
2019-07-05 15:43:06MVCC实现原理 MVCC相关问题 前提概要 什么是MVCC? MVCC MVCC,全称Multi-Version Concurrency Control,即多版本并发控制。MVCC是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问,在... -
mysql 事务的实现原理
2021-01-27 19:46:13今天想跟大家一起研究下事务内部到底是怎么实现的,在讲解前我想先抛出个问题:事务想要做到什么效果?按我理解,无非是要做到可靠性以及并发处理可靠性:数据库要保证当insert或update操作时抛异常或者数据库crash... -
Mysql账户管理原理与实现方法详解
2020-12-15 01:57:38本文实例讲述了Mysql账户管理原理与实现方法。分享给大家供大家参考,具体如下: 账户管理 在生产环境下操作数据库时,绝对不可以使用root账户连接,而是创建特定的账户,授予这个账户特定的操作权限,然后连接进行... -
mysql索引的实现原理
2019-08-11 19:24:111,mysql索引原理: hash:通过索引的计算定位到数据的存储的位置,进行一次I/O即可,效率非常高,但是是适合用于等值查询,范围查询索引是不起作用。 B+bree:MySQL的b+tree不是b-tree,而是在b-tree上进行了... -
mysql数据库索引实现原理
2019-06-01 21:27:11在介绍索引实现之前,我们先来了解下几种树的数据结构。 二叉搜索树 二叉搜索树有以下性质 1.每个节点有一个关键字 2.左右孩子至多有一个。 3.关键字大于左孩子,小于右孩子。 正因为二叉搜索树的特性,所以...