先上手册:docs.mongodb.org.../command/findAndModify

MongoDB的findAndModify命令可以从数据库查找返回一个文档的同时更新/插入/删除文档,原子操作,线程安全,功能强大,原型复杂……
当然这也是没办法的事情,毕竟要应对各种奇怪的需求……

比较常见的应用有「自增序列」和「任务队列」:

自增序列(供参考的PHP代码实现)
<?php
/**
 * @param MongoCollection $coll the collection as ids storage
 * @param string          $name optional, specific table name
 * @return int
 */
function pkid(MongoCollection $coll, $name = '')
{
    $find = $coll->findAndModify(
        ['name' => $name],
        ['$inc' => ['pkid' => 1]],
        ['pkid' => 1],
        ['new' => true, 'upsert' => true]
    );

    return $find['pkid'];
}

 

任务队列(供参考的PHP代码实现)
<?php
const TASK_STAT_FAIL = -1;
const TASK_STAT_UNDO = 0;
const TASK_STAT_PROC = 1;
const TASK_STAT_DONE = 2;

// some test docs
$conn->insert([
    'time' => time(),
    'stat' => TASK_STAT_UNDO,
    'proc' => getmypid(),
    'type' => 'post',
    'args' => [],
]);
// ...

$cond = [
    'time' => ['$lt' => time() + 1],
    'stat' => TASK_STAT_UNDO,
    'proc' => ['$mod' => [1, 0]],
    'type' => 'post',
];
// retry 2 times
$cond['stat'] = ['$lt' => TASK_STAT_PROC, '$gt' => TASK_STAT_FAIL - 2];

do
{
    if ($task = $conn->findAndModify(
        $cond,
        ['$set' => ['time' => time(), 'stat' => TASK_STAT_PROC]],
        null,
        ['sort' => ['_id' => 1]]
    ))
    {
        // run task
        // ...
        // set task status to $task['stat']-1 if failed else TASK_STAT_DONE

        $cond['_id'] = ['$gt' => $task['_id']];
    }
    else
    {
        // IO loop
        unset($cond['_id']);

        // no task! sleep for 1 second
        sleep(1);
    }
    
    $cond['time']['$lt'] = time() + 1;
} while (time() - $_SERVER['REQUEST_TIME'] < 3600); // 1 hour timeout