mysqli_poll如何工作? [英] How does mysqli_poll work?

查看:40
本文介绍了mysqli_poll如何工作?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

mysqli_poll 上的文档有点稀疏.

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

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

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

最后,secusec实际上有什么作用吗?如果是这样,该怎么办?我尝试将sec设置为0,将usec设置为1(我认为这意味着0秒+ 1微秒= 1微秒的总等待时间),但是当我运行大型查询时,它会暂停一秒钟以上,因此超时似乎不会中止或导致错误.它是做什么的?

解决方案

TL; DR:您的假设是正确的,但在编码时要小心.


mysqli_poll是一个围绕套接字选择的薄型方便包装器,并且了解 socket_select 的工作原理将使您对理解此功能有很长的路要走.

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

要回答您的问题:

在我看来,$ read是一个数组,用于保存要检查的连接,但是$ error和$ reject应该是未填充的var,如果有错误,该变量将由函数填充.正确吗?

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

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

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

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

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

是的,这很关键,在文档中并不明显. $read将被修改为准备读取的连接列表.您将遍历它们并开展业务.但是,必须注意的一点是:如果修改了$read,如果将轮询置于循环中并尝试再次从中读取,会发生什么情况?好吧,您只会从子集中读取内容,这不是您想要的.

在PHP中进行选择时,大多数示例显示的是将源$read数组复制到到新数组中,然后再进行选择.在mysqli_poll的手册页中,请注意以下循环,该循环在调用mysqli_poll之前重置"读取的数组:

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

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

socket_select 上的PHP注释中可以看到另一个示例.请注意$read = $clients;在选择之前如何?

最后一个问题:

最后,sec和usec实际上有什么作用吗?如果是这样,该怎么办?我尝试将sec设置为0,将usec设置为1(我认为这意味着0秒+ 1微秒= 1微秒的总等待时间),但是当我运行大型查询时它会暂停一秒钟以上,因此它似乎不会中止或在超时时导致错误.它是做什么的?

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

作为旁注, mysqli_poll的单元测试/a>可能会发光.


更新:昨晚我不在计算机旁进行测试.现在,我已经分享了一些看法.

测试1:长时间运行的查询

$ 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\n", $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\n\n",
        $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秒钟即可解决,并且民意调查发现这几乎恰好是2秒.

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

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

民意调查将在半秒钟后开始,并且循环将按预期运行四次.如果像示例中那样将其更改为1微秒,则它会在1微秒后开始运行.而且,如果将其更改为0秒和0微秒,则它会尽可能快地运行.

所以,当我说0意味着永远等待时,我肯定是错误.

测试2:多个查询,一些错误,一些长时间运行,并且超时

让我们将脚本更改为具有更多链接,然后重试:

$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\n", $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\n", $j, json_encode($result->fetch_assoc()));
                mysqli_free_result($result);
            } else if (false !== $result) {
                printf("link #%d, output=%s\n", $j, $link);
            } else {
                printf("link #%d, error=%s\n", $j, mysqli_error($link));
            }
        }
    }
    printf(
        "finish i=%d, count(read, error, reject)=(%d, %d, %d) @ T+%f\n\n",
        $i++, count($read), count($error), count($reject), (microtime(true)-$begin)
    );
} while (count($links) !== count($read) + count($error) + count($reject));

在此测试中,我希望有两个结果可以立即解决:一个是语法错误,另一个是数据行.我还期望这将花费1.5秒,因为休眠2秒的查询要等到超时时间后才能解决.事实并非如此:

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:我们正在遍历所有链接,并且要求每个链接都获得收益. 收割过程一直等到查询结束.

测试3:目标明确的长期查询:

与测试#2相同,但是这次让我们对我们要收获的东西保持精明.

$ 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\n", $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\n",
        $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\n");
    }
    printf("finish i=%d\n\n", $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\n", $label, json_encode($result->fetch_assoc()));
            mysqli_free_result($result);
        } else if (false !== $result) {
            printf("%s, output=%s\n", $label, $link);
        } else {
            printf("%s, error=%s\n", $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

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

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

The documentation on mysqli_poll is a bit sparse.

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.

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?

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?

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: Your suppositions are correct, but take care in your coding.


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 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.

To answer your questions:

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?

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

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

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.

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

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?

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.

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;
}

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.

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

To your last question:

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?

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".

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.

test 1: long running queries

$ 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\n", $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\n\n",
        $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

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);

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.

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

test 2: multiple queries, some error and some long running, with timeouts

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\n", $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\n", $j, json_encode($result->fetch_assoc()));
                mysqli_free_result($result);
            } else if (false !== $result) {
                printf("link #%d, output=%s\n", $j, $link);
            } else {
                printf("link #%d, error=%s\n", $j, mysqli_error($link));
            }
        }
    }
    printf(
        "finish i=%d, count(read, error, reject)=(%d, %d, %d) @ T+%f\n\n",
        $i++, count($read), count($error), count($reject), (microtime(true)-$begin)
    );
} while (count($links) !== count($read) + count($error) + count($reject));

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

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.

test 3: long running queries with targeted reaping:

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\n", $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\n",
        $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\n");
    }
    printf("finish i=%d\n\n", $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\n", $label, json_encode($result->fetch_assoc()));
            mysqli_free_result($result);
        } else if (false !== $result) {
            printf("%s, output=%s\n", $label, $link);
        } else {
            printf("%s, error=%s\n", $label, mysqli_error($link));
        }
    }
}

And now run it.

$ 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.

I've opened documentation bug #70505 on it.

这篇关于mysqli_poll如何工作?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆