前两天因为一个服务内存泄漏的问题,让我想看看python mysql是如何实现的. 以前没注意到MySQLdb的代码实现,通过这两天的学习,还是很有收获的。

以前总是以为MySQLdb的_mysql.c很是复杂,事实上虽然也复杂了点,但基本都看的懂,_mysql.c里不含有mysql的底层协议解析及一些底层针对mysql的互动。

该文章写的有些乱,欢迎来喷 ! 另外文章后续不断更新中,请到原文地址查看更新. http://xiaorui.cc/?p=3234

python MySQLdb的源代码地址,  https://github.com/farcepest/MySQLdb1

_mysql.c在头部引入了mysql c库模块,在python下是有必要使用mysql c api的,不然你又要实现更复杂的轮子. 另外python不能直接引用C库代码的,必修经过比如PyObject改造。 有个趣事,mysql官方也有一个python的mysql库,他其实就是咱们现在说的MySQLdb/_mysql.c以前的版本。 在stackoverflow看到一些蛛丝马迹,说是最开始的python _mysql.c是有bug的,后来这老外在MySQLdb里修复了后,这_mysql.c也被mysql官方集成进去了。 所以我们在mysql官方看到的样例是基于_mysql.c以前的版本。

下面是_mysql.c提供给MySQLdb调用的函数, 利用PyObject我们可以使python调用C实现的一些函数,这没的说. 下面是_mysql.c提供给MySQLdb的主要的几个函数.


我们从创建连接开始说, _mysql_connect主要是调用了_mysql_ConnectionObject_Initialize方法,传递的参数是host,user,passwd,charset等连接选项.

初始化连接的函数主要的代码就一行,mysql_real_connect是mysql c库的函数,不是_mysql.c构建的.

我们再来说明下cursor的实现, cursor其实就是结果数据集合。 对于cursor存储结果是有两种存储方式. 下面是mysql c api关于存储的那两个函数. 

mysql_store_result(MYSQL *mysql);

mysql_use_result(MYSQL *mysql);

这两个函数分别代表了获取查询结果的两种方式, 我们大多数的场景还是会选择store_result。
第一种,调用mysql_store_result函数将从Mysql服务器查询的所有数据都存储到客户端,然后读取;
第二种,调用mysql_use_result初始化检索,以便于后面一行一行的读取结果集,而它本身并没有从服务器读取任何数据,这种方式较之第一种速度更快且所需内存更少,但它会绑定服务器,阻止其他线程更新任何表,而且必须重复执行mysql_fetch_row读取数据,直至返回NULL,否则未读取的行会在下一次查询时作为结果的一部分返回,故经常我们使用mysql_store_result。

下面的逻辑对应MySQLdb的是cursor = db_conn.cursor(cursorclass=xxx)的动作,cursor 可以理解为存储的游标。 我们可以看到每次创建cursor的时候,他都会创建新的result对象并初始化,另外会为此申请新的内存区。

我们通过MySQLdb的execute执行sql语句,这里不限于select、insert、update、delete,执行的主要代码就是调用_mysql.c的_query()函数.


还是调用了mysql原生c库的mysql_real_query()函数。 该函数有三个参数,mysql连接,query请求语句,盘问语句的长度. 


我们在MySQLdb模块里是如何取出查询结果的? 有三个获取方法, cursor.fetchone(), cursor.fetchall() ,cursor.fetchmany().  

下面是MySQLdb/cursors.py文件的CursorUseResultMixIn类.  我们会发现不管是fetchone(1)单个,fetchall(0)所有,fetchmany(x)多个都调用了_fetch_row函数,他的参数是是size,这size代码你要获取多少数据。_fetch_row调用的是_mysql.c提供的fetch_row函数.

下面是_mysql.c fetch_row的实现代码. 在_mysql_ResultObject_fetch_row里也是可以看到两大参数,一个maxrows,一个格式 .


_mysql_ResultObject_fetch_row调用_mysql__fetch_row 获取结果. 主要两个参数 skiprows,maxrows,用途当然是分页的.

一旦调用了mysql_store_result()并获得了不是Null指针的结果,可以调用mysql_fetch_row()来获取结果集中的行,或调用mysql_row_seek()和mysql_row_tell()来获取或设置结果集中的当前行位置。


Cursor.rowcount 可以查看影响行数


insert_id是可以帮助我们拿到当前表的最新主键id, 这个方法只适用于有自增id的表里. 这个结果不是来自mysqldb本身的记录,而是从服务端获取的,当然这操作没有原子性保证. 


mysql的close()函数比较简单,直接调用mysql c库的mysql_close()方法来关闭连接.

当我们每次去创建新cursor结果集的时候,_mysql.c做了什么? 清理回收空间.

这么一看其实python的mysqldb实现还是有点意思的,MySQLdb的python代码只是做了围绕_mysql.c的封装,    如果出于好玩的化,我们也可以直接 import _mysql来实现db的增删改查。 MYSQL_RES的接受及空间回收是在_mysql.c里实现的,话说在python下回收大数据是个很郁闷的事情。 

对于_mysql.c实现就说这么多了,我们的讲述已经涵盖了 connect, execute ,fetchall, close主要四大功能函数。 

END.



对Python及运维开发感兴趣的朋友可以加QQ群 : 478476595 !!!
{ 2000人qq大群内有各厂大牛,常组织线上分享及沙龙,对高性能及分布式场景感兴趣同学欢迎加入该QQ群 }

另外如果大家觉得文章对你有些作用!   帮忙点击广告. 一来能刺激我写博客的欲望,二来好维护云主机的费用.
如果想赏钱,可以用微信扫描下面的二维码. 另外再次标注博客原地址  xiaorui.cc  ……   感谢!

线上mysql优化器误判引起慢查询

前言:      收到疯狂的慢查询及请求超时报警,通过metrics分析出来自mysql请求的异常,cli —> show proceslist 看到很多...

阅读全文

让人无语的MySQL嵌套事务

      MySQL是支持嵌套事务的,但是没多少人会这么干的…. 前段时间在国外看到一些老外在争论MySQL嵌套事务的场景必要性。 逗死我了,...

阅读全文

为什么不建议innodb使用亿级大表

上下文:        在Facebook上看到了一个比较新颖的mysql技术话题, 为什么不推荐在mysql下使用大表 , 或者说 什么情况下可以...

阅读全文

发表评论