大致框架
系统的主要工作流程始于客户端(Client)发起请求。当客户端发送请求时,这个请求首先被送到连接管理器(ConnectionManager)。
连接管理器负责处理连接请求,它会根据当前系统的负载和资源情况,选择创建新的连接线程(ConnectionThread)或重用已有的连接线程。对于首次连接的用户,连接管理器还会进行用户验证(Authenticates user upon first request),确保只有授权的用户才能访问数据库。
一旦用户通过验证,请求将被送到线程管理器(Thread Manager),由它负责启动一个或多个线程来处理这些请求。接下来,用户模块(UserModule)会接管请求处理过程并传递给命令调度器,命令调度器会处理命令、通过查询高速缓存发送给解析器或直接发送给解析器解析查询请求,如果启用了全查询日志记录,命令调度器将在调度前要求日志记录模块将查询或命令记录在纯文本日志中。因此,在全日志记录配置中,所有的查询都将被记录,即使是由于语法错误而绝不会执行并立即返回一个错误的查询也不例外。表管理器打开各个表,取出所需要的锁。这时表操作模块准备继续执行其特定任务,将向抽象存储引擎模块发出大量请求,要求执行各种低层次操作,如插入或更新记录、根据键值检索记录,或执行表层次上的操作,如修复表或更新索引统计。
第三章、核心类
THD 类
1、继承关系上,三个类非常明显:
MDL_context_owner:抽象接口代码,用来实现MDL模块和THD以及服务端代码分离。主要有元数据锁的相关接口,含控制和信息通知等。
Query_arena:看它的元素定义Item * m_item_list,它内部一定维护着一个很大的列表 而Item又继承自Parse_tree_node,所以它内部一定维护着Query语句(存储过程)的相关解析后的元素,也就是抽象语法树的结节。
Open_tables_state:该类保存线程有关已打开和锁定的表的状态,同时维护了表的信息和和锁信息。其提供了压入和弹出两个状态接口函数来操作这两类状态。
核心成员:
LEX *lex //当前查询的分析树描述符。在4.1版及更高版本中为成员的声明
char* query //当前查询的纯文本格式。在4.1版及更高版本中为成员
uint32 query_length //当前查询的长度(单位:字节)。在4.1版及更高版本中为成员的声明
Item* free_list //当前查询的所有分析树节点的链接列表。在执行后清零时使用(sql/sql_parse.cc 中的 free_items())。在4.1 版及更高版本中为成员的声明
MEM_ROOT mem_root //线程内存池。由alloc_root()和free_root()使用。在4.1版及更高版本中为成员的声明
NET net //客户端连接描述符
MEM_ROOT warn_root //用于发布警告或错误的内存池。4.1版新增内容
Protocol*protocol //客户端/服务器通信协议描述符。将根据当前查询是否是预处理语句而指向不同的对象类型。4.1版新增内容
元数据锁
元数据锁:metadata lock,简称MDL,在MySQL 5.5版本引进。元数据锁不用像表锁那样显式的加锁和释放锁,而是在访问表时被自动加上,以保证读写的正确性。加锁和释放锁规则如下:
MDL读锁之间不互斥,也就是说,允许多个线程同时对加了 MDL读锁的表进行CRUD(增删改查)操作;
MDL写锁,它和读锁、写锁都是互斥的,目的是用来保证变更表结构操作的安全性。也就是说,当对表结构进行变更时,会被默认加 MDL写锁,因此,如果有两个线程要同时给一个表加字段,其中一个要等另一个执行完才能开始执行。
MDL读写锁是在事务commit之后才会被释放;
NET,TABLE,FIELD类
NET:定义网络连接描述符
TABLE:数据库表描述符
FIELD:域描述符(常用于分析器,优化器) 基本抽象类 >> 特定域类型:整型,字符串型,时间戳等
API调用
内存分配
文件管理
字符串操作
客户端 / 服务端通信
非压缩包:包头,包长度(3字节),序列号(1字节)
压缩包:有额外三字节的长度域
my_net_write() 将包放入缓冲区
net_flush() 立即将缓冲区数据发送至客户端
验证握手
服务器检查客户端主机是否能与之连接
可以:带4字节包头问候包
不可以:错误消息包
客户端:认证包响应
服务端:OK响应包
握手结束–>验证安全性
客户端命令包发送命令至服务端
服务器响应并执行命令,发送一个或多个响应包(OK包,错误包,EOF包,结果集包)
EOF包可传送大量消息
第六章、线程处理请求
线程创建后:描述符放在全局线程列表(I_List<THD> thread
)
列表作用:
- 为SHOW PROCESSLIST命令提供数据。
- 在执行KILL命令时找到目标线程。
- 在停机时发出信号让所有的线程终止。
所有与线程创建、终止或追踪有关的操作都由互斥体LOCK_thread_count保护。有三种POSIX线程条件变量与线程同时使用。COND_thread_count在停机过程中帮助实现同步化,确保在主线程终止前所有的线程都结束工作并退出。当主线程决定唤醒一个高速缓存线程并派它处理当前客户端会话时,COND_thread_cache是广播。高速缓存线程使用 COND_flush_thread_cache发出信号,表示它们要在停机期间或在处理SIGHUP 时退出。
高速缓存中若有线程睡眠则用缓存中的,否则创建新线程
经过检查与初始化后,
handle_one_connection():不退出则执行命令,退出则启用end_thread(),在此之后线程可能会被缓存或者直接终止。
互斥体
MySQL对全局变量进行某种平衡分组,以及为每个组使用一个互斥体。
形如LOCK_XXX,THR_XXX
读写锁
第一次获得锁概率很高的情况下并不适合用读写锁,因为读写锁会有额外的CPU开销;
但是当第一次失败率很高时,就算加上读写锁多出来的CPU开销,也比互斥锁等待的时间少,这个时候读写锁更适合
同步
使用条件变量pthread_cond_wait()
,pthread_cond_signal()
,pthread_cond_broadcast()
抢占
设置抢占标志位通知被抢占的线程,若正在执行锁定I/O,启用线程报警,thr_alarm()
第七章、存储引擎接口
实际存储和检索数据的代码
表处理器:存储引擎与Mysql优化器之间的接口
Handler类:提供一些可实现基本功能的方法,如打开关闭表,连续扫描记录,按照键值存储、删除记录
// handler实例相关表描述符
struct st_table *table
Handlerton类:包含大多数回调函数指针的c结构
具有处理保存点,commit,rollback等函数
1. 职责范围
handlerton类:主要负责定义存储引擎与MySQL服务器之间的全局性交互接口。它包含了存储引擎的基本信息(如名称、类型、标志等)以及一系列用于管理存储引擎的函数指针。这些函数指针指向了存储引擎实现的特定函数,如初始化、关闭、创建表、删除表等。handlerton类可以视为存储引擎的“句柄”或“接口”,它提供了MySQL服务器与存储引擎之间交互的桥梁。
handler类:则更具体地定义了存储引擎如何操作表中的行和索引。它是存储引擎与MySQL服务器之间关于数据操作的接口。每个打开的表都会关联一个handler类的实例,该实例封装了所有与表相关的操作,如读取、写入、更新、删除行,以及管理索引等。handler类的方法通常与表的特定操作相关,是表级别的行为接口。
2. 实例化与生命周期
handlerton类:通常一个存储引擎只会实例化一个handlerton类的实例,这个实例在MySQL服务器启动时创建,并在服务器关闭时销毁。它代表了存储引擎的全局状态和行为。
handler类:对于每个打开的表,MySQL服务器都会创建一个handler类的实例。这个实例的生命周期与表的打开和关闭操作相关。当表被打开时,handler实例被创建;当表被关闭时,handler实例被销毁。因此,handler实例的数量可能随着表的操作而动态变化。
3. 功能与操作
handlerton类:提供了诸如初始化存储引擎、关闭存储引擎、创建和删除表等全局性操作。它还支持事务处理(如果存储引擎支持)的接口,如提交和回滚事务。
handler类:提供了对表中数据的具体操作,如读取、写入、更新和删除行。它还支持索引的创建、删除和查找等操作。handler类的方法通常与表的CRUD(创建、读取、更新、删除)操作紧密相关。
4. 抽象层次
handlerton类:处于较高的抽象层次,它定义了存储引擎与MySQL服务器之间的交互框架和全局性接口。
handler类:则处于较低的抽象层次,它具体实现了对表中数据的操作,是表级别的行为接口。
第八章、并发访问与锁定
表锁、行锁、页锁
- 表锁:漏洞少,性能低(MyISAM,MEMORY,InnoDB)
- 行锁:漏洞多,性能高(InnoDB)
- 页锁:折中方法(Berkeley)
MySQL采用可并发插入
表锁管理器
include/thr_lock.h
enum thr_lock_type
- TL_IGNORE
- TL_UNLOCK 释放该锁
- TL_READ 读锁
…
表锁:读锁与写锁
四个队列: - 当前读
lock->read
- 暂停读
lock->read_wait
- 当前写
- 暂停写
读锁请求:
无写锁且无暂停写锁,就得到读锁
写锁高于读锁,暂停写锁队列中的锁也高于读锁
第九章、解析器与优化器
解析器
句法扫描
5.1版的新版本中有些变化。许多内置函数都从sql_functions[]
数组移到了native_functions_hash
中。现在内置函数由语法规则模块进行查找,而不是由句法扫描器进行。
句法扫描器的入口点是sql/sql_lex.cc
中的yylex()
。函数的名称具有特殊的意义:需要与语法规则模块发生器GNU Bison兼容,它希望通过调用一个使用该名称的函数来取得令牌。
语法规则
入口点为:yyparse()
解析树由一个LEX类型的对象代表,这是sql/sql_lex.h
中的结构st_lex
的typedef。LEX成员众多。我们将重点介绍两个成员:enum-sql-command sql-command
和SELECT_LEX select_lex
sql_command
显示我们正在执行的SQL查询的类型,是选择、更新、插入、删除,还是其他查询类型。
select_lex
属于SELECT_LEX类,这些成员包含有关各种查询特点的信息,如WHERE从句;表列表;域列表;有关优化器提示的信息;对其他子查询的SELECT-LEX实例的交叉引用;ORDER BY,GROUP BY和HAVING表达式;以及许多其他细节。
重要:Item
类
Item具有多种名称以val-开头的方法。名称的其他部分取决于返回值的类型。例如,如果返回值为整数,则方法名称为val-int(),优化器以后使用包含在LEX-SELECT的where成员中的Item来构建一个过滤器表达式,用于它所检查的记录组合。过滤器表达式通过调用Item::val-int()
进行评估。如果返回1,则认为记录满足限制条件,i然后包含在结果集中,否则放弃记录。
SELECT count (*) FROM customer WHERE Iname='Jones' AND age BETWEEN 25 AND 30
优化器主要功能:
- 确定使用哪个键从表中获取记录,并选择最适合该表的键。
- 针对每个表确定扫描表是否比读取键更好,如果有很多记录与键值匹配,则键的优点下降,而表扫描速度更快。
- 当查询中出现一个以上的表时,确定表的连接顺序。
- 重写WHERE从句,以删除死代码,减少不必要的计算,尽可能更改限制条件,以便为键使用开辟道路。
- 从连接中删除未使用的表。
- 确定是否能将键用于ORDER BY和GROUP BY
- 试着使用一个内部连接替换一个外部连接。
- 试着简化子查询,并决定其结果的高速缓存程度。
- 合并视图(扩大试图引用,使其成为一个宏)。
给定连接顺序,找到每个表最佳访问路径;再确定最佳链接顺序(best_access_path;find_best ()/greedy_search())
使用explain
:显示其查询方案
对explain
输出的解释:
- id: SELECT 标识符。如果你的查询包含子查询,MySQL 会为每个查询分配一个唯一的标识符。
- select_type: SELECT 的类型,例如 SIMPLE(简单的 SELECT,不使用 UNION 或子查询等),PRIMARY(查询中若包含任何复杂的子部分,最外层的 SELECT 被标记为 PRIMARY),UNION(UNION 中的第二个或后续的 SELECT 语句),SUBQUERY(子查询中的第一个 SELECT),等等。
- table: 输出行所引用的表。
- partitions: 匹配的分区。
- type: 访问类型,这是显示连接类型的一个非常重要的指标,它告诉你 MySQL 是如何找到所需行的。例如,ALL(全表扫描),index(索引全扫描),range(索引范围扫描),ref(使用非唯一性索引或唯一性索引的前缀来检索单条记录),eq_ref(对于每个来自于前面的表的行组合,从该表中读取一行。这可能是最好的联接类型,除了 const 类型),等等。
- possible_keys: 显示可能应用在这张表上的索引。注意,这仅仅是一个可能的索引列表,并非实际使用的索引。
- key: 实际使用的索引。如果为 NULL,则没有使用索引。
- key_len: 使用的索引的长度。在某些情况下,不是索引的全部部分都会被使用。
- ref: 显示索引的哪一列或常数被用于查找值。
- rows: MySQL 认为必须检查的用来返回请求数据的行数估计值。
- filtered: 表示返回结果的行占开始找到符合表条件的行的百分比。
- Extra: 包含不适合在其他列中显示但十分重要的额外信息。例如,是否使用了文件排序(Using filesort),是否使用了临时表(Using temporary),是否进行了全表扫描(Using where; Using index)等。
范围优化器
优化具有限制条件(将键数值限制在特定范围内)的查询。有一个模块专门用于这个目的,被称为范围优化器。范围优化器的源代码可以在sql/opt_range.h和sql/opt_range.cc中查找,入口点在sQL_sELECT::test_quick_select()中。
- range
- index_merge 一个以上的键有范围时
- range_desc 递减顺序的range
- fulletext 纯文本键
核心优化器类
JOIN
描述SELECT查询方案的查询键类
prepare() 初始化
optimize() 确定查询方案
exec() 实际的查询
reinit() 准备用于另一次exec()调用
cleanup() 优化和执行期间释放已分配资源
JOIN_TAB
:优化相关的信息,关系到每个参加连接的表实例
select_result
:处理select查询的输出结果
解析树
第十章、存储引擎
.frm
文件包含了表定义(列名称、大小以及键等)
MyISAM
元信息的表记录的级联
除了.frm
文件还有数据文件(.MYD
)和索引文件(.MYI
)
数据文件
固定长度和可变长度
固定:分为带BIT类型的域与不带BIT类型的域
索引文件
记录头包含下列区间类型:state、base,keydef和 recinfo。State和base区间仅出现一次,keydef区间为每个键出现一次,recinfo区间为每个键的每个域出现一次。注意,表中的每个记录都以一个特殊的域开始,用于标记被删除的记录和NULL域,这个附加的域也会有自己的recinfo区间。
均在storage/myisam/mi_open
中
- state:键,数据文件长度,时间戳,打开表的次数,删除和实际的记录数目等等(mi_state_info_write ())
- base:记录数,域的数目,各种限值等等(mi_base_info_write())
- keydef:键部件数目,键算法类型(B树orR树)(mi_keydef_write())
- recinfo:域类型代码,域长度(mi_recinfo_write())
- recinfo后为键块,即B树或R树的树叶(有2字节记录头:第一位指出是否为叶节点,其余位为已使用部分大小)
键类型
B树键
纯文本建(fulltext索引,FULLTEXT INDEX)
空间键(R树,键值需为n维空间的一个地理对象,SPATIAL INDEX),例如在需要经纬度时使用
InnoDB
InnoDB使用表空间存储数据
簇索引->将主键作为键值的B树,InnoDB表必须有一个主键
MyISAM缓冲键页,InnoDB缓冲键和数据
日志
undo
撤销日志:回滚事务redo
重做日志:存储崩溃恢复使用的信息
数据行
新格式:记录开始处是记录中的域数据偏移量列表,接下来的4个为用于标记记录已经删除和其他目的;4个位用于显示本记录所拥有的记录的数目;13个位用于记录的堆数目,3个位中包含记录类型,后面还有2字节指向后面键的指针
附加内部域:每个InnoDB数据行都有附加的内部域,其中存储要在事务、恢复和多版本中使用的信息。一个域长6字节,包含最后一次修改记录的事务的ID。同时包含7个字节的域,即回滚指针。回滚指针指向撤销日志中的回滚段中的记录。该指针可以用于回滚一个事务,如果当前事务隔离级别要求,则用于显示旧版本的数据。
Memory(Heap)
将数据存储在内存中。该代码的最初目的是,在执行能在一次通过中完成的SELECT时,让优化器能够创建和使用临时表
支持散列和B树键
MyISAM Merge
允许MySQL DBA或开发人员将一系列等同的MyISAM表以逻辑方式组合在一起,并作为1个对象引用它们。对于诸如数据仓储等VLDB环境十分适合。
NDB(集群)
在NDB Cluster 中,节点意味着一个 进程。可以在一台计算机上运行多个节点;对于运行一个或多个集群节点的计算机,我们使用术语“集群主机”。
集群节点共有三种类型,在最小的 NDB Cluster 配置中,至少有三个节点,每种类型之一:
管理节点:此类节点的作用是管理NDB Cluster内的其他节点,执行提供配置数据、启动和停止节点以及运行备份等功能。由于此节点类型管理其他节点的配置,因此应先启动此类型的节点,然后再启动任何其他节点。管理节点使用命令ndb_mgmd启动。
数据节点:此类节点存储集群数据。数据节点的数量与片段副本的数量乘以片段的数量相同。例如,对于两个片段副本,每个副本有两个片段,需要四个数据节点。一个分片副本足以存储数据,但不提供冗余;因此,建议有两个(或更多)片段副本以提供冗余,从而提供高可用性。数据节点使用命令ndbd 或 ndbmtd启动。
NDB Cluster 表通常完全存储在内存中而不是磁盘上(这就是我们将 NDB Cluster 称为 内存数据库的原因)。但是,一些 NDB Cluster 数据可以存储在磁盘上
SQL节点:这是访问集群数据的节点。在NDB集群中,SQL节点是使用NDBCLUSTER存储引擎的传统MySQL服务器。SQL节点是由–ndbcluster和–ndb-connectstring选项启动的mysqld进程
SQL 节点实际上只是一种特殊类型的 API 节点,它指定访问 NDB Cluster 数据的任何应用程序
Archive(可学习,较简单)
压缩与归档数据。
以压缩格式存储记录,仅支持select和insert,不用担心更新与删除
Fedarated
访问存储在远程Mysql服务器上的表
第十一章、事务
实现处理器子类
handler: :has_transactions ():向上层SQL层报告存储引擎是否支持事务
handler::start_stmt ()和handler::external_lock () :启动事务
事务存储引擎通常需要使用一个数据结构来追踪当前事务的状态。MySQL存储引擎架构通过为指向 THD 类事务描述符的指针分配内存来满足这种需要。
innobase_register_trx_and_stmt () 调用-> innobase_register_stmt () 调用-> trans_register_ha ()
ha_innobase(the InnoDB handler)有一个键数据成员在桥接格式的操作(以及在InnoDB handler中执行的其他操作)中起重大作用,这个成员是innobase_prebuilt,类型为struct row_prebuilt_struct*。这是一个指向一个结构的指针,该结构以某种方式组织InnoDB表数据,以便最有效地使用MySQL上层SQL层格式完成操作。
它通过调用 row_create_prebuilt()在 ha_innodb::open()中初始化。这个结构中有一个成员值得多加注意:trx,类型为stuct trx_struct*。
trx是一个指向事务描述符的指针,所包含的数据如:事务ID,事务隔离级别,事务是否创建或移除一个表或一个索引、最后一次提交的事务日志序号,日志名称和对应于事务的二进制复制日志中的偏移量,以及各种其他标志、计数器和与处理事务有关的描述符指针。InnoDB将trx指针放在THD下的内存插槽中,该插槽是为主事务描述符提供的(thd->ha_data[innobase_hton.slot]),上图所示。
定义handerlerton
Handlerton 实质上是一个连接到表处理器(table handler)的 singleton,并由此得名。
特定事务回调函数:
savepoint_set ()
savepoint_rollback ()
savepoint_release ()
commit ()
rollback ()
prepare ()
recover ()
commit_by_xid ()
rollback_by_xid ()
close_connection ()
panic ()
flush_logs ()
start_consistent_snapshot () ==> innobase_start_trx_and_assign_read_view ()
binlog_func ()
release_latches ()
InnoDB中函数基本会加上innobase_to前缀
查询高速缓存
handler::register_query_cache_table()
InnoDB使用innobase_query_caching_of_table_permitted()
执行回调函数。它允许进行相当复杂的分析,以便作出决策。必须小心避免死锁。函数处理所有的事情,如果查询所涉及的给定表可以被安全地高速缓存,则返回 TRUE,否则返回FALSE
复制二进制日志
- 为了在系统崩溃时保证二进制日志与表数据的一致,存储引擎必须实现XA事务。
- 在基于语句的复制中,从服务器在一个线程中顺序执行二进制日志更新。因此,主服务器上的所有更新都必须在SERIALIZABLE事务隔离等级上进行,以保证从服务器上具有相同的结果。
扩展:
XA 事务支持不同数据库之间实现分布式事务。
这里的不同数据库,可以是不同的 MySQL 实例,也可以是不同的数据库类型,比如 ,MySQL 数据库和 Oracle 数据库。
XA 事务本质上是一种 基于两阶段提交的分布式事务 ,分布式事务可以简单理解为多个数据库事务共同完成一个原子性的事务操作。参与操作的多个事务要么全部提交成功,要么全部提交失败。
在使用 XA 分布式事务时,InnoDB 存储引擎的事务隔离级别需要设置为串行化 。
XA 事务由 一个事务管理器(Transaction Manager)、一个或者多个资源管理器(Resource Manager) 和 一个应用程序(Application Program) 组成。
死锁
InnoDB死锁检测算法,检测到即回滚,若不是InnoDB的问题,则使用超时死锁检测,由innodb_lock_wait_timeout控制
第十二章、复制
从服务器连接到主服务器上,并开始执行更新,同时从主服务器的二进制日志中读取更新。从服务器上有两种线程完成这项工作:I/O线程和SQL线程。I/O线程下载主服务器二进制日志中的内容,然后将它们存储在被称为中继日志的本地临时文件中。中继日志由SQL线程处理,该线程重新创建最初的执行环境并执行更新。
使用FLUSH TABLES WITH READ LOCK,SHOW MASTER STATUS
和SELECT MASTER_POS_WAIT()
组合查询,可以在程序上实现主服务器和从服务器同步。
附录
初始化模块
sql/mysqld.cc
init_common_variables()
init_thread_environment()
init_server_components()
//sql/sql_acl.cc 中的 grant_init()
//sql/slave.cc 中的 init_slave()
get_options()
连接管理器
sql/mysqld.cc
handle_connections_sockets ()
线程管理器
sql/mysqld.cc
create_new_thread()
start_cached_thread ()
连接线程
sql/mysqld.cc
// 第六章 线程处理请求
handle_one_connection()
用户验证
sql/sql_parse.cc中check_connection()
,sql/password.cc
解析器
sql/gen_lex_hash.cc
sql/lex.h
sql/lex_symbol.h
sql/lex_hash.h(生成的文件)
sql/sql_lex.h
sql/sql_lex.cc
sql/下以item_开头并以.h或.cc为扩展名的一组文件
查询高速缓存
sql/sql_cache.cc
Query_cache: :store_query ()
Query_cache: :send_result_to_client ()
表修改
sql/sql_update.cc
,sql/sql_insert.cc
,sql/sql_delete.cc
,sql/sql_table.cc
存储引擎
sql/ha_myisam.h和sql/ha_myisam.cc
sql/ha_innodb.h和sql/ha_innodb.cc
sql/ha_heap.h 和 sql/ha_heap.cc
sql/ha_ndbcluster.h 和 sql/ha_ndbcluster.cc
myisam/
innobase/
heap/
ndb/