在线时间 661 小时 最后登录 2023-8-1 注册时间 2017-5-2 听众数 32 收听数 1 能力 10 分 体力 55510 点 威望 51 点 阅读权限 255 积分 17604 相册 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 田老师国赛冲刺课
一:背景" D. I. c( X) D$ r- A, a' i: b+ A
1. 讲故事
+ Z- ?! I; Q1 \ 最近看各大技术社区,不管是知乎,掘金,博客园,csdn基本上看不到有小伙伴分享sqlserver类的文章,看样子这些年sqlserver没落了,已经后继无人了,再写sqlserver是不可能再写了,这辈子都不会写了,只能靠技术输出mysql维持生活这样子。
) J: W A) b ?( }0 X! _- Y" U
. `3 s% P6 l; T1 ~; e) X2 Q 二:了解架构图/ g/ o. o, Y) C/ [$ e# E
mysql最大的好处就是开源, 手握百万源码,有什么问题搞不定呢? 这一点要比sqlserver爽多了,不用再dbcc捣来捣去。: I$ d6 \% J! _
5 ?8 h/ u) d5 O/ Z8 a% k 1. 从架构图入手( n$ \5 |6 E0 D8 j% _8 V
大家都知道做/装修房子都要有一张图纸,其实软件也是一样,只要有了这么一张图纸,大方向就定下来了,再深入到细节也不会乱了方向,然后给大家看一下我自己画的架构图,画的不对请轻拍。
/ O# S$ M( ^7 e- J
$ s/ C6 a" f9 t% o. r% o7 K 5 R, }$ ` k& L) ~" Y
8 u# Y7 Z% z$ ~$ m* o
其实SqlServer,Oracle,MySql架构都大同小异,MySql的鲜明特点就是存储引擎做成了插拔式,这就牛逼了,现行最常用的是InnoDB,这就让我有了一个想法,有一套业务准备用 InMemory 模式跑一下,厉害了~
. _( f3 W1 ?# [1 n4 ^4 F1 g" m % x8 {! V! d( Z: D
2. 功能点介绍5 m% [2 E! d" c D5 k$ e" N' a% L
MySql其实就两大块,一块是MySql Server层,一块就是Storage Engines层。% y% O6 @( K) X) `7 H
. ?8 {! b& E6 g% g& W- u+ E <1> Client
$ E3 I" l* h$ v9 h& O7 P; P+ K 不同语言的sdk遵守mysql协议就可以与mysqld进行互通。! A4 ~1 {2 R, \& W/ [
+ W' d" ^4 G2 X5 s
<2> Connection/Thread Pool( [/ @: p# e& r4 Y, \, B; z
MySql使用C++编写,Connection是非常宝贵的,在初始化的时候维护一个池。7 X: o+ Z1 s" a- P& m$ w0 V
) F9 u- I( _/ Z1 R0 m+ ~( {% s <3> SqlInterface,Parse,Optimizer,Cache! A6 I$ E5 q% P) V
对sql处理,解析,优化,缓存等处理和过滤模块,了解了解即可。3 d% c; U6 O, d+ Y' w
4 L3 }3 X! `1 l2 `
<4> Storage Engines2 e! V2 v P) y7 r6 ]8 I
负责存储的模块,官方,第三方,甚至是你自己都可以自定义实现这个数据存储,这就把生态做起来了,🐮👃。; c1 r- `+ j: e ^
& S. w5 y* g9 n4 e" k! d) z% A& Q 三: 源码分析
+ S* A; k/ T% h# o) @ 关于怎么去下载mysql源码,这里就不说了,大家自己去官网捣鼓捣鼓哈,本系列使用经典的 mysql 5.7.14版本。 E6 Z% @6 ~, E5 j. c
. H" k2 I9 N( U2 z+ D0 i8 H+ c3 D9 ~ 1. 了解mysql是如何启动监听的
5 b" T' j3 f7 x8 n; t( B 手握百万行源码,怎么找入口函数呢??? 😁😁😁,其实很简单,在mysqld进程上生成一个dump文件,然后看它的托管堆不就好啦。。。
2 @) k( C4 ` v _ 4 B: E( G2 m6 D# N* B& c0 w( A1 \, N" F
0 c( A3 v6 Y4 P; x" I 8 H% M0 {: [5 h7 o0 _4 q
从图中可以看到,入口函数就是 mysqld!mysqld_main+0x227 中的 mysqld_main, 接下来就可以在源码中全文检索下。
* L M+ @8 n9 Y* K e _
6 }+ x, q2 F) W% X! A( H+ Z <1> mysqld_main 入口函数 => sql/main.cc/ W; m, X; M0 w: x8 d
8 K" `2 H7 J0 o9 K6 J
' g' S1 b# P. n5 G* u extern int mysqld_main(int argc, char **argv);
) Z6 c" O; a, i
! V# y7 W( M3 ~$ `( W' s" E* {1 P int main(int argc, char **argv)& z' _: K* m8 @' F# b6 i
{
" r3 q4 x# s; [" a4 h* G2 ] return mysqld_main(argc, argv);
- P( k( F0 H+ S# j9 p }% Y6 g2 m0 [8 Z0 ?+ O' [
2 h+ _& t; R& f" l$ ?
# d \# e& Q W, M* B 这里大家可以用visualstudio打开C++源码,使用查看定义功能,非常好用。
4 f1 b- r) b; S) Z' Y+ B \
7 f) t: g- b& ]( G: s <2> 创建监听3 e# _# p$ V9 H, r' M) a
h$ g$ S s7 [
& t) U0 U9 k) G6 e3 Q2 q, l `& P( K& W int mysqld_main(int argc, char **argv)
0 s- I% E3 B1 Q1 | { ^) O2 c( V. J D6 R8 q$ r7 n, L
//创建服务监听线程8 \, T+ X b# b) N, i# ]! @' v
handle_connections_sockets();
' o2 Y% g0 s, A- R ^5 Y3 a- z" d }" V1 J6 t2 H' |2 w; L2 f! M
- b0 }( |% E# n void handle_connections_sockets()7 b8 j0 N2 P J/ l b
{/ c4 K# _, T% N) U2 v4 h
//监听连接
7 S) f( e4 N* B: |3 d! Q2 y p new_sock= mysql_socket_accept(key_socket_client_connection, sock,, h+ E' S- v! H3 ?
(struct sockaddr *)(&cAddr), &length);
2 \0 h: ~) _ k) p, A" Q( v$ }
8 C. E- t! K6 q Z3 ^3 [1 n! I if (mysql_socket_getfd(sock) == mysql_socket_getfd(unix_sock))5 j5 ^4 L/ A9 ^/ t4 W6 M" ^' x
thd->security_ctx->set_host((char*) my_localhost);
9 b& {) n3 b+ V B 0 F k- U* {5 G
//创建连接
1 D5 @+ c9 \* V6 T( z create_new_thread(thd);
$ D9 G2 _' A8 A* d) _3 V; S }
9 E* |1 q, v$ A2 }$ R5 s
- r5 C6 N7 B1 s3 h/ v$ I/ s* k+ Q //创建新线程处理处理用户连接
( a; @4 P& B2 c) ` W4 m1 z% P static void create_new_thread(THD *thd){
: K3 {0 o$ E. S 6 u( F. Q9 ?$ E/ M
thd->thread_id= thd->variables.pseudo_thread_id= thread_id++;
( I. v6 I4 Z% N6 T4 g
1 M& Y( B. U7 J7 E* ^: M; o& u //线程进了线程调度器: E: T8 f( X+ e5 t" u
MYSQL_CALLBACK(thread_scheduler, add_connection, (thd));
( w1 U; ]( w7 c+ _& x' v( | }
: w! D% c: l! Y0 I# \3 N" }
8 S0 u& f4 f5 t1 F/ s
: z; e$ D' }/ v5 L( q+ L" S 至此mysql就开启了一个线程对 3306 端口进行监控,等待客户端请求触发 add_connection 回调。
& C. W5 D2 \( [" n . m+ F3 A8 ?( B" D" c
: r/ \9 Y8 s! m& U% w
2. 理解mysql是如何处理sql请求9 R, R4 [& {2 O; }- k! L
这里我以Insert操作为例稍微解剖下处理流程:
; I+ _# g7 c7 `9 B# R : g; R2 d3 v @$ [, L
当用户有请求sql过来之后,就会触发 thread_scheduler的回调函数add_connection。6 S7 X, i/ e5 n0 E
' k% Y. \. {/ q+ L. Z5 r; t$ o
# a0 r$ S* g9 k+ @$ w) l3 a5 m/ M/ A static scheduler_functions one_thread_per_connection_scheduler_functions=4 ]% i2 r! H3 v- r# [( V: p0 G
{1 D+ A, f3 W9 g
0, // max_threads
4 q: {0 I+ D5 w' }+ Y% S2 p( r: ` NULL, // init3 A, R3 d+ ^6 z
init_new_connection_handler_thread, // init_new_connection_thread; r. t! K5 [5 K
create_thread_to_handle_connection, // add_connection
$ _7 c; s; k: s* s+ f NULL, // thd_wait_begin
1 f* C# \8 a9 }) e4 {5 i. A6 ^ NULL, // thd_wait_end
/ @' l6 c: s% B3 M/ S5 Y4 v0 y NULL, // post_kill_notification
! v& T7 h6 Z# m1 A8 F) P one_thread_per_connection_end, // end_thread& b, x3 r5 e g& A7 b2 ^2 x& L# d: q
NULL, // end$ j% L+ B) y$ Z, E8 X
};' c5 ^2 f% P) V
) `& f- O3 g' x% V3 ?
+ X5 W, N/ a3 ^- ` 从 scheduler_functions 中可以看到,add_connection 对应了 create_thread_to_handle_connection,也就是请求来了会触发这个函数,从名字也可以看出,用一个线程处理一个用户连接。/ _8 n% L1 `7 c8 o" l0 l& G, `! D
: _$ C7 _* d' H' E F3 q <1> 客户端请求被 create_thread_to_handle_connection 接管及调用栈追踪
4 Q% R8 Z( a: I- M8 i; x! ^
; |* a, J) T d# k* w' r+ q void create_thread_to_handle_connection(THD *thd)
" ?- s. a1 o% A+ T- W5 W {
7 k# _. Q2 m& P, `& `0 M if ((error= mysql_thread_create(key_thread_one_connection, &thd->real_id, &connection_attrib,
! p. i8 N+ c. F, N, k' Q7 }6 W" l handle_one_connection,(void*) thd))){}
8 J( n5 Z$ }% l. s3 q }0 t+ S+ s& @4 A- V2 P
//触发回调函数 handle_one_connection" K; ~. ]' I$ |; G5 r
pthread_handler_t handle_one_connection(void *arg)# e* J3 \' f; m" e5 a2 l8 \
{6 w7 k! k6 h+ x. v% R; R# U' t' g3 c
do_handle_one_connection(thd);
# }# H4 ?) n) ^ }2 v- v6 Q- e1 R9 q' E* q3 F
//继续处理: r: u0 R9 A* A2 C! o5 f" t
void do_handle_one_connection(THD *thd_arg){/ S' p4 Q) |! ]# ]9 \
while (thd_is_connection_alive(thd))
, t- i9 o- I; x4 F8 F9 u( p- Q {
4 U8 p) u1 {$ @% u8 ~$ j mysql_audit_release(thd);! b: C( a% e& i$ g) n0 R
if (do_command(thd)) break; //这里的 do_command 继续处理
# e. u8 m+ M2 {4 h6 W' W9 h }
$ O' d& Z* G* k) j: o }! B+ |( G. H1 ?% v. q- f8 d, |
//继续分发
! u) z) i2 s7 m6 q* r bool do_command(THD *thd)
8 R3 `$ H* t' N7 W ^+ Q5 e6 ` {
7 M% g6 N: W& Y7 o/ A% N+ ^2 @/ ~" [ return_value= dispatch_command(command, thd, packet+1, (uint) (packet_length-1));) u5 c" v+ W& o9 D* v3 }& Y
}
8 R: {% h9 S2 L bool dispatch_command(enum enum_server_command command, THD *thd, char* packet, uint packet_length)! U; B! S: p7 B2 p
{+ |6 ]' X8 p6 T" A$ K; Y# f# B
switch (command) {
# S2 G! E7 D4 V6 [% ~. O6 J case COM_INIT_DB: .... break;9 R A# p7 R6 W) A4 T
...) r: C# f. h, N
case COM_QUERY: //查询语句: insert xxxx
' n# b$ q7 G n. a$ i+ ]/ O, W g mysql_parse(thd, thd->query(), thd->query_length(), &parser_state); //sql解析! U/ U7 d9 {4 v
break;3 F5 ]- T0 A7 w/ n% T2 }0 b9 l
}
9 _: |; b! K. k0 w/ v3 Y5 B4 x1 G }; c- {0 t/ H9 d6 Y& |
//sql解析模块
/ m" t+ e- K4 J- _& {: J) l void mysql_parse(THD *thd, char *rawbuf, uint length, Parser_state *parser_state)
y6 b. h2 o4 I" d; G {
; x w* p7 u6 E, n! x/ J8 a8 W+ t: B error= mysql_execute_command(thd);. E' b" e2 ]# h! }( I$ w: r1 t% ^
}# U+ b8 J: U# N1 x1 ?. Y, D
* E5 S. i; a# H: B% m, v
3 n: X2 t: a! \& p- [ <2> 到这里它的Parse,Optimizer,Cache都追完了,接下来看sql的CURD类型,继续追。。。- ~ b, X8 u3 `
# f2 P$ ^# C* O0 R0 Z" q7 Z
//继续执行5 n& W% a9 S, i* L
int mysql_execute_command(THD *thd)
, O( f; | D- W' B5 V {* ]) T. s# E( B6 I& x
switch (lex->sql_command)
5 o5 _* N$ @. p3 s {
4 `+ I+ H4 T) m. y3 A' u case SQLCOM_SELECT: res= execute_sqlcom_select(thd, all_tables); break;+ v) y# c. _+ k: e* [. y
3 u+ V4 _) Y1 V. `3 D% L5 N& \: x R
//这个 insert 就是我要追的
/ q6 _* x# e) m case SQLCOM_INSERT: res= mysql_insert(thd, all_tables, lex->field_list, lex->many_values,0 h2 ]. h( E; U: C5 s+ r% j
lex->update_list, lex->value_list,: N$ p7 Z7 v9 p1 y/ \
lex->duplicates, lex->ignore);, P2 s3 ?5 _2 I4 C$ @$ p7 ~
}) B( v- R8 ?7 f# @4 K" N, u
}
1 f- p7 ?$ o3 Q$ ~# ?' J+ M5 x) ^ //insert插入操作处理% K- w2 ]1 f0 i$ [6 z
bool mysql_insert(THD *thd,TABLE_LIST *table_list,List<Item> &fields, List<List_item> &values_list,/ h3 O1 q, v; }7 I! w
List<Item> &update_fields, List<Item> &update_values, / [/ A' m! Z* e6 @* D6 w4 R5 r
enum_duplicates duplic, bool ignore)* u" E/ x% S! @
{0 a% X. F s; a: l+ A# x
while ((values= its++))
) H' U3 Q: @ G1 B' ]! e% x. o! @ {
! c0 Z2 C/ |- |& a error= write_record(thd, table, &info, &update);3 s6 P& P& P( H5 ^' H
}
/ S4 J7 c: c# F1 Q7 Q) ^" ]1 g }
7 p3 ^7 Q/ `! U/ @! d) \$ q: S //写入记录 c% j) M5 c4 ^
int write_record(THD *thd, TABLE *table, COPY_INFO *info, COPY_INFO *update)
. S1 y/ n% ^, h$ o2 y {
$ J" g, F3 O% @8 j; V0 Y( j# G if (duplicate_handling == DUP_REPLACE || duplicate_handling == DUP_UPDATE)" C! v% b$ \# h; l0 I
{8 w8 a( _; x$ L% }5 P3 E5 x
// ha_write_row 重点是这个函数+ F# ?4 J' N" ~ a7 v. p- T7 M* e
while ((error=table->file->ha_write_row(table->record[0]))); p8 x% h% T3 G
{
# r6 I1 N6 Z6 E6 y4 V5 Z9 ] ....! }* z6 c1 F' y3 }6 m; ]0 V. b
}8 t+ R' A+ Q3 z- {# x
}
. v0 I0 l& S5 G- j" \: U& R+ [ }6 j3 F3 y ?3 K% C4 Q
/ w4 `. M( v8 m0 h) a# P1 L
4 e( f; X0 `: M) z- S ! e }5 x- L- U# D/ Y+ t: @
可以看到,调用链还是挺深的,追到 ha_write_row 方法基本上算是追到头了,再往下的话就是 MySql Server 给 Storage Engine提供的接口实现了,不信的话继续看呗。。。
" x6 o5 b1 s/ e6 Y! a ' N0 @3 o+ n. _+ T$ Q
<3> 继续挖 ha_write_row% ]9 Q2 q6 n/ ^* a. B: Y
5 K+ `# D5 u7 z: a: _ P
int handler::ha_write_row(uchar *buf)
( I' {" b6 g1 }4 ?) [! V8 y6 x {6 t" r3 w$ `0 g3 z- o* w
MYSQL_TABLE_IO_WAIT(m_psi, PSI_TABLE_WRITE_ROW, MAX_KEY, 0,{ error= write_row(buf); })
" `, W: X+ B& _( N/ {3 W8 t0 K }$ Q$ K6 ^( C# ~! R2 A% ]2 ^* Z
: q' h; D) K2 }% X4 ^& g" |* S
//这是一个虚方法0 i0 f! A J5 f# B$ C5 n
virtual int write_row(uchar *buf __attribute__((unused)))
* J' V, v1 @! a1 ^3 E {
$ \' s z: u3 U2 z5 P return HA_ERR_WRONG_COMMAND;
9 x9 P1 p& F8 O2 o$ S }9 Y) v5 C" ^8 A) u$ s R( q& P
$ b' t& e5 |1 _9 q8 v, ~
" V) ?. p$ A, B% j 看到没有,write_row是个虚方法,也就是给底层方法实现的,在这里就是给各大Storage Engines的哈。😁😁😁1 ?( @" |* B2 R. p
! b! t# `1 a$ |$ q/ Q& a! p4 m( w
3. 调用链图3 |; {$ T1 B) D& K
这么多方法,看起来有点懵懵的吧,我来画一张图,帮助大家理解下这个调用堆栈。) S, j5 x/ E" w3 e# h
# a. L& G% _/ ^4 s) [ ; [, r7 Q( y2 [8 ?$ V5 g$ J# ^
( B% A" j( m5 i" B0 c
三:总结
: \& A: y! X8 G6 z( `% O; B 大家一定要熟读架构图,有了架构图从源码中找信息就方便多了,总之学习mysql成就感还是满满的。
9 {/ J, h7 u: E# g" ^9 z4 x* G! x4 v ————————————————
9 T1 s- z0 q) M! | 版权声明:本文为CSDN博主「一线码农」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
& c$ F; @& k# K' H5 j( f* y2 @, G- A* y 原文链接:https://blog.csdn.net/huangxinchen520/article/details/106487415
' } m3 \2 k+ i1 Y
zan