浴室泡泡大作战
91.22M · 2026-03-29
要真正搞清“一条 Python 语句在 C 扩展里到底怎么跑”,最好把源码、调用栈、GIL 释放点、网络收发点都“跟”一遍。下面以
MySQLdb.connection.query("SELECT …")
为例,把从 Python 层到 C 层再到 libmysqlclient 的完整路径拆给你看。
(代码行号基于 mysqlclient-python 2.2.x,MySQL-Connector/C 8.0.x,CPython 3.11)
import _mysql # 这是 C 扩展模块
…
connect = _mysql.connect
Connection = _mysql.connection
MySQLdb 只是对 _mysql 做了一层薄薄的包装,真正的类叫 _mysql.connection,所以
MySQLdb.connection.query 其实就是
_mysql.connection.query。
模块初始化时把 connection 类型注册到 Python:
static PyTypeObject MyConnection_Type = {
PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "_mysql.connection",
.tp_methods = _mysql_ConnectionObject_methods,
…
};
.tp_methods 里有一项:
{"query", (PyCFunction)_mysql_ConnectionObject_query, METH_VARARGS,
PyDoc_STR("query(sql) -> None")},
因此 Python 虚拟机在执行
LOAD_ATTR + CALL_METHOD 指令时,通过 PyType 的 method resolution 直接找到
_mysql_ConnectionObject_query。
static PyObject *
_mysql_ConnectionObject_query(MyConnectionObject *self, PyObject *args)
{
char *sql;
int len;
if (!PyArg_ParseTuple(args, "s#", &sql, &len))
return NULL;
Py_BEGIN_ALLOW_THREADS /* 1. 主动释放 GIL */
int r = mysql_real_query(self->conn, sql, len);
Py_END_ALLOW_THREADS /* 2. 重新拿 GIL */
if (r) {
_mysql_Exception(self); /* 3. 把 mysql_error() 包装成 Python 异常 */
return NULL;
}
Py_RETURN_NONE;
}
关键三步:
mysql_real_query();mysql_error()/mysql_errno() 转成 _mysql.ProgrammingError/DatabaseError 等 Python 异常。libmysqlclient 是 C 语言官方客户端库,内部做了:
net_write_command())recv() 等待 server 返回结果包mysql_store_result()/mysql_use_result() 读结果集。如果你在 Python 里继续写:
cursor = conn.cursor()
cursor.execute("SELECT …")
cursor.execute 会再调 _mysql.connection.query,然后:
_mysql_ConnectionObject_store_result() 把 MYSQL_RES * 抓出来;PyList_New()/PyTuple_New() 把每一行转成 Python 对象;MYSQL_FIELD 的类型映射成 Python 类型(FIELD_TYPE_LONG→PyLong,FIELD_TYPE_DATETIME→PyDateTime 等)。Python 字节码
→ LOAD_ATTR “query”
→ CALL_METHOD (进入 C)
→ PyArg_ParseTuple
→ Py_BEGIN_ALLOW_THREADS
→ libmysqlclient:mysql_real_query (阻塞等网络)
→ Py_END_ALLOW_THREADS
→ 出错则 _mysql_Exception
→ 返回 Py_None。
apt install python3-dbg libmysqlclient-dev mysql-client-core-8.0-dbggdb -ex r --args python3 -c "
import MySQLdb, time
c=MySQLdb.connect(user='root',passwd='***')
c.query('SELECT 1')
"
(gdb) b _mysql_ConnectionObject_query(gdb) b mysql_real_query(gdb) n 可以看到 Py_BEGIN_ALLOW_THREADS 宏展开后调用PyThread_release_lock();s 能跟到 net_write_command() → send()/recv()。// demo.c
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <mysql.h>
typedef struct {
PyObject_HEAD
MYSQL *mysql;
} DemoConn;
static PyObject *
DemoConn_query(DemoConn *self, PyObject *args)
{
char *sql;
Py_ssize_t len;
if (!PyArg_ParseTuple(args, "s#", &sql, &len))
return NULL;
Py_BEGIN_ALLOW_THREADS
int err = mysql_real_query(self->mysql, sql, len);
Py_END_ALLOW_THREADS
if (err) {
PyErr_Format(PyExc_RuntimeError, "mysql: %s", mysql_error(self->mysql));
return NULL;
}
Py_RETURN_NONE;
}
static PyMethodDef DemoConn_methods[] = {
{"query", (PyCFunction)DemoConn_query, METH_VARARGS, ""},
{NULL}
};
static PyTypeObject DemoConnType = {
PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "demo.Connection",
.tp_basicsize = sizeof(DemoConn),
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_methods = DemoConn_methods,
};
static PyModuleDef demomodule = {
PyModuleDef_HEAD_INIT,
.m_name = "demo",
.m_size -1,
};
PyMODINIT_FUNC
PyInit_demo(void)
{
PyObject *m = PyModule_Create(&demomodule);
if (!m) return NULL;
if (PyType_Ready(&DemoConnType) < 0) return NULL;
PyModule_AddObject(m, "Connection", (PyObject*)&DemoConnType);
return m;
}
编译
python3 -m pip install mysqlclient # 确保有头文件
gcc -shared -fPIC $(python3-config --includes)
-I/usr/include/mysql -L/usr/lib/x86_64-linux-gnu
demo.c -lmysqlclient -o demo$(python3-config --extension-suffix)
测试
python3 -c "
import demo, MySQLdb
c = demo.Connection() # 这里省掉了 connect 参数,仅演示
c.query('SELECT 1')
"
用 ltrace -e mysql_real_query python3 test.py 就能抓到库函数调用。
Python 语句
↓ 字节码 CALL_METHOD
C 扩展函数 (PyCFunction)
↓ Py_BEGIN_ALLOW_THREADS
libmysqlclient 阻塞网络
↓ Py_END_ALLOW_THREADS
结果 or 异常 → Python 对象