数学建模社区-数学中国

标题: MySql轻松入门系列————第一站 从源码角度轻松认识mysql整体框架图 [打印本页]

作者: zhangtt123    时间: 2020-6-3 10:36
标题: MySql轻松入门系列————第一站 从源码角度轻松认识mysql整体框架图
一:背景7 J( W' A' E0 _% y" G
1. 讲故事
4 H8 i- g5 |% C/ H最近看各大技术社区,不管是知乎,掘金,博客园,csdn基本上看不到有小伙伴分享sqlserver类的文章,看样子这些年sqlserver没落了,已经后继无人了,再写sqlserver是不可能再写了,这辈子都不会写了,只能靠技术输出mysql维持生活这样子。
' b/ L8 E, S# s# ^
) k+ c) i. ?7 f/ `8 c5 L0 A0 W% O二:了解架构图
; k8 O' U3 y) e3 N4 J9 imysql最大的好处就是开源, 手握百万源码,有什么问题搞不定呢? 这一点要比sqlserver爽多了,不用再dbcc捣来捣去。
- c6 n% ^3 b* o* D% G  P+ c8 }
+ v, y$ s  q# b8 t9 {1. 从架构图入手- ]6 s0 U6 A  S$ a# l
大家都知道做/装修房子都要有一张图纸,其实软件也是一样,只要有了这么一张图纸,大方向就定下来了,再深入到细节也不会乱了方向,然后给大家看一下我自己画的架构图,画的不对请轻拍。
, _, O6 Q* x& I7 C- }  C: T2 t8 N) Q5 U( V* {6 _
0 E6 t* h# w: w8 C3 K& ~1 \8 n

" X/ M" K+ W3 I其实SqlServer,Oracle,MySql架构都大同小异,MySql的鲜明特点就是存储引擎做成了插拔式,这就牛逼了,现行最常用的是InnoDB,这就让我有了一个想法,有一套业务准备用 InMemory 模式跑一下,厉害了~% Y" l" U$ |  ~! a1 i

% J* I; i7 ^6 u) R0 E2. 功能点介绍
$ p& N* J5 `6 `7 C+ x& r# D2 GMySql其实就两大块,一块是MySql Server层,一块就是Storage Engines层。
2 t' a/ E5 y: r6 @* Q' r
1 Z# z# h( q8 [2 @<1> Client7 A0 M+ C( B) A+ z# g! |
不同语言的sdk遵守mysql协议就可以与mysqld进行互通。; g  ?$ n" {5 `3 m5 J/ b- q

" z; I4 R" L) q' ~3 G/ U<2> Connection/Thread Pool7 q) w) Z7 E1 F4 h! N8 y
MySql使用C++编写,Connection是非常宝贵的,在初始化的时候维护一个池。
3 j* D4 T2 z: C( k% l2 o2 \1 W
2 z  R+ r5 t' ]' a- e<3> SqlInterface,Parse,Optimizer,Cache
' L( D/ T! S7 D* u, N3 S+ }1 k对sql处理,解析,优化,缓存等处理和过滤模块,了解了解即可。. f6 J* {9 a9 p5 D6 w% K  Z
8 P' a# t' Q5 D; @. ^
<4> Storage Engines1 n: W" _  ~5 N/ V) I3 F7 Q/ I9 f
负责存储的模块,官方,第三方,甚至是你自己都可以自定义实现这个数据存储,这就把生态做起来了,&#128046;&#128067;。; ~! I& {; h, }
; f5 F) h/ h* ^( t' U; B
三: 源码分析( }+ D, l% F: Z/ d
关于怎么去下载mysql源码,这里就不说了,大家自己去官网捣鼓捣鼓哈,本系列使用经典的 mysql 5.7.14版本。
% V, }: H4 v8 I( w3 J3 B  V$ [( N5 o0 ~" H" e
1. 了解mysql是如何启动监听的3 a# n# \3 A; ~; {# m
手握百万行源码,怎么找入口函数呢??? &#128513;&#128513;&#128513;,其实很简单,在mysqld进程上生成一个dump文件,然后看它的托管堆不就好啦。。。
1 y& [" F" B- k1 y. }" u
* O. J' m" @$ \8 q
6 u! L! U' P7 A
9 M% U+ x6 ^% M; o从图中可以看到,入口函数就是 mysqld!mysqld_main+0x227 中的 mysqld_main, 接下来就可以在源码中全文检索下。
2 N( T! v  }6 f) C) a2 O$ P6 N1 E7 Q. [5 V( F* V
<1> mysqld_main 入口函数 => sql/main.cc7 [+ A7 ~7 @4 r
" I) M# \, j' z5 t5 f9 T1 U: {

* t3 l: d, `5 p* G. [1 vextern int mysqld_main(int argc, char **argv);
+ ?- y" J, m6 x( |; D
- `: b3 g( @# a! h/ Eint main(int argc, char **argv)
- d4 s7 g+ P" H/ `{6 B; Y# r3 n7 U. J; [. K2 X
  return mysqld_main(argc, argv);
4 z* I  c6 ^1 I  U+ C}
) J- M  U( s5 Z" t& k! U2 Z5 I- v
* x) v4 F! s% A5 N% i& u8 C, N
! n2 z0 `, i4 r$ W. a3 O$ B这里大家可以用visualstudio打开C++源码,使用查看定义功能,非常好用。
( O( p' W, H) ]( I7 n& i& T, V5 |% A
<2> 创建监听
; g/ {1 g0 G, A, ~, g1 \( I
! Y4 c3 y6 M- `  C
% S6 u, ?" p3 s8 ]' Hint mysqld_main(int argc, char **argv)7 z- \& C# Z8 G$ l4 ^# J
{
. M, g& i# @6 G* @    //创建服务监听线程
+ i/ Y2 _9 ^' V$ z    handle_connections_sockets();
+ w! ?' ^! l2 i- ^- f}
+ ^# Q( U# z9 A  |4 s9 U" ~- G) U3 @
void handle_connections_sockets()+ P' H+ a  k+ u( Q7 U) W0 h: t
{
% o$ r% a' t1 J$ z/ n     //监听连接
" w1 h5 g% V) ]( q     new_sock= mysql_socket_accept(key_socket_client_connection, sock,
# W3 o# ?: L8 c2 M8 u% z3 F% B+ e5 F                                    (struct sockaddr *)(&cAddr), &length);7 n8 E9 l2 `  s0 }$ _/ e2 t
$ z* o( M! x, b
    if (mysql_socket_getfd(sock) == mysql_socket_getfd(unix_sock))
) I5 J/ f* }! t6 a      thd->security_ctx->set_host((char*) my_localhost);
! H( X5 p+ J- E) a
- d1 k" T4 V7 _$ x" V    //创建连接
7 d: `" f* m9 J- S/ L% ?    create_new_thread(thd);
5 g% X# U1 B. H& ^2 `8 U}, k- N( b! W) M% [

2 J5 P$ u/ q# S5 C5 Z1 G! N! R//创建新线程处理处理用户连接
/ Q/ |' V! ?0 Q( k/ n4 M/ vstatic void create_new_thread(THD *thd){
5 l6 V& e5 D+ l* L
0 ^0 e& S3 {$ f+ N$ r/ p% F   thd->thread_id= thd->variables.pseudo_thread_id= thread_id++;
+ p/ }/ b' Y3 B7 j8 U- u6 L0 {2 l2 M. Q- w7 |
   //线程进了线程调度器' T8 o, G+ D+ A- |( F# S
   MYSQL_CALLBACK(thread_scheduler, add_connection, (thd));   
! Q) i7 e) D$ v% i}% a$ ?+ \# z% W4 _( _& U# m8 N$ F$ s/ X
& y. J+ G+ Z- V5 j5 z$ w
. d2 m: ~& _6 z' f( r4 Q) T" A
至此mysql就开启了一个线程对 3306 端口进行监控,等待客户端请求触发 add_connection 回调。
7 C& R. }4 ?( U# v( L7 E9 @4 N
5 B/ J' D3 g! E# M' v: q  q+ G/ \6 z& R
2. 理解mysql是如何处理sql请求- Z5 ]# {5 Z. `# j# c
这里我以Insert操作为例稍微解剖下处理流程:  U. g$ e7 `4 H
% n! \; O' j; B5 k
当用户有请求sql过来之后,就会触发 thread_scheduler的回调函数add_connection。# G5 s( G0 e  c

! R3 C. B9 q( d; p& H+ n1 _/ l! p. |3 {0 `
static scheduler_functions one_thread_per_connection_scheduler_functions=
5 y% I$ F9 H$ _: I{" S+ W0 R/ g$ I) e8 I
  0,                                     // max_threads
) h& N  y' a# v! j3 H4 W  NULL,                                  // init
1 ?) A* e* K- q9 |+ Z  init_new_connection_handler_thread,    // init_new_connection_thread
9 @9 l4 [, j7 q' @8 M" A  create_thread_to_handle_connection,    // add_connection- n( P0 V5 ^8 c6 _$ _5 R
  NULL,                                  // thd_wait_begin
: S( @9 c' z) c' k2 b' e* ^$ l  NULL,                                  // thd_wait_end9 |! ]9 n) G  {
  NULL,                                  // post_kill_notification9 x: Z4 J& H1 R8 K+ Q' S
  one_thread_per_connection_end,         // end_thread
: }% Y- }9 G2 P  NULL,                                  // end% D: X1 m/ ~( b( ]  X* R
};9 \! x, H+ n$ N+ j0 K0 U& l5 ~

+ Z3 }( e* D* H3 {3 q6 V4 l3 M& @$ e2 r. O) f8 E$ E. K$ @5 v
从 scheduler_functions 中可以看到,add_connection 对应了 create_thread_to_handle_connection,也就是请求来了会触发这个函数,从名字也可以看出,用一个线程处理一个用户连接。% c& a. D! r, n3 c/ K

: X& U# v: t2 `<1> 客户端请求被 create_thread_to_handle_connection 接管及调用栈追踪
# c# b7 p8 x5 Q8 U  f; x# d3 @. M  T! T6 k8 H9 K6 P, y
void create_thread_to_handle_connection(THD *thd)+ x$ ~4 S* k1 X8 A
{
( q" z/ B6 Z: d: T% `; Q8 X     if ((error= mysql_thread_create(key_thread_one_connection, &thd->real_id, &connection_attrib,
+ R+ I6 F% y# Y6 L                                     handle_one_connection,(void*) thd))){}
* p4 u9 O$ O! R0 B: c}% D. N, Y4 e6 @8 f) B+ v% q  i7 p4 P
//触发回调函数  handle_one_connection
8 k6 d! r& Q: g- h! ^2 Dpthread_handler_t handle_one_connection(void *arg)
9 B% D2 S( u5 S4 `1 v  q( r7 p{2 f; _# {3 H7 P. j
     do_handle_one_connection(thd);: R1 e! ?$ ~1 P  C* J* W  h& a
}8 }. X7 l9 k  X5 V3 u* ]# g/ {
//继续处理( z, G7 ~5 d; G9 f5 x
void do_handle_one_connection(THD *thd_arg){
% ~4 l, a& l7 }" T& }7 o    while (thd_is_connection_alive(thd))+ d% r5 c5 }# [6 w7 T
    {
3 q2 [2 R# h5 \( [2 G" V# w      mysql_audit_release(thd);
" X8 t' R# ?4 ]/ y! f0 a. u# `9 n      if (do_command(thd))  break;  //这里的 do_command 继续处理( Y! b* a' Z$ _" Q2 n  ]
    }
7 m% I& H0 W; P# A% g* T3 v}2 m) a9 w5 x: c
//继续分发" N2 H7 A! A) g1 V
bool do_command(THD *thd)7 n5 S1 b1 x/ |9 y3 A
{
/ j8 }0 ^6 D  w6 T    return_value= dispatch_command(command, thd, packet+1, (uint) (packet_length-1));
' P9 ^4 u& `8 M$ B' z}
- [  p0 c, G  Cbool dispatch_command(enum enum_server_command command, THD *thd, char* packet, uint packet_length)
' b+ P1 I0 Q6 Y( Z' j, Y/ h3 i{6 X* B8 o& V9 b$ N6 W7 f
      switch (command) {
, A8 j$ _9 X4 F* T7 {) [4 W5 \         case COM_INIT_DB: ....  break;# u4 a$ x  ]9 V4 r0 b( {0 \
         ...- G1 Q. |% r+ w. f5 u+ ^7 O
         case COM_QUERY:   //查询语句:  insert xxxx
/ a# z; y- u; S9 C             mysql_parse(thd, thd->query(), thd->query_length(), &parser_state);  //sql解析
) R* m( f2 t* U1 n           break;; p2 {: R& ?6 r  U# I8 f$ w' d
      }
) k$ X9 @$ @& G; b! V}
1 z0 b: ~" g  Q  x# K//sql解析模块+ N$ I; H6 N, P4 Y1 ~3 d4 ]& N  O
void mysql_parse(THD *thd, char *rawbuf, uint length, Parser_state *parser_state)
2 a" n5 \$ O+ M2 R9 U& H$ I2 x( T7 o+ x{
7 H/ \; m0 S0 d      error= mysql_execute_command(thd);
4 R1 I' _# u+ n; u) {0 ]}
& l6 ~+ w$ m1 Z$ V- l1 V, V/ u4 E4 k1 F8 ^

8 K9 N, @( g0 p" D- A7 N9 [9 |<2> 到这里它的Parse,Optimizer,Cache都追完了,接下来看sql的CURD类型,继续追。。。
; d( v9 U3 ?$ V0 x. y0 ^; S. t6 [
6 U: o. ?: u7 q' a6 T7 A" _//继续执行+ f2 s, @2 L- I& j0 e  n- f2 O0 {
int mysql_execute_command(THD *thd)
1 F# h- y5 T; }6 l8 d{- ?4 Z! l. _' E+ _- }* }
  switch (lex->sql_command) & `" v" \3 }0 m) U
  {4 h9 O$ |0 I  {" F0 P
      case SQLCOM_SELECT:  res= execute_sqlcom_select(thd, all_tables);  break;
& y2 D" K( g; @, o& W
; ?5 `% g% C4 S      //这个 insert 就是我要追的6 U' f  a" t8 m) }; E) P
      case SQLCOM_INSERT:   res= mysql_insert(thd, all_tables, lex->field_list, lex->many_values,
: H: `4 P& E: c$ U( I9 c                                              lex->update_list, lex->value_list,4 c7 W& E5 t% }7 e& a
                                              lex->duplicates, lex->ignore);, v" h- {' f( e4 y" j+ J9 e& S, @3 W
  }( U% k% v% C6 _  H. R
}0 G  Q4 S/ X! m6 n& E9 Q
//insert插入操作处理
+ D, t. F+ w) X* O2 i9 E) Lbool mysql_insert(THD *thd,TABLE_LIST *table_list,List<Item> &fields, List<List_item> &values_list,
- J: ~4 d0 m; M% B( N$ Y$ y1 B                  List<Item> &update_fields, List<Item> &update_values, " Y4 T1 h! h' ^9 @
                  enum_duplicates duplic, bool ignore)8 X, R5 n9 X/ o  H8 l- \: k7 V
{+ a, w5 F9 a* r" p& S" A
      while ((values= its++))
6 k3 x4 c8 Q9 O      {
8 w  n1 `7 x# x6 m8 a           error= write_record(thd, table, &info, &update);0 ~8 B' ]' @7 k3 ]$ a! ^6 q2 u; H
      }
  M; U' ~8 B4 ^2 F% U}0 a" s2 _( K* A2 B: h+ F
//写入记录
# k0 G& y( s! S3 C. sint write_record(THD *thd, TABLE *table, COPY_INFO *info, COPY_INFO *update)
" Q  A/ _+ R" C6 e1 o{! U& r* D( t7 o* d( J
    if (duplicate_handling == DUP_REPLACE || duplicate_handling == DUP_UPDATE)3 ?4 l' b8 V+ K$ y; c% h6 a  H2 z' q" }
    {
+ B. A3 n  o; y. a! m         // ha_write_row  重点是这个函数7 O: v2 N' ~& g# U# p- d1 g6 P' Y
         while ((error=table->file->ha_write_row(table->record[0])))# ^+ `+ O% J8 N+ |" V$ I
         {2 M2 F" V# u. _
             ....* w8 h6 H: Y7 c' a8 ]
         }" M; D6 ?! ~: t+ @$ b% x
    }
4 Z/ T" r) K0 k+ U+ X! k}5 B. I1 |+ K; k0 b

1 V+ m- b1 ]9 [6 |' B  R" L- q
! ?8 v- f" W) t, \  B6 q
; k% g( n2 b, F+ j/ G0 D可以看到,调用链还是挺深的,追到 ha_write_row 方法基本上算是追到头了,再往下的话就是 MySql Server 给 Storage Engine提供的接口实现了,不信的话继续看呗。。。
2 P/ D/ i( d) v: B9 k( v  y# G
+ s. M7 @3 t" c' L% x% u8 }( T<3> 继续挖 ha_write_row
4 W' u; W7 l3 W: [7 u" q0 b( q' |3 C
: w& i) k1 T* Z2 gint handler::ha_write_row(uchar *buf)( t% H) h! \  r5 A" Z3 E: s
{
9 H. H3 N! ?- _5 G1 H+ }# p    MYSQL_TABLE_IO_WAIT(m_psi, PSI_TABLE_WRITE_ROW, MAX_KEY, 0,{ error= write_row(buf); })! W8 b  u3 ]; b, W2 m
}: r8 z7 M9 C, v

2 H" p5 n0 @/ S+ i//这是一个虚方法
4 d, R& ^2 U$ t! rvirtual int write_row(uchar *buf __attribute__((unused)))
" f& a$ G& U) P{
( B" @+ F: C) }$ `    return HA_ERR_WRONG_COMMAND;
6 c6 N7 y) ]( g; ]+ w) B: j}
3 w8 z7 [9 z( |
* \  ]- r$ M( T9 T' w4 \6 m
& T: n2 ^2 I" R6 q3 k. v! D看到没有,write_row是个虚方法,也就是给底层方法实现的,在这里就是给各大Storage Engines的哈。&#128513;&#128513;&#128513;5 G- `3 P/ ]3 ?" D" m" _

, E. H/ F9 [' K5 P7 L% [, @3. 调用链图
; y: t/ d& h8 x0 R4 o这么多方法,看起来有点懵懵的吧,我来画一张图,帮助大家理解下这个调用堆栈。- H$ W' E: G- y7 p2 P5 U, e; w
( @6 ]! g8 @& S* e$ v2 K. S
4 O1 [2 G$ P& C
, L- E( Y/ O( ?2 ]+ p
三:总结) r* c+ j, J! p8 j1 }  I
大家一定要熟读架构图,有了架构图从源码中找信息就方便多了,总之学习mysql成就感还是满满的。
/ Z- d: j: ?+ a1 u5 w5 x- F————————————————1 c0 A& @8 O# k' L' _1 X
版权声明:本文为CSDN博主「一线码农」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
3 [4 G1 }5 a+ L! a原文链接:https://blog.csdn.net/huangxinchen520/article/details/1064874152 |2 C$ t3 P2 `# n7 L3 Y2 i  W





欢迎光临 数学建模社区-数学中国 (http://www.madio.net/) Powered by Discuz! X2.5