mysqli_poll 如何工作?

2021-12-25 00:00:00 php mysql mysqli

mysqli_poll 的文档有点少.

在示例中,他们创建了 3 个相同的数组,其中包含要检查的所有 MySQLi 连接,但是如果您阅读参数说明,那看起来根本不正确.

In the example, they create 3 identical arrays containing all the MySQLi connections to check, but if you read the parameter descriptions, that doesn't look right at all.

在我看来 $read 是一个包含要检查的连接的数组,但是 $error$reject 应该是未填充的变量如果有错误,将由函数填充.对吗?

It sounds to me like $read is an array holding the connections to check, but $error and $reject should be unpopulated vars that will be filled by the function if there are errors. Is that correct?

当函数返回 >= 1 时会发生什么?你怎么知道哪些连接已经准备好收割"数据了?$read 也被修改了吗?即减少到实际有数据的设置连接?

What happens when the function returns >= 1? How do you know which connections have data ready to be "reaped"? Is $read modified too? i.e. reduced to the set connections that actually have data?

最后,secusec 真的做了什么吗?如果是这样,是什么?我尝试将 sec 设置为 0 并将 usec 设置为 1(我假设这意味着 0 秒 + 1 微秒 = 1 微秒总等待时间)但它当我运行一个大查询时,它会暂停超过一秒钟,所以它似乎不会在超时时中止或导致错误.它有什么作用?

Lastly, do sec and usec actually do anything? If so, what? I tried setting sec to 0 and usec to 1 (I assume that means 0 seconds + 1 microsecond = 1 microsecond total wait time) but it pauses for more than a second when I run a big query, so it doesn't seem to abort or cause an error when it times out. What does it do?

推荐答案

TL;DR:您的假设是正确的,但请注意编码.

mysqli_poll 是一个围绕套接字选择的薄型便利包装器,并且了解 socket_select 的工作原理会让您大有帮助以了解此功能.

mysqli_poll is a thin convenience wrapper around a socket select, and knowing how socket_select works will get you a long way toward understanding this function.

mysqli_poll 仅在底层驱动程序为 mysqlnd 时可用,因为只有 MySQL ND 提供对 MySQL 服务器的本机、套接字级访问.要记住的重要一点是套接字级别"访问使轮询成为可能,以及为什么理解套接字选择对于理解 mysqli_poll 的功能和限制至关重要.

mysqli_poll is only available when the underlying driver is mysqlnd, because only MySQL ND provides native, socket-level access to the MySQL server. The important point to keep in mind is that the "socket-level" access is what makes polling possible, and why understanding socket select is crucial in understanding the function and limitations of mysqli_poll.

回答您的问题:

在我看来,$read 是一个包含要检查的连接的数组,但 $error 和 $reject 应该是未填充的变量,如果出现错误,将由函数填充.对吗?

It sounds to me like $read is an array holding the connections to check, but $error and $reject should be unpopulated vars that will be filled by the function if there are errors. Is that correct?

是的,但不是完整的图片.注意mysqli_poll的签名:

Yes, but not a complete picture. Notice the signature of mysqli_poll:

int mysqli_poll ( 数组 &$read , 数组 &$error , 数组 &$reject , int $sec [, int $usec ] )

int mysqli_poll ( array &$read , array &$error , array &$reject , int $sec [, int $usec ] )

所有三个数组都是按引用传递的,这意味着 PHP 引擎能够修改所有三个数组,它会这样做.在明显的情况下,当来自 $read 的任何请求连接处于错误或连接拒绝状态时,它会修改 $error$reject.

All three arrays are pass-by-reference, meaning the PHP engine has the ability to modify all three, which it will. In the obvious case, it modifies $error and $reject when any of the requested connections from $read are in an error or connection rejected state.

但是PHP也会在有数据等待读取时修改$read.这是回答您问题的关键:

But PHP will also modify $read when there is data waiting to be read. This is the key to answering your question:

当函数返回 >= 1 时会发生什么?您如何知道哪些连接已准备好收割"数据?$read 也被修改了吗?即减少到实际有数据的设置连接?

What happens when the function returns >= 1? How do you know which connections have data ready to be "reaped"? Is $read modified too? i.e. reduced to the set connections that actually have data?

是的,这在文档中至关重要且不明显.$read 将被修改为准备读取的连接列表.您将遍历它们并做您的生意.但是一个必要的点被掩盖了:如果 $read 被修改,如果你把轮询放在一个循环中并尝试再次读取它们会发生什么?好吧,您只会从一个子集中读取,这不是您想要的.

Yes, and that is crucial and not obvious in the docs. $read is going to get modified to the list of connections that are ready to be read. You'll loop over them and do your business. But an imperative point is glossed over: if $read is modified, what happens if you put the poll in a loop and try to read from those again? Well, you'll only be reading from a subset, which isn't what you want.

在 PHP 中进行选择时,大多数示例表明,源 $read 数组在被选择之前被复制到一个新数组.在 mysqli_poll 的手册页中,注意这个循环在调用 mysqli_poll 之前重置"读取数组:

What most examples show when doing a select in PHP is that the source $read array is copied to a new array before being select'd. In the man page for mysqli_poll, notice this loop that "resets" the read array just before the call to the mysqli_poll:

foreach ($all_links as $link) {
    $links[] = $errors[] = $reject[] = $link;
}

这可能是最重要的一点:当 mysqli_poll 完成时,传递到 mysqli_poll 的这些数组中的每一个都会被修改:数组将被修剪,以便只有受影响的连接在结果中,因此您必须在每次调用 mysqli_poll 之前重置数组.

This is perhaps the most important point: each of these arrays passed into mysqli_poll will get modified when mysqli_poll finishes: the arrays will be trimmed so that only affected connections are in the result, so you have to reset the arrays each time before you call mysqli_poll.

另一个例子见这个关于socket_select的PHP注释.注意到如何在选择之前 $read = $clients; 吗?

Another example is seen in this PHP note on socket_select. Notice how $read = $clients; before the select?

最后一个问题:

最后,sec 和 usec 真的可以做什么吗?如果是这样,是什么?我尝试将 sec 设置为 0 并将 usec 设置为 1(我假设这意味着 0 秒 + 1 微秒 = 1 微秒总等待时间)但是当我运行一个大查询时它会暂停超过一秒,所以它似乎没有中止或超时时导致错误.它有什么作用?

Lastly, do sec and usec actually do anything? If so, what? I tried setting sec to 0 and usec to 1 (I assume that means 0 seconds + 1 microsecond = 1 microsecond total wait time) but it pauses for more than a second when I run a big query, so it doesn't seem to abort or cause an error when it times out. What does it do?

是的,它有效.这些应该表示上限 PHP 将等待数据在 $read 中的任何连接上可用(但请继续阅读).它对您不起作用,因为最短时间是 1 秒.当您将 0 设置为秒时,即使您有 >0 微秒,PHP 将其解释为永远等待".

Yes, it works. These should represent the upper-bound PHP will wait for data to become available on any of the connections in $read (but read on). It did not work for you because the minimum time is 1 second. When you set 0 for second, even though you had a > 0 microsecond, PHP interpreted that as "wait forever".

作为旁注,mysqli_poll 的单元测试 可能很有启发性.

As a side note, the unit tests for mysqli_poll might be illuminating.

更新:昨晚我没有靠近计算机进行测试.现在,我有一些观察要分享.

Update: I wasn't near a computer to test last night. Now that I am, I've got some observations to share.

$ cat mysqli_poll_test
$link  = mysqli_connect(...);
$sql   = 'SELECT SLEEP(2), 1';
mysqli_query($link, $sql, MYSQLI_ASYNC);

$links = array ($link);
$begin = microtime(true);
$i = 0;
do {
    printf("start i=%d @ T+%.f
", $i, (microtime(true)-$begin));
    $read = $error = $reject = $links;
    mysqli_poll($read, $error, $reject, 1, 500000);
    printf(
        "finish i=%d, count(read, error, reject)=(%d, %d, %d) @ T+%f

",
        $i++, count($read), count($error), count($reject), (microtime(true)-$begin)
    );
} while (count($links) !== count($read) + count($error) + count($reject));

$ php mysqli_poll_test
start i=0 @ T+0.000012
finish i=0, count(read, error, reject)=(0, 0, 0) @ T+1.501807

start i=1 @ T+1.501955
finish i=1, count(read, error, reject)=(1, 0, 0) @ T+2.001353

在这个测试中,长时间运行的查询是在 MySQL 服务器上简单的休眠 2 秒.mysqli_poll 的超时时间为 1.5 秒.正如预期的那样,经过 1.5 秒后,投票返回.同样正如预期的那样,没有准备好读取的数据,因此 do .. while 重新启动.在剩下的半秒后,轮询返回,表明一个链接已准备好读取.这是意料之中的,因为查询只需要 2 秒即可解决,而 poll 发现非常接近两秒.

In this test, the long running query is a simple sleep for 2 seconds at the MySQL server. The mysqli_poll has a timeout of 1.5 seconds. As expected, after 1.5 seconds elapse, the poll returns. Also as expected, there is no data ready to be read, so the do .. while restarts. After the remaining half-second, the poll returns indicating one link is ready to read. This is expected, because the query takes only 2 seconds to resolve and poll sees that very close to exactly two seconds.

如果将轮询超时更改为半秒并重新运行:

If you change the poll timeout to half a second and re-run:

// changed this from 1 to 0 --------V
mysqli_poll($read, $error, $reject, 0, 500000);

投票在半秒后开始,并且循环运行四次,正如预期的那样.如果像示例中那样将其更改为 1 微秒,它会在 1 微秒后退出.如果您将其更改为 0 秒和 0 微秒,它会尽可能快地运行.

The poll kicks out after half a second, and the loop runs four times, as expected. If you change it to 1 microsecond as in your example, it does kick out after 1 microsecond. And if you change it to 0 seconds and 0 microseconds, it runs as fast as it possibly can.

所以,当我说 0 意味着永远等待时,我绝对错了.

So, I was definitely wrong when I said 0 meant wait forever.

让我们更改脚本以添加更多链接,然后重试:

Let's change our script to have a few more links and try again:

$link0  = mysqli_connect(...);
$link1  = mysqli_connect(...);
$link2  = mysqli_connect(...);

$sql0   = 'SELECT SLEEP(2) AS wait, 1 AS num';
$sql1   = 'SELECT foo FROM';
$sql2   = 'SELECT 2 AS num';

mysqli_query($link0, $sql0, MYSQLI_ASYNC);
mysqli_query($link1, $sql1, MYSQLI_ASYNC);
mysqli_query($link2, $sql2, MYSQLI_ASYNC);

$links  = array ($link0, $link1, $link2);
$begin = microtime(true);
$i = 0;
do {
    printf("start i=%d @ T+%.f
", $i, (microtime(true)-$begin));
    $read = $error = $reject = $links;
    $count = mysqli_poll($read, $error, $reject, 1, 500000);
    if (0 < $count) {
        foreach ($links as $j => $link) {
            $result = mysqli_reap_async_query($link);
            if (is_object($result)) {
                printf("link #%d, row=%s
", $j, json_encode($result->fetch_assoc()));
                mysqli_free_result($result);
            } else if (false !== $result) {
                printf("link #%d, output=%s
", $j, $link);
            } else {
                printf("link #%d, error=%s
", $j, mysqli_error($link));
            }
        }
    }
    printf(
        "finish i=%d, count(read, error, reject)=(%d, %d, %d) @ T+%f

",
        $i++, count($read), count($error), count($reject), (microtime(true)-$begin)
    );
} while (count($links) !== count($read) + count($error) + count($reject));

在此测试中,我希望立即解决两个结果:一个是语法错误,另一个是数据行.我也希望这需要 1.5 秒,因为直到超时到期后才会解决休眠 2 秒的查询.情况似乎并非如此:

In this test, I'm expecting two results to resolve immediately: one a syntax error and the other a data row. I'm also expecting this to take 1.5 seconds, since the query sleeping 2 seconds will not resolve until after the timeout expires. That doesn't appear to be the case:

start i=0 @ T+0.000002
link #0, row={"wait":"0","num":"1"}
link #1, error=You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '' at line 1
link #2, row={"num":"2"}
finish i=0, count(read, error, reject)=(1, 0, 0) @ T+2.001756

start i=1 @ T+2.001827
finish i=1, count(read, error, reject)=(0, 0, 3) @ T+3.503024

它一直等到 SLEEP(2) 查询解析,违反了超时是等待上限的断言.发生这种情况的原因是 mysqli_reap_async_query:我们正在迭代所有链接,并且要求获取每个链接.收割过程一直等到查询完成.

It waits until the SLEEP(2) query resolves, violating the assertion that the timeout was the upper-bound for waiting. The reason this happens is the mysqli_reap_async_query: we're iterating over all links, and each of those is being asked to be reaped. The reaping process waits until the query finishes.

与测试 #2 相同,但这次让我们对收获的东西保持清醒.

Same as test #2, but this time let's be smart about what we're reaping.

$ cat mysqli_poll.php
<?php
$link0  = mysqli_connect(...);
$link1  = mysqli_connect(...);
$link2  = mysqli_connect(...);

$sql0   = 'SELECT SLEEP(2) AS wait, 1 AS num';
$sql1   = 'SELECT foo FROM';
$sql2   = 'SELECT 2 AS num';

mysqli_query($link0, $sql0, MYSQLI_ASYNC);
mysqli_query($link1, $sql1, MYSQLI_ASYNC);
mysqli_query($link2, $sql2, MYSQLI_ASYNC);

$links  = array ($link0, $link1, $link2);
$begin = microtime(true);
$i = 0;
do {
    printf("start i=%d @ T+%.f
", $i, (microtime(true)-$begin));
    $read = $error = $reject = $links;
    $count = mysqli_poll($read, $error, $reject, 1, 500000);
    printf(
        "check i=%d, count(read, error, reject)=(%d, %d, %d) @ T+%f
",
        $i, count($read), count($error), count($reject), (microtime(true)-$begin)
    );
    if (0 < $count) {
        reap('read', $read);
        reap('error', $error);
        reap('reject', $reject);
    } else {
        printf("timeout, no results
");
    }
    printf("finish i=%d

", $i++);
} while (count($links) !== count($read) + count($error) + count($reject));

function reap($label, array $links) {
    foreach ($links as $link) {
        $result = mysqli_reap_async_query($link);
        if (is_object($result)) {
            printf("%s, row=%s
", $label, json_encode($result->fetch_assoc()));
            mysqli_free_result($result);
        } else if (false !== $result) {
            printf("%s, output=%s
", $label, $link);
        } else {
            printf("%s, error=%s
", $label, mysqli_error($link));
        }
    }
}

现在运行它.

$ php mysqli_poll.php
start i=0 @ T+0.000003
check i=0, count(read, error, reject)=(1, 0, 0) @ T+0.001007
read, error=You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '' at line 1
finish i=0

start i=1 @ T+0.001256
check i=1, count(read, error, reject)=(1, 0, 1) @ T+0.001327
read, row={"num":"2"}
reject, error=You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '' at line 1
finish i=1

start i=2 @ T+0.001627
check i=2, count(read, error, reject)=(0, 0, 2) @ T+1.503261
timeout, no results
finish i=2

start i=3 @ T+1.503564
check i=3, count(read, error, reject)=(1, 0, 2) @ T+2.001390
read, row={"wait":"0","num":"1"}
reject, error=You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '' at line 1
reject, error=
finish i=3

好多了.每个查询都会在自己合适的时候解析,并填充适当的数组.与之前的示例相比,此示例中的重要区别在于我们遍历每个修改过的数组.这与某些文档相反,这些文档显示迭代所有链接本身.

Much better. Each query resolves in its own good time, with appropriate arrays filled out. The important difference in this example, versus earlier is that we iterate over each of the modified arrays. This runs contrary to some documentation, which shows iterating over all the links themselves.

我已经在其上打开了文档错误 #70505.

相关文章