- 在线时间
- 661 小时
- 最后登录
- 2023-8-1
- 注册时间
- 2017-5-2
- 听众数
- 32
- 收听数
- 1
- 能力
- 10 分
- 体力
- 55522 点
- 威望
- 51 点
- 阅读权限
- 255
- 积分
- 17608
- 相册
- 0
- 日志
- 0
- 记录
- 0
- 帖子
- 447
- 主题
- 326
- 精华
- 1
- 分享
- 0
- 好友
- 79
TA的每日心情 | 慵懒 2020-7-12 09:52 |
|---|
签到天数: 116 天 [LV.6]常住居民II 管理员
 群组: 2018教师培训(呼和浩 群组: 2017-05-04 量化投资实 群组: 2017“草原杯”夏令营 群组: 2018美赛冲刺培训 群组: 2017 田老师国赛冲刺课 |
一:背景, q* k% K- K6 L9 Y. ^/ p! C! E9 \' ?9 s! L
1. 讲故事
7 G. |- {0 x( A9 |最近看各大技术社区,不管是知乎,掘金,博客园,csdn基本上看不到有小伙伴分享sqlserver类的文章,看样子这些年sqlserver没落了,已经后继无人了,再写sqlserver是不可能再写了,这辈子都不会写了,只能靠技术输出mysql维持生活这样子。
# P# A; r8 y, K$ I1 k4 m+ f* b/ h8 w0 @! J# {
二:了解架构图
3 x" {7 |( f7 m2 W; @9 \8 ^mysql最大的好处就是开源, 手握百万源码,有什么问题搞不定呢? 这一点要比sqlserver爽多了,不用再dbcc捣来捣去。 E" l+ D5 k+ Z' y7 t$ u
* j9 I& G9 @' _# k1 S: a. p1. 从架构图入手
3 _0 ?& @. w& b/ K2 s' |大家都知道做/装修房子都要有一张图纸,其实软件也是一样,只要有了这么一张图纸,大方向就定下来了,再深入到细节也不会乱了方向,然后给大家看一下我自己画的架构图,画的不对请轻拍。
7 o5 w$ J) N: I: x8 M ; X1 p3 P, X. ]$ x6 ^4 J
* @" B. a4 q# _, ~& V# S) x. m
l% T6 {1 ^, t其实SqlServer,Oracle,MySql架构都大同小异,MySql的鲜明特点就是存储引擎做成了插拔式,这就牛逼了,现行最常用的是InnoDB,这就让我有了一个想法,有一套业务准备用 InMemory 模式跑一下,厉害了~
9 }# c) p: @) u3 e9 Q. i# x# ^0 ]) X- m/ | I/ i7 t, k, h3 }8 }" M
2. 功能点介绍
7 r# q% C" D6 L( H: N2 {# |MySql其实就两大块,一块是MySql Server层,一块就是Storage Engines层。
3 ]$ @. [9 q0 |7 ?- _' B+ o; Q4 H/ ^: o0 d9 j5 c
<1> Client
0 o; N) L5 e% p: g" B, W, }6 H不同语言的sdk遵守mysql协议就可以与mysqld进行互通。' Q. X9 v3 L# K/ l
2 A- E* G) P4 j7 w
<2> Connection/Thread Pool: J+ o; q- H. t
MySql使用C++编写,Connection是非常宝贵的,在初始化的时候维护一个池。& c8 D6 W6 i+ S/ k w
% D, |6 z3 e6 }0 G
<3> SqlInterface,Parse,Optimizer,Cache
j' N5 c4 D& M) E对sql处理,解析,优化,缓存等处理和过滤模块,了解了解即可。6 ^0 I7 I; F, j2 c, O' e Z
2 N% G O5 \ O" A& O) p" x
<4> Storage Engines
* h) e+ C. g+ e) k& V' `* v负责存储的模块,官方,第三方,甚至是你自己都可以自定义实现这个数据存储,这就把生态做起来了,🐮👃。
! d. z7 V! E9 u+ N6 N' d2 {2 X/ q) ?6 R0 ?1 P: h& R
三: 源码分析( a4 V& Q; `% D5 ^5 W
关于怎么去下载mysql源码,这里就不说了,大家自己去官网捣鼓捣鼓哈,本系列使用经典的 mysql 5.7.14版本。
u9 _& f/ W% B4 I' i/ y
. _/ J5 K$ a* L1. 了解mysql是如何启动监听的
- l7 E) z- e* H5 [. E手握百万行源码,怎么找入口函数呢??? 😁😁😁,其实很简单,在mysqld进程上生成一个dump文件,然后看它的托管堆不就好啦。。。
% l$ h. C3 E# {5 ~2 T5 c1 P$ @0 m![]()
0 O! h6 j* ]1 z3 w/ T% z3 {: g8 J7 T
' [- [1 u; ^3 D% b5 s* P
. ]: ^! W+ Z, K2 D4 t, ] E& s& }从图中可以看到,入口函数就是 mysqld!mysqld_main+0x227 中的 mysqld_main, 接下来就可以在源码中全文检索下。
. j/ u4 H2 I) b- t7 q4 F, h' Z: d0 `3 i. U. U) R2 o6 z9 _; W/ Y
<1> mysqld_main 入口函数 => sql/main.cc* {( B( W; L/ ]1 Z# r& p) n
" H$ \& F' X% Y9 z7 z9 c. B7 p. K' X" c5 I
extern int mysqld_main(int argc, char **argv);+ ~/ K9 y4 V5 J% ? p1 n& z+ ?1 O
1 [, Z9 {0 F5 n* U5 b) i! a% r
int main(int argc, char **argv)4 C2 U/ v9 w' }2 I% y* N- H
{
* d( h# ?) d9 G! I; ^5 n4 `4 D return mysqld_main(argc, argv);
& k: ?& H2 m! R; O; d* T7 V}
3 Q3 P. T; J0 \: g& V: D3 T* i( ?, E1 E; s9 k. Q! c; A b! R2 F
5 X- t% ~) I% b7 W这里大家可以用visualstudio打开C++源码,使用查看定义功能,非常好用。
( M. f& V" A R* l. a7 E0 Q+ h+ i' E6 R- Z( W3 Y
<2> 创建监听2 k, k M- ~2 R& ~7 V
5 N* T" h. k7 V/ X" Z
7 `. c/ {: n' m! s4 Uint mysqld_main(int argc, char **argv); b9 L0 b s/ Y, G2 @! v+ T
{
/ c5 u3 m% q( @, s' m2 h# {2 g( n) u //创建服务监听线程$ E+ V. ?& B, i
handle_connections_sockets();
# b7 t" V% s3 |: x0 W3 p}
4 }+ O" Q6 {, o4 x
0 ^7 D3 j7 K# ~$ ]void handle_connections_sockets()
- W4 i5 o/ B7 J! a% {! `{
1 s, a' e: y) m! g //监听连接0 l4 Q I$ q6 r8 }* N n9 S
new_sock= mysql_socket_accept(key_socket_client_connection, sock,
+ I0 H O9 p, | (struct sockaddr *)(&cAddr), &length);
) y. l7 ^& o* X3 ?: j8 y+ u0 o' t. W
if (mysql_socket_getfd(sock) == mysql_socket_getfd(unix_sock))
) ~1 }" f: E) P& ?, a thd->security_ctx->set_host((char*) my_localhost);
3 d3 x2 W0 n7 i4 P6 G2 Y
c1 L2 C! F2 r8 | //创建连接
- m2 O1 L* c% @2 f6 Z create_new_thread(thd);
$ u n% I8 ], h( z}3 w. |9 P4 R( _% o
7 k# ~/ j; K6 k* @) P1 }
//创建新线程处理处理用户连接* E* G: r& s6 z: x; N+ e. K
static void create_new_thread(THD *thd){
; X: f& |& a% \% Y
8 ^$ M2 j* }4 ]% R thd->thread_id= thd->variables.pseudo_thread_id= thread_id++;% f% y* ?6 a- y5 j, z+ [7 n. O4 N
- M5 r3 I; w' k. s4 W //线程进了线程调度器+ _( I+ n: |" X+ z
MYSQL_CALLBACK(thread_scheduler, add_connection, (thd)); 3 t4 U9 }+ p! K1 e: t
}
4 U4 I0 U# C1 O2 Z5 y2 B- J, f
% f( X8 N; Y9 B, f4 o
7 W' y- X9 z$ K' e8 r至此mysql就开启了一个线程对 3306 端口进行监控,等待客户端请求触发 add_connection 回调。
/ O& ~# a5 z4 s' W, M0 Q/ A2 s- O% C6 s( _3 P
; f% E. m2 }( Y1 p$ S( R; T; Y2. 理解mysql是如何处理sql请求
# z$ i0 p6 [ V; \$ J, O这里我以Insert操作为例稍微解剖下处理流程:
$ J( U5 @0 D% k9 {. D; p
1 E Q/ S1 i# l3 V! d当用户有请求sql过来之后,就会触发 thread_scheduler的回调函数add_connection。
0 J3 }$ o8 e' `6 L/ ]8 I0 T2 [8 d" m& T* T# I$ h# N" G2 J
8 v+ p# K7 a, ^; b. U3 P# f
static scheduler_functions one_thread_per_connection_scheduler_functions=
! ?' B) q, C0 [4 g% F$ ^3 p{% L: e- S6 [% J0 ^% ~- D' h$ j
0, // max_threads# ]& J1 a$ I# C3 F* l1 h! G: l
NULL, // init) r1 n; Q5 z& V% @
init_new_connection_handler_thread, // init_new_connection_thread
5 ~0 a1 s6 x( y# X+ W- g create_thread_to_handle_connection, // add_connection
. a$ b- e0 a |% \) [8 v NULL, // thd_wait_begin; L& A" B6 P, W* t
NULL, // thd_wait_end
: Y* Y1 K/ W: J7 e T9 Q2 p% x* D! z NULL, // post_kill_notification
9 U5 Y2 I! A; A2 H! x one_thread_per_connection_end, // end_thread
' p) f* @" ~+ }% u, \2 C NULL, // end9 }; W0 y+ Y7 Z$ g2 M4 L2 G5 d
};4 Z. l9 c4 ]$ b$ v' W% J
& c7 @4 ?$ H' p3 D
+ P9 w: D$ k. o从 scheduler_functions 中可以看到,add_connection 对应了 create_thread_to_handle_connection,也就是请求来了会触发这个函数,从名字也可以看出,用一个线程处理一个用户连接。9 [7 s# m1 }8 k
: w' w" T; o+ i$ @/ ]8 j6 m3 p7 Z; C<1> 客户端请求被 create_thread_to_handle_connection 接管及调用栈追踪, i; [0 X9 e* @% w& q% F$ p
# X$ d3 `9 g# E3 v8 Zvoid create_thread_to_handle_connection(THD *thd). @7 E/ z, w# \4 z6 V" K
{
. Y" v* o* b9 g" _$ ?! a( J3 n2 d( l if ((error= mysql_thread_create(key_thread_one_connection, &thd->real_id, &connection_attrib,
W; ~6 `6 p/ w! ]+ ]: w; L. J handle_one_connection,(void*) thd))){}! H: H/ ?- l& F( G+ g) F
}$ v7 z( ^. h8 T& o# F+ i7 y C
//触发回调函数 handle_one_connection
# U5 t: Q+ d+ g3 h2 B1 O7 L8 a8 Opthread_handler_t handle_one_connection(void *arg)+ }# ~4 j, E$ G% N* _& Z
{
. }+ _+ T& ^' X& m3 u# ` do_handle_one_connection(thd);
/ W Z; L$ ~6 v) A( l4 h% k0 ~}
; N7 Q7 E4 J2 q/ r3 X//继续处理
5 a c- g6 x2 M4 T* d8 b) G. X+ ?( h+ ^void do_handle_one_connection(THD *thd_arg){
& O5 G% i( U8 C+ Q while (thd_is_connection_alive(thd))
/ {4 S+ z B: h# \( u+ U" t {% A. B0 n9 H8 O/ q$ c
mysql_audit_release(thd);
; J; Z# b3 X: `* P6 t if (do_command(thd)) break; //这里的 do_command 继续处理
, `( |: x% g ?9 E/ Y }) q8 E: w3 l4 F) S6 V5 n9 D# m
}+ M& p4 y2 k6 B; M' G
//继续分发' E6 P' ?- V* ?2 _2 ~: g; U" S1 m6 t6 F
bool do_command(THD *thd)
" F# \; D7 Q) ?$ s, D: R{
3 W9 ^6 p4 A2 T% D6 P! x) } return_value= dispatch_command(command, thd, packet+1, (uint) (packet_length-1));
$ F6 r; r1 u* d+ o7 O9 y}
( {) Q- H1 ~, ^* W: cbool dispatch_command(enum enum_server_command command, THD *thd, char* packet, uint packet_length)0 F' {& ^6 H& U" ~* f
{5 y+ i1 M7 M( v) D% U5 s6 L
switch (command) {7 J2 G# ?& I9 }" r4 z0 v; t
case COM_INIT_DB: .... break;: _5 a0 C& q3 `( K0 M# `' r2 E9 E+ i
...8 s, C& U& V4 v+ W* u) i! B
case COM_QUERY: //查询语句: insert xxxx, @' l. \8 m. \5 _6 o% n
mysql_parse(thd, thd->query(), thd->query_length(), &parser_state); //sql解析
. T9 j* f M4 e+ x0 D* F break;
* }* r: i4 L4 G4 u4 w* _$ h; ~7 ^9 j }
9 q ^; ]. w- H G" [}
$ [- h0 O, D/ D8 M+ g) `1 U8 C//sql解析模块
) r) J. M$ g5 v+ Qvoid mysql_parse(THD *thd, char *rawbuf, uint length, Parser_state *parser_state)
- H( A+ ^ j; l0 y# u* U7 u( R% \% [8 F{
9 y. N& o& g: D: B error= mysql_execute_command(thd);
! V( j1 ?+ V' P0 e P: l" B0 {8 q0 c}. ^- W1 L4 i- n# u6 A q
% p5 G9 w) ]$ \/ v* k Q/ J) o: B
7 n* Y: S$ ]1 s2 t' b4 q- i) N<2> 到这里它的Parse,Optimizer,Cache都追完了,接下来看sql的CURD类型,继续追。。。
8 p- Q9 C4 a* a8 b% L5 L, G& n) H# V$ c: X+ D7 F
//继续执行
/ m5 B+ n C5 Jint mysql_execute_command(THD *thd)
- ~4 [- M% q9 O3 `( X3 J{
/ z2 P1 @! m* K! F! h switch (lex->sql_command) - L' R6 x# U' {$ @+ R+ p. s( W
{5 W5 a& t7 k0 F7 Q+ i
case SQLCOM_SELECT: res= execute_sqlcom_select(thd, all_tables); break;
+ b4 g. \- K, w& Y2 h1 n, F$ M# Q
' I& ]* {# u9 `9 L1 O" ] //这个 insert 就是我要追的: G$ P3 J" \" ^
case SQLCOM_INSERT: res= mysql_insert(thd, all_tables, lex->field_list, lex->many_values,
4 W! |; g% _$ ]" l2 J! v1 A J- V lex->update_list, lex->value_list,! O6 t: c# a0 y, ~9 R& T
lex->duplicates, lex->ignore);
0 B/ n( e; B0 Y& l& h% h }' v9 x4 v+ e" J8 x# J) {4 s- @. S
}1 ?2 a$ F$ x1 k0 Q5 J0 ?
//insert插入操作处理5 o* ^7 e; h9 ~2 j* h' b! r _
bool mysql_insert(THD *thd,TABLE_LIST *table_list,List<Item> &fields, List<List_item> &values_list,
/ S% G2 K% t0 B8 r4 U, {7 M List<Item> &update_fields, List<Item> &update_values, # p& {/ e, K/ C/ q* h- @- X& k
enum_duplicates duplic, bool ignore)
+ P* J$ A* c1 W; Z{; ?* N) l) Y$ o9 A8 @4 n
while ((values= its++))
9 x" M4 j+ J/ l$ m$ x7 R6 \ { R+ d6 \7 d/ S
error= write_record(thd, table, &info, &update);3 q2 A( p' @# T, J& z
}! A2 w/ c8 @" X: g
}/ o: L6 r5 t; O' ^# x3 x
//写入记录- g! w0 @, h$ @' s0 d3 i2 t
int write_record(THD *thd, TABLE *table, COPY_INFO *info, COPY_INFO *update)
! U3 C$ m2 r! W( ~2 l- t- c% t{
9 T4 }- j4 y: L9 V- D) R if (duplicate_handling == DUP_REPLACE || duplicate_handling == DUP_UPDATE)( F+ ]% l5 }. {: U+ N6 @$ T$ ]
{1 J8 \* [- U5 M6 \
// ha_write_row 重点是这个函数
7 W3 p& g+ ^# L' l* [0 X9 r while ((error=table->file->ha_write_row(table->record[0])))) B9 b) w4 P E; }/ F" G0 @" i$ V
{8 `% l: y# R0 c, K, r9 Y
....5 ?0 v% P6 k' S C: Z4 K, @
}
# A* ]# u$ Y1 M" d$ j }! F9 {$ e8 N5 m/ {
}
l _% A% f/ C6 @ C$ A: P) \7 i6 J& w/ p X( |: h& I; H
2 m0 M$ @' |9 ~; J7 Z% c
: ]3 ~$ j3 G. j$ o4 m! ?, k
可以看到,调用链还是挺深的,追到 ha_write_row 方法基本上算是追到头了,再往下的话就是 MySql Server 给 Storage Engine提供的接口实现了,不信的话继续看呗。。。
' G6 E9 H9 J/ c: G, |! `5 p8 r6 r) n" @+ }2 t3 w4 d1 O& P
<3> 继续挖 ha_write_row' P4 Y1 {5 {' \: z
4 r* H8 n- I" ~int handler::ha_write_row(uchar *buf)
* Z0 }8 _# Y* I9 b; O0 N( ]{
; V: \) l5 T- x: d7 t MYSQL_TABLE_IO_WAIT(m_psi, PSI_TABLE_WRITE_ROW, MAX_KEY, 0,{ error= write_row(buf); })
7 `6 Y: R8 u: E9 }9 M- Y}( W( Y+ Z ]6 z. S! W8 |
' h9 w6 Z3 k. W, @( ?2 J) H: H- O
//这是一个虚方法
0 S [' |/ N; A: H @( ^virtual int write_row(uchar *buf __attribute__((unused))): \. r/ c& `/ N
{
+ _' q- g' `) a! T ?% F return HA_ERR_WRONG_COMMAND;
% i7 h+ ~' D5 K+ z# B; P}
2 H% u! q5 s8 ?4 x) z# ^+ K; S& t' ~9 c4 y, E# ?, Y) l/ F* S
) y7 p G3 f# G0 [: L. B看到没有,write_row是个虚方法,也就是给底层方法实现的,在这里就是给各大Storage Engines的哈。😁😁😁+ W: ]$ W1 J" \# c/ S$ I! G& |, i! w
6 L/ S0 g: I$ k0 I' x3. 调用链图. i$ b& |7 i1 I8 C
这么多方法,看起来有点懵懵的吧,我来画一张图,帮助大家理解下这个调用堆栈。% M, P: g) \: {! l
% s) `, f# l& L* j
+ t- ]- @# W/ l4 n* s
$ e% P( V* Q0 m8 S/ F三:总结
' @6 }: n3 @. B& e D+ C' m% J大家一定要熟读架构图,有了架构图从源码中找信息就方便多了,总之学习mysql成就感还是满满的。
, P5 Y+ d8 O9 h6 @+ t* i+ K3 ?————————————————
/ g/ U* w3 z1 L9 R! M, |版权声明:本文为CSDN博主「一线码农」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
. ~, v: ^6 L: W* y) D+ h. z! s原文链接:https://blog.csdn.net/huangxinchen520/article/details/106487415
1 c+ ?2 ]$ F0 q3 S, S2 d; i |
zan
|