数学建模社区-数学中国

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

作者: zhangtt123    时间: 2020-6-3 10:36
标题: MySql轻松入门系列————第一站 从源码角度轻松认识mysql整体框架图
一:背景( ?7 e% D9 }. ]- v9 A3 _* C
1. 讲故事8 l6 h, C( C* z, U
最近看各大技术社区,不管是知乎,掘金,博客园,csdn基本上看不到有小伙伴分享sqlserver类的文章,看样子这些年sqlserver没落了,已经后继无人了,再写sqlserver是不可能再写了,这辈子都不会写了,只能靠技术输出mysql维持生活这样子。
4 ^; ?5 J; I4 H3 d% c; `
+ r, l; k3 a+ ?1 k; n二:了解架构图- y5 g8 r5 V" F+ o! A
mysql最大的好处就是开源, 手握百万源码,有什么问题搞不定呢? 这一点要比sqlserver爽多了,不用再dbcc捣来捣去。
+ J$ O" @5 x# M8 a" \  Q/ J% ?! V7 e+ u! U  z& q2 L# a
1. 从架构图入手$ p# b. E$ U3 u+ J2 ]/ K5 S+ i
大家都知道做/装修房子都要有一张图纸,其实软件也是一样,只要有了这么一张图纸,大方向就定下来了,再深入到细节也不会乱了方向,然后给大家看一下我自己画的架构图,画的不对请轻拍。
2 J; L: S0 p/ v1 d9 `8 S8 \+ N* L4 B: O
9 n& P, A+ E- c% W
7 K: o4 L3 k0 t& L6 v1 f9 l/ A
其实SqlServer,Oracle,MySql架构都大同小异,MySql的鲜明特点就是存储引擎做成了插拔式,这就牛逼了,现行最常用的是InnoDB,这就让我有了一个想法,有一套业务准备用 InMemory 模式跑一下,厉害了~
3 l* f7 f1 C/ l0 V8 {
+ G) _; w" `+ f9 O# \0 w) i2. 功能点介绍( I* x3 U5 {+ t+ R; L& m
MySql其实就两大块,一块是MySql Server层,一块就是Storage Engines层。/ P. l2 U" k  t
1 n6 \. i6 w* ]& H# J( o- k; `- I& n
<1> Client& S( Q1 E( y% G. v
不同语言的sdk遵守mysql协议就可以与mysqld进行互通。7 b2 [. T# c  d( x) Z, P; F; w

* u4 T% I1 R+ Z" M& q' v5 b( k, B<2> Connection/Thread Pool) |; q& W5 O) B+ n$ x" M% }' m$ r
MySql使用C++编写,Connection是非常宝贵的,在初始化的时候维护一个池。
9 `8 V- `$ Y" O3 C: T7 u9 u. T' G( a3 w) m( @3 k
<3> SqlInterface,Parse,Optimizer,Cache
; W+ Q; v3 e2 H% z对sql处理,解析,优化,缓存等处理和过滤模块,了解了解即可。8 J1 X+ c, A9 I
3 v6 y- p8 [7 d
<4> Storage Engines" ~# C' @9 D4 ?( }  _3 Q
负责存储的模块,官方,第三方,甚至是你自己都可以自定义实现这个数据存储,这就把生态做起来了,&#128046;&#128067;。
4 O" ?3 X! x1 g; I
: q, y: m) I: |7 `; G: H; S7 I/ H4 U三: 源码分析
+ ^9 ~- E1 Q  `- ~( E7 |' X关于怎么去下载mysql源码,这里就不说了,大家自己去官网捣鼓捣鼓哈,本系列使用经典的 mysql 5.7.14版本。
1 m8 V* g4 u2 a( s7 y+ D' \0 d; b$ t( _: T* {6 e; _7 _' o
1. 了解mysql是如何启动监听的) z3 J0 k1 \+ @2 b3 j
手握百万行源码,怎么找入口函数呢??? &#128513;&#128513;&#128513;,其实很简单,在mysqld进程上生成一个dump文件,然后看它的托管堆不就好啦。。。
( t- x* y7 x7 l8 d. b8 z0 M/ B
  T; p. W/ m8 N3 x4 B+ ~7 `  y1 n& f+ ]% x, D9 Q- J7 Q8 D

0 I1 Z& v# H5 H$ a  s% ~# x从图中可以看到,入口函数就是 mysqld!mysqld_main+0x227 中的 mysqld_main, 接下来就可以在源码中全文检索下。% j/ c# R1 O1 X7 F
. @6 ^1 j5 b, E0 E/ M
<1> mysqld_main 入口函数 => sql/main.cc
6 J4 P4 y+ V5 C& P2 O% \. }6 W& P; _' l1 ]' e4 }# ]
0 a( p" i& i' J( d0 M
extern int mysqld_main(int argc, char **argv);* r9 v8 U) [8 N3 p1 K6 h/ q0 A

, i0 S* C, L; Q  s1 U5 I4 {( Rint main(int argc, char **argv)
; O/ x+ A1 _, h, w! P{
, z/ V( q# e7 f4 f& M1 u2 I  return mysqld_main(argc, argv);
. e  \* \6 w5 Z3 ~+ X& ^) c8 F}
5 \' Y7 p. `3 H) Y( J. N/ y* U) V; }* b' d; a  K

* j! J% p* J: w( ^2 @这里大家可以用visualstudio打开C++源码,使用查看定义功能,非常好用。; P* A4 d: v% H/ X* l
0 |+ ^0 f) e7 Z# d5 m7 [  J9 E
<2> 创建监听
. s' S+ t$ K9 T1 v1 {+ F' A
, j( G" Y) K' o# U, h& P- J; \( V) a+ {5 W0 s& K  T
int mysqld_main(int argc, char **argv)
$ O6 I, e' z% H' t! s. w{: K9 k, d$ R/ m9 u: o- q2 V" l5 B
    //创建服务监听线程
( c3 F* g$ U/ Z' g( w6 V0 B    handle_connections_sockets();% {( A+ X8 _* ~  R' K% t4 E
}
; |$ [/ J1 p4 o! d! \! y0 f9 [
. N; K' |. N3 K  y1 _% L/ T9 m/ jvoid handle_connections_sockets()2 T5 a3 @$ _4 k* v0 _4 K8 h
{2 I1 O" M6 k- A6 u
     //监听连接
! g+ {3 o6 b: I/ F* {# |$ Q+ U     new_sock= mysql_socket_accept(key_socket_client_connection, sock,5 c. }# @$ ]/ Q2 s  U1 _1 E
                                    (struct sockaddr *)(&cAddr), &length);
  V0 B% X1 R+ ]7 T: F$ \3 K* {' [) o' l, e. A! v$ b
    if (mysql_socket_getfd(sock) == mysql_socket_getfd(unix_sock)), f; s3 P3 d( Q$ h+ P+ z  x' i
      thd->security_ctx->set_host((char*) my_localhost);
2 _2 l; b& h' s9 z, c1 Q$ ^$ d0 }+ f( v, }! Z( B
    //创建连接) h0 O: n8 m  @( C" a% s8 |7 P& C! Y
    create_new_thread(thd);6 C7 h0 B' L& _  D' ^( X* G/ l9 E
}
0 D; X$ r& \, N# @; u, T* y) R7 I- s
//创建新线程处理处理用户连接
( R- D% I) t' ?0 c4 _static void create_new_thread(THD *thd){
" ^0 i) w& K" O9 t1 r; C1 j* b  l6 D+ m
   thd->thread_id= thd->variables.pseudo_thread_id= thread_id++;  k0 d  M( ]5 ]( y/ r
* R$ ~" }3 i" }/ C5 w& E6 b1 ~
   //线程进了线程调度器
8 r2 l6 k2 `% I0 g/ I% n( J  s. h   MYSQL_CALLBACK(thread_scheduler, add_connection, (thd));   ( i  w( [  q! s, ~8 W7 w  U: y) G
}  r, m, d7 V" D. b* a
' {4 v3 y, k. [4 ~

; i+ S8 P# X8 p- f  b7 @5 Q至此mysql就开启了一个线程对 3306 端口进行监控,等待客户端请求触发 add_connection 回调。  L0 p! j6 G. Z: ~' f! c) z
# k% [5 a. m. {# Q8 ^2 S7 X
# N" n5 ?' ?- E* V: G
2. 理解mysql是如何处理sql请求
: S5 c8 _& n7 e. Q9 W7 B这里我以Insert操作为例稍微解剖下处理流程:
5 ]2 y5 L8 F5 F1 x- _
5 P" V; U# r/ i+ W/ l) d当用户有请求sql过来之后,就会触发 thread_scheduler的回调函数add_connection。7 ^1 j* B8 ^7 I# T% d: d, n

' X4 N6 m2 q3 N6 p& O: c3 c7 |4 t; @5 J6 ?5 v
static scheduler_functions one_thread_per_connection_scheduler_functions=
. |$ J* ?8 B3 B* J{! g$ ~3 W6 t1 |5 r  B: A
  0,                                     // max_threads3 {  T: v( w* X+ q0 [( [4 ?
  NULL,                                  // init4 q- b4 V& a+ m
  init_new_connection_handler_thread,    // init_new_connection_thread
5 U5 s, e4 v0 e( s4 }; A: g9 ^9 h/ m  create_thread_to_handle_connection,    // add_connection7 ~; j/ S% G5 O" R) c+ W2 w
  NULL,                                  // thd_wait_begin$ U  D: {' u6 x/ t0 u% K
  NULL,                                  // thd_wait_end7 c- |; _. m! @
  NULL,                                  // post_kill_notification. [+ ]$ H: O) C: i: R  _! Q7 z
  one_thread_per_connection_end,         // end_thread. }% X, F8 d$ \1 P6 t7 y$ F
  NULL,                                  // end1 v- z5 |! c) u  D. a3 i+ F" s
};7 q# e% }- L- j  H1 `5 Q% k# d
, X6 D' D8 }/ t  N$ ]' G' p  w

% `3 m. R/ f) v- E从 scheduler_functions 中可以看到,add_connection 对应了 create_thread_to_handle_connection,也就是请求来了会触发这个函数,从名字也可以看出,用一个线程处理一个用户连接。
' E  c/ @* r' p; p9 h& E0 Q2 |$ p; q  m1 f4 ^
<1> 客户端请求被 create_thread_to_handle_connection 接管及调用栈追踪
* Y2 P  d2 ]/ K! T: B! {; a; M$ W4 z- a0 `/ y+ w. D+ W6 [6 }
void create_thread_to_handle_connection(THD *thd)% Q# a! ^) i! j# ^6 \- s
{
3 A; e1 t% e. S+ V  ]# e     if ((error= mysql_thread_create(key_thread_one_connection, &thd->real_id, &connection_attrib,
6 k5 z2 {: |6 F+ W: A+ ^; X                                     handle_one_connection,(void*) thd))){}
' v3 }1 {) M' }" y9 H. g}
+ l  H( j7 M3 h( d" B//触发回调函数  handle_one_connection
! [* l1 h. t( dpthread_handler_t handle_one_connection(void *arg)- n4 N7 i% X  z5 I
{
% d' T- y# t7 q. h7 a9 P* j# d     do_handle_one_connection(thd);
# G$ ]- ]  _6 h9 d}
' v2 j3 y% M1 N//继续处理
& G1 u# W: w; a- g; z1 Mvoid do_handle_one_connection(THD *thd_arg){3 h( f# V: ]' y5 I* _6 i  j1 T. O5 U
    while (thd_is_connection_alive(thd))
! ?3 {  E5 G2 t! h    {
! I( d3 r) L4 G  n      mysql_audit_release(thd);& H# V" `/ `/ N+ x
      if (do_command(thd))  break;  //这里的 do_command 继续处理
% w' ^# `, F' o/ U; j9 `    }
* B, `  l' m$ M9 J}
. e$ ~# A* v( C9 F//继续分发
8 @* I; F0 _0 l; x; V- }( l% Mbool do_command(THD *thd)6 h% T1 M! x0 [* _& ?; w7 y
{! h. a9 L, g' a
    return_value= dispatch_command(command, thd, packet+1, (uint) (packet_length-1));
7 K9 w6 z2 I% K1 p& `# s/ e) W* w}
0 b3 D2 X5 z" i( \* m* [bool dispatch_command(enum enum_server_command command, THD *thd, char* packet, uint packet_length)3 E4 M; Q' r6 R
{' H" ~! v* A1 @5 C
      switch (command) {
. ]9 T$ r, F* z/ v# ]         case COM_INIT_DB: ....  break;
, J' V! c1 x# l/ v- r2 K         ...! r7 K6 @% ^; ]& l3 R2 I1 ?
         case COM_QUERY:   //查询语句:  insert xxxx( W  e  W  c7 u' i/ G
             mysql_parse(thd, thd->query(), thd->query_length(), &parser_state);  //sql解析  `; a7 @4 ^5 O! ~0 H3 P* n- q
           break;; S6 ~2 y0 w  Q$ w3 ]. {
      }6 x( O( ]* g5 {& A; X; S
}
0 p6 L7 B% p9 K$ n//sql解析模块5 D. ~0 I6 R$ P; [! j! J
void mysql_parse(THD *thd, char *rawbuf, uint length, Parser_state *parser_state)" H. m' c( X# q
{
8 Z1 h8 K+ w8 q3 B' d( m0 P      error= mysql_execute_command(thd);9 J; g9 [: L4 O: c8 Y. ^: [3 z. U
}
) a2 B) O" k7 P/ W! m: r% L/ ]7 m; ]

' k5 @: N& V- j, d$ m8 [9 y# {, N<2> 到这里它的Parse,Optimizer,Cache都追完了,接下来看sql的CURD类型,继续追。。。3 c4 G, ?' ?9 x; D+ V" V

) Z6 T& y. G$ ]9 j* s) L* Z7 ~; F5 j; r//继续执行" ?8 _# N# ?3 k2 U7 o5 l
int mysql_execute_command(THD *thd)5 t9 {) |2 z6 x6 `% k+ r' j* X
{" m) y) \; t- g/ L
  switch (lex->sql_command) ( D/ p0 X+ S9 ~/ v1 Y
  {
! C/ h4 }0 w' r6 V( e8 ^  g      case SQLCOM_SELECT:  res= execute_sqlcom_select(thd, all_tables);  break;6 D) Y" N; B. d' f! |% K2 c& X
1 U9 B- j. ^, c
      //这个 insert 就是我要追的3 i7 F2 [3 a8 i6 U
      case SQLCOM_INSERT:   res= mysql_insert(thd, all_tables, lex->field_list, lex->many_values,, \1 O: R" w$ R, }  d  H
                                              lex->update_list, lex->value_list,
  E4 t7 D% C8 ~                                              lex->duplicates, lex->ignore);
/ B3 p, O& {2 U1 ?* }7 @: r9 \/ h  i' s  }
; a$ `6 J8 }, ^& g* `}2 @4 x, m( P0 k- y0 y1 g' W
//insert插入操作处理4 x8 i- Z8 T9 f
bool mysql_insert(THD *thd,TABLE_LIST *table_list,List<Item> &fields, List<List_item> &values_list,7 S- w3 A; K' y1 \1 Q( G
                  List<Item> &update_fields, List<Item> &update_values, 1 p  A: K% F' n# O" ]
                  enum_duplicates duplic, bool ignore)
  [- F3 m" N$ {' U# a8 I( f{
* P# A4 a# O# r      while ((values= its++))
: l7 P4 _: N: Z* ]7 p2 a      {( J0 E. \+ d% v
           error= write_record(thd, table, &info, &update);
: ~: k+ k" U4 t; z- N& {      }* ?- E8 }! T6 l# x8 |" V# q
}/ u& V7 k. O1 [3 E" Z
//写入记录
2 c+ y% f: t/ sint write_record(THD *thd, TABLE *table, COPY_INFO *info, COPY_INFO *update)
; w! |; \( Q) O$ e( R# I! u{
3 q. ?9 ~! b4 m# Z! s    if (duplicate_handling == DUP_REPLACE || duplicate_handling == DUP_UPDATE)- n) C- y! C3 @: D) P$ V3 \7 V
    {2 k: Z- Q8 O# [  `9 Y4 K5 D
         // ha_write_row  重点是这个函数
7 o0 r+ N' s1 _  v6 a         while ((error=table->file->ha_write_row(table->record[0])))4 t$ p' r1 u0 ?, a
         {
# w3 \/ p) {: e2 Q3 U. U! p, g             ....5 b' K. P1 v) U" w  c8 G1 v
         }
$ h$ e1 N8 U  z1 W* t8 u- k    }8 _; Q, R7 L" S  J. ~% U# |/ e
}
8 J( F" \4 A4 \% p9 v  \' n6 u% K
) F0 l% C- n% n* n. A) @. ^4 w9 {: ]( b
( r# ^" J* q# P" v' ~' O
可以看到,调用链还是挺深的,追到 ha_write_row 方法基本上算是追到头了,再往下的话就是 MySql Server 给 Storage Engine提供的接口实现了,不信的话继续看呗。。。
+ s, F2 W( V! W' b/ L( Y
  L1 K4 Y+ L0 h3 E5 h" |+ |<3> 继续挖 ha_write_row( o3 T$ b# C8 a4 Q8 h+ ~

- d! }+ ?( t4 o$ E/ P( k9 B1 Fint handler::ha_write_row(uchar *buf)4 K: W! [0 n8 _) W) I- J
{( F/ R* K$ Q# E) s* w( t0 ~
    MYSQL_TABLE_IO_WAIT(m_psi, PSI_TABLE_WRITE_ROW, MAX_KEY, 0,{ error= write_row(buf); })/ C+ Z( }! g/ A3 s5 N
}
" z: F+ s' U  o3 C% Q. F' ~8 g$ D  h. ?6 X; @( V' \! {- l' D
//这是一个虚方法% Y: e9 K% {+ s; @
virtual int write_row(uchar *buf __attribute__((unused))), Z* V* o+ y7 i+ N) \1 C9 C
{, T2 _  W4 ~( D7 |5 M' F8 K9 R5 k3 k: ^0 R* B
    return HA_ERR_WRONG_COMMAND;: P1 |2 f& J6 k
}
4 ?; ]+ P: _5 {, P0 V% A9 z9 g  v
+ u6 W8 Z# g' }! o3 E- u: @7 P; S
9 W& q( A, Z3 B- g3 F看到没有,write_row是个虚方法,也就是给底层方法实现的,在这里就是给各大Storage Engines的哈。&#128513;&#128513;&#128513;
1 D: r5 f8 p& y& T8 U- C0 }+ U
4 s7 o- C7 R- f3. 调用链图5 R: R. m7 y: X- l2 T
这么多方法,看起来有点懵懵的吧,我来画一张图,帮助大家理解下这个调用堆栈。
: U  |0 K8 E, C; t2 F7 C1 |/ n3 O( y& `/ K0 [
/ _0 }6 _# v. G8 T/ b' [

: Z9 R: R0 q- W三:总结
  p4 ^% H0 E' W& a大家一定要熟读架构图,有了架构图从源码中找信息就方便多了,总之学习mysql成就感还是满满的。
6 \% b, P/ y( y6 c* M————————————————% k  h% ~. U& Z# `' H
版权声明:本文为CSDN博主「一线码农」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。* [& |7 \+ n  W" b! U
原文链接:https://blog.csdn.net/huangxinchen520/article/details/106487415
; T0 X2 k' C, M. M" h




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