【问题标题】:PHP, mysql and concurrent updatesPHP、mysql 和并发更新
【发布时间】:2012-11-24 19:04:11
【问题描述】:

您好,我有一个浏览器游戏,其中不同的玩家安排可以持续例如一到两个小时的动作。 cronjob 每分钟检查所有已完成的操作(endtime

最近发生了 - 比如说 - 有 100 个动作要完成,而 cronjob 任务 1 没有在一分钟内完成,所以 cronjob 任务 2 被触发,结果是 完成两次的所有动作。

我怎样才能避免这种情况再次发生?我必须为会话使用特定的事务隔离代码并保留行以供更新?

PHP 5.3.18 mysql 是 5.5.27 表引擎是 INNODB

当前每分钟调用一次的代码是这样的:

public function complete_expired_actions ( $charflag = false )
{       
    // Verifying if there are actions to complete...

    $db = Database::instance();
    $db -> query("set autocommit = 0");
    $db -> query("begin");

    $sql = "select * from 
        character_actions
        where status = 'running' 
        and endtime <= unix_timestamp()"; 

    $result = $db -> query ( $sql ) ;               

    // try-catch. Se si verifica un errore, l' azione che commette l' errore viene rollbackata

    foreach ( $result as $row )
    {
        try 
        {

            $o = $this->factory( $row -> action );
            $o -> complete_action ( $row );


            if ($row -> cycle_flag == FALSE)
            { 
                // non aperta ad attacchi SQl-injection perchè i parametri non sono passati via request
            if ( $charflag == true )
                    kohana::log( 'info', "-> Completing action: " . $row -> id . ' - ' . $row -> action . " for char: " . $row->character_id );

                $db->query( "update character_actions set status = 'completed' where id = " . $row->id ); 
                // in ogni caso invalida la sessione!
                Cache_Model::invalidate( $row -> character_id );                                                                    
            }

            $db->query('commit');

            } catch (Kohana_Database_Exception $e)
            {
                kohana::log('error', kohana::debug( $e->getMessage() ));
                kohana::log('error', 'An error occurred, rollbacking action:' .  $row->action . '-' . $row->character_id );
                $db->query("rollback");         
            }   

    }       

    $db->query("set autocommit = 1");

【问题讨论】:

    标签: mysql concurrency innodb


    【解决方案1】:

    在这种情况下,我使用一个新列,例如:mark to the table character_actions

    然后在工作开始时我调用这个查询:

    $uniqueid = time();
    $sql="update character_actions set mark = '$uniqueid' where status = 'running' and endtime <= unix_timestamp() AND mark is NULL "; 
    

    那么你的代码就可以了

    $sql = "select * from 
            character_actions
            where status = 'running' 
            and mark = '$uniqueid'"; 
    
        $result = $db -> query ( $sql ) ;   
    

    这种方法有一个限制,它会启动不同的并行工作,这会降低机器的速度,从而导致更多的延迟和更多的并行工作......

    可以通过引入限制来解决:

    $lim= 100 ; // tune to finish the job in 60 seconds
    $sql="update character_actions set mark = '$uniqueid' where status = 'running' and endtime <= unix_timestamp() AND mark is NULL limit $lim  "; 
    

    当然会导致积分归属延迟。

    【讨论】:

    • 好的,我想这会起作用,因为下一个 cronjobs 将获得一组单独的查询。谢谢你,我会试试这个想法。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-08-02
    • 1970-01-01
    • 2012-04-13
    • 2010-12-17
    • 2017-03-31
    • 1970-01-01
    相关资源
    最近更新 更多