Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mysql procedure #1688

Merged
merged 26 commits into from Jun 5, 2018

Conversation

Projects
None yet
2 participants
@twose
Copy link
Member

twose commented Jun 5, 2018

完整的MySQL存储过程支持

先贴上来, 还有一点细节问题, 说明我一会儿补


做了以下几件事:

fetch mode

一开始先想着和PDO一样给Swoole做一个fetch模式

['fetch_mode' => true] //连接配置里加入这个
$stmt = $db->prepare('SELECT `id` FROM `userinfo` LIMIT 2');
$stmt->execute(); // true = success
$stmt->fetch(); // result-set array 1
$stmt->fetch(); // result-set array 2

分离client和statement

加了一个 MYSQL_RESPONSE_BUFFER 宏, 处理了一些代码分离了client和statement的buffer

并给statement结构上也挂了一个result的zval指针

typedef struct
{
    ...
    swString *buffer; /* save the mysql multi responses data */
    zval *result; /* save the zval array result */
} mysql_statement;

这样就可以实现以下代码:

$stmt1 = $db->prepare('SELECT * FROM ckl LIMIT 1');
$stmt1->execute();
$stmt2 = $db->prepare('SELECT * FROM ckl LIMIT 2');
$stmt2->execute();
$stmt1->fetchAll();
$stmt2->fetchAll();

因为现在result是挂在statement上的, 和client分离干净, 就不会因为这样的写法产生错误

当然这并没有多大用, 主要还是为了后面处理多响应多结果集

分离mysql_parse_response

这样就就可以在除了onRead回调之外的别的地方复用这个方法, 处理多结果集了

存储过程

存储过程会返回多个响应, 如果和swoole之前的设计一样, 一次性全返回是不太现实的

PDO和MySQLi的设计都是用一个 next 方法来切换到下一个响应

刚开始是想做一个链表存储多个响应, 很快就发现并不需要

所以首先做了一个 mysql_is_over方法

它用来校验MySQL包的完整性, 这是swoole以前没有的, 所以在之前的PR后虽然可以使用存储过程, 但是并不能每次都收到完整的响应包, 第一次没收到的包会被丢弃

然后说一下几个注意点

  1. MySQL协议决定了并不能倒着检查status flag, 我们必须把每个包的包头都扫描一遍, 通过package length快速扫描到最后一个已接收的包体, 这里只是每次只是检查每个包前几个字节, 消耗不大
  2. MySQL其它包体中的 MYSQL_SERVER_MORE_RESULTS_EXISTS 的标志位并不准确, 不可采信, 只有eofok包中的是准确的 (这里一定要注意)
  3. 在存储过程中执行一个耗时操作的话, recv一次性收不完, 而且会等很久, 这时候需要return等下一次onRead触发(之前的代码里是continue阻塞), 这就不得不在client上加一个check_offset来保存上次完整性校验的位置, 从上个位置开始继续校验后续的MySQL包是否完整, 节省时间
  4. 存储过程中遇到错误(error响应)就可以直接终止接收了
  5. 在PHP7的zval使用上踩了点坑, 现在理解了, 幸好有鸟哥的文章zval给我解惑..

校验包的完整性直到所有数据接收完毕

(分离了client和statement后, execute获取的数据是被存在statement->buffer里而不是client->buffer)

这时候onRead中只会解析第一个响应的结果, 并置到statement对象上, 而剩下的数据仍在buffer中, 并等待nextResult来推动offset解析下一个, 可以说是懒解析了, 有时候会比一次性解析所有响应划算, 而且我们可以清楚的知道每一次nextResult切换前后, 对应的affected_rows和insert_id的值(如果一次性读完, 只能知道最后的)

最后效果就是以下代码

$stmt = $db->prepare('CALL reply(?)');
$stmt->execute(['hello mysql!']); // true
do {
    $res = $stmt->fetchAll();
    var_dump($res);
} while ($stmt->nextResult());

非fetch_mode模式下这么写

$stmt = $db->prepare('CALL reply(?)');
$res = $stmt->execute(['hello mysql!']); // the first result
do {
    var_dump($res);
} while ($res = $stmt->nextResult());

比较巧妙的是nextResult推到最后一个response_ok包的时候会返回null, while循环终止, 我们就可以在循环后读取ok包的affected_rows, 如果最后存储过程最后一个语句是insert成功, 这里会显示1

var_dump($stmt->affected_rows); //1

25个单元测试都通过了, 但是这个单元测试数量感觉还不够, 后续会补

@matyhtf matyhtf merged commit bd4681f into swoole:master Jun 5, 2018

2 checks passed

Codacy/PR Quality Review Up to standards. A positive pull request.
Details
continuous-integration/travis-ci/pr The Travis CI build passed
Details
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.