防止从MySQL和PHP发送重复记录

DS9*_*DS9 7 php mysql yii2

我有一个表作为ad_banner_queue,我用它来根据广告的权重生成队列.广告被插入广告表中.如果队列中的所有现有广告都已传递给用户,则会生成队列.

现在的问题是,如果同时发出请求并且Rand()返回相同的记录,我应该如何阻止发送重复的广告?

以下是代码:

<?php
/* To Get the random Ad */
public function getBanner($params) {
    /* Fetch the Random from table */
    $ads_queue = (new \yii\db\Query())
            ->select('ad_quque_id, banner_image, unique_code')
            ->from('ad_banner_queue')
            ->join('inner join', 'advertisement', 'ad_banner_queue.ad_id = advertisement.ad_id')
            ->where('is_sent=0')
            ->orderBy('RAND()')
            ->one();

    /* In case of queue is not there generate the new queue */
    if ($ads_queue === false) {
        $output = $this->generateAdQueue();
        //In case of something went wrong while generating the queue
        if ($output == false) {
            return array();
        }

        //Now fetch the record again
        $ads_queue = (new \yii\db\Query())
                ->select('ad_quque_id, banner_image, unique_code')
                ->from('ad_banner_queue')
                ->join('inner join', 'advertisement', 'ad_banner_queue.ad_id = advertisement.ad_id')
                ->where('is_sent=0')
                ->orderBy('RAND()')
                ->one();
    }

    /* Now, marked that one as is_sent */
    Yii::$app->db->createCommand()->update('ad_banner_queue', ['is_sent' => 1], 'ad_quque_id =:ad_quque_id', array(':ad_quque_id' => $ads_queue['ad_quque_id']))->execute();
    return $ads_queue;
}

/**
 * Below will Generate the Queue if not exist
 */
public function generateAdQueue() {
    /* First check thatt there is existing queue, if so don't generate it */
    $data_exist = (new \yii\db\Query())
            ->select('ad_quque_id')
            ->from('ad_banner_queue')
            ->where('is_sent=0')
            ->scalar();
    if ($data_exist === false) {
        /* Delete all other entries */
        (new \yii\db\Query())
                ->createCommand()
                ->delete('ad_banner_queue')
                ->execute();

        /* Fetch all banner */
        $ads = (new \yii\db\Query())
                ->select('ad_id, unique_code, ad_name, banner_image,ad_delivery_weightage')
                ->from('advertisement')
                ->where('status_id in (8)') //Means only fetch Approved ads
                ->all();
        if (!empty($ads)) {
            foreach ($ads as $ad) {
                /* Make entry as per that weightage, example, if weightage is 10 then make entry 10 times */
                $ins_fields = array();
                for ($i = 1; $i <= $ad['ad_delivery_weightage']; $i++) {
                    $ins_fields[] = array($ad['ad_id']);
                }
                Yii::$app->db->createCommand()->batchInsert('ad_banner_queue', ['ad_id'], $ins_fields)->execute();
            }
            return true;
        } else {
            return false;
        }
    } else {
        return false;
    }
}
Run Code Online (Sandbox Code Playgroud)

inq*_*uam 5

我认为你的意思是那些同时进行请求的不同"人"不应该得到相同的随机行?最稳健的方式,不进行测试,以避免在两个运行请求中选择相同记录两次的可能性很小,可能是锁定表并在事务中执行读取和更新.您必须使用支持此功能的存储引擎,例如InnoDB.

完成LOCK TABLESUNLOCK TABLES使用事务表(如InnoDB表)的方法是开始事务SET autocommit = 0,而不是 START TRANSACTION后跟LOCK TABLES.那么UNLOCK TABLES在明确提交事务之前不应该调用.

例如,如果您需要一次性读取和写入表格,则可以执行以下操作:

SET autocommit = 0;
LOCK TABLES ad_banner_queue AS ad_banner_queue_w WRITE, ad_banner_queue AS ad_banner_queue_r READ;
... perform your select query on ad_banner_queue_r, then update that row in ad_banner_queue_w with is_sent = 1...
COMMIT;
UNLOCK TABLES;
Run Code Online (Sandbox Code Playgroud)

我们使用别名锁定的原因是您不能使用相同的名称在单个查询中多次引用锁定的表.因此我们使用别名来获取表和每个别名的单独锁.


小智 5

虽然这似乎是一个微不足道的问题,但它根本不是,有几种方法可以处理它,每种方法都有自己的缺点,主要你可以从三个不同的角度来面对这个问题:

与它共存

在现实生活中,您获得重复拉取的机会很低,您需要认真考虑是否愿意承担额外的工作以确保广告不会连续显示两次,您还需要考虑缓存存在并且您可能会打破你的大脑让广告原子化只是为了发现浏览器/代理/缓存正在提供重复的广告:(

在数据库上处理

您可以处理这个问题,让数据库负责保持数据安全和连贯(实际上这是数据库的主要任务),有几种方法可以做到:

  • 锁和表(如前所述),我个人喜欢在 PHP 和 MySQL 中使用锁的方法,您将遭受性能损失和死锁风险,但无论如何它仍然是一个解决方案,您只需在您的队列表上选择更新即可确保没有人再次阅读,直到您更新。这里的问题是您将在完成此操作时锁定整个表,并且您需要小心您的数据库驱动程序和自动提交。
  • 游标游标是基本上为您愿意执行的职责而创建的数据库结构,您创建一个游标并使用其功能安全地遍历它。由于事务的原因,在 PHP 中使用游标也可能非常棘手,您需要非常清楚自己在做什么以避免错误。
  • 游标和存储过程将其处理到数据库中的最佳方法是管理数据库本身内部的游标,这就是存储过程存在的原因,只需创建过程以从游标中提取新项目并在全部消耗完后再次填充它。

在 PHP 端处理

在这种情况下,您需要在 PHP 上实现自己的队列,可能有多种方法可以实现,但主要问题可能是在您的应用程序上实现多进程安全的原子操作,如果您不是 100,我个人不喜欢使用任何类型的锁% 确定您的应用程序的执行流程,否则您可能最终将其全部锁定。无论如何,这里有三个机会:

  • 使用包含在 php 或第三方中的sem 或互斥锁,超时和锁可能会变成一个地狱,它们不容易被检测到,因此如上所述我会避免它。

  • 使用 PHP MSG Queue我认为这是最安全的方法,只要您在 *nix 系统上运行您的应用程序,只需将所有可用广告发送到消息队列,而不是在数据库上创建表,一旦所有广告都被消耗,您就可以重新生成再次排队,该系统的缺点是您的服务器无法分发,如果您在重新启动之前不保存它,您可能会丢失当前的队列状态。

  • 第三方队列系统取决于您的应用程序工作负载或交互您可能需要使用队列管理系统,如果您想要一个分布式系统,这是必须的,使用 msg 队列系统来处理这个问题可能听起来太严重了,但这种方法可能是救命稻草。

概括

如果你不能忍受它并且对数据库足够精通,我会去使用存储过程和游标,你不需要为并发而烦恼,只要你使用符合 ACID 的数据库,数据库就会处理它(不是 MyISAM 即)

如果您想避免进入数据库并且您的系统是 *nix 并且不会被分发,您可以尝试使用 msg_queues

如果你认为你的系统有时可能是分布式的或者不依赖旧的 SysV 机制,你可以尝试像 RabbitMQ 这样的消息代理,这些好东西是令人上瘾的,一旦你开始使用它们,你就会开始每天看到它们的新用途。

  • 你可以用 Innodb 做行级锁。您还可以使用 GET_LOCK() 获取命名锁,而不锁定表;这对于这种情况非常有用...... (2认同)