有什么好方法可以灵活地存储和计算用户点?

时间:2018-10-09 15:51:00

标签: php laravel performance count

我想为用户提供不同的促销优惠。例如,在撰写文章时,对文章进行评分...

我正在寻找一种灵活的解决方案,在该解决方案中,我可以调整不同动作的得分,并且总分也会改变。

如果将点保存在表中,则以后将无法更改它们。像这样:

$request->user()->points += 10;
    $request->user()->save();

当我每次重新计算分数时,性能如何?像这样:

$articlepoints = $user->articles->count();
&votepoints = $user->votes->count();
$totalpoints = $articlepoints*10+$votepoints*5; 

还有其他选择吗?

2 个答案:

答案 0 :(得分:0)

如果试图最小化每个负载上的计数操作,则可以使用Cache :: remember()方法。您只需要决定要多久一次“过期”已计算的值。我假设您将在User模型中运行此功能,但是您可以在任意位置执行此操作:

$articlepoints = Cache::remember('articlepoints_'.$this->id, 5, function () {
    return $this->articles->count();
});

此代码的作用是检查索引“ articlepoints_userID”下该用户文章点是否存在于缓存中。 userID是动态的,并且将为每个用户存储一个不同的值。如果该索引存在,则将其用作值。

如果缓存中不存在该索引,则将传递给该函数,在该函数中返回计算出的值并将其存储5分钟。您可以对方程中的每个值(投票点和总点)执行此操作,并使用所需的任何到期时间。

使用此代码,可以大大减少对count()函数的调用。但是,您仍然可能要为每个数据库调用或磁盘调用,具体取决于您的缓存驱动程序。为了避免这种类型的开销,您需要查看其他缓存驱动程序,例如redis。

查看Laravel Cache docs,了解您可以使用缓存完成的所有其他出色操作。学习缓存系统是提高Laravel应用程序性能的好方法。

答案 1 :(得分:0)

这种事情的常见模式是保留一个表以记录每个导致得分的事件,并且还将总计的积分汇总到用户表或某种形式的统计信息表中的一列中,该表包含每个用户。这样,您便可以记录每个事件,如果您更改每种事件类型的价值,则可以追溯重新计算总数,无需每次都进行计算就可以访问总数(并在查询中使用它们)。

当我做这种事情时,我使用数据库中的触发器在存储事件时自动更新汇总总数,因此应用程序逻辑保持整洁。这是在MySQL中工作的示例:

CREATE TABLE `users` (
  `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
  `username` varchar(20) NOT NULL,
  `total_activity_points` int(11) UNSIGNED NOT NULL DEFAULT 0,
  PRIMARY KEY (`id`),
  UNIQUE KEY `users_username_uk` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;

INSERT INTO `users` (`username`) VALUES ('Bob');
INSERT INTO `users` (`username`) VALUES ('Alice');

CREATE TABLE `activities` (
  `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
  `title` varchar(20) NOT NULL,
  `points` int(11) UNSIGNED NOT NULL DEFAULT 0,
  PRIMARY KEY (`id`),
  UNIQUE KEY `activities_title_uk` (`title`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;

INSERT INTO `activities` (`title`,`points`) VALUES ('Article', 10);
INSERT INTO `activities` (`title`,`points`) VALUES ('Vote', 5);

CREATE TABLE `user_activities` (
  `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
  `user_id` int(11) UNSIGNED NOT NULL,
  `activity_id` int(11) UNSIGNED NOT NULL,
  `create_date` DATETIME NOT NULL,
  PRIMARY KEY (`id`),
  CONSTRAINT `user_activities_fk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE,
  CONSTRAINT `user_activities_fk_2` FOREIGN KEY (`activity_id`) REFERENCES `activities` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;

DELIMITER //
DROP TRIGGER IF EXISTS `after_insert_user_activities` //
CREATE TRIGGER `after_insert_user_activities`
  AFTER INSERT
  ON `user_activities`
  FOR EACH ROW
  BEGIN
    DECLARE v_total INTEGER DEFAULT 0;
    SET v_total = (SELECT SUM(`activities`.`points`)
                   FROM `user_activities`
                   INNER JOIN `activities` ON `activities`.`id`=`user_activities`.`activity_id`
                   WHERE `user_activities`.`user_id` = NEW.`user_id`);
    UPDATE `users`
    SET `total_activity_points` = v_total
    WHERE `users`.`id` = NEW.`user_id`;
  END;
//
DELIMITER ;

DELIMITER //
DROP TRIGGER IF EXISTS `after_delete_user_activities` //
CREATE TRIGGER `after_delete_user_activities`
  AFTER DELETE
  ON `user_activities`
  FOR EACH ROW
  BEGIN
    DECLARE v_total INTEGER DEFAULT 0;
    SET v_total = (SELECT SUM(`activities`.`points`)
                   FROM `user_activities`
                     INNER JOIN `activities` ON `activities`.`id`=`user_activities`.`activity_id`
                   WHERE `user_activities`.`user_id` = OLD.`user_id`);
    UPDATE `users`
    SET `total_activity_points` = v_total
    WHERE `users`.`id` = OLD.`user_id`;
  END;
//
DELIMITER ;

INSERT INTO `user_activities` (`user_id`,`activity_id`,`create_date`) VALUES (1,1,NOW());
INSERT INTO `user_activities` (`user_id`,`activity_id`,`create_date`) VALUES (1,2,NOW());
INSERT INTO `user_activities` (`user_id`,`activity_id`,`create_date`) VALUES (2,2,NOW());
INSERT INTO `user_activities` (`user_id`,`activity_id`,`create_date`) VALUES (2,2,NOW());

DELIMITER //
DROP TRIGGER IF EXISTS `after_update_activities` //
CREATE TRIGGER `after_update_activities` AFTER UPDATE ON `activities`
  FOR EACH ROW
  BEGIN
    DECLARE v_finished INTEGER DEFAULT 0;
    DECLARE v_userId INT(11) UNSIGNED;
    DECLARE v_total INT(11) UNSIGNED;

    DECLARE user_id_cursor CURSOR FOR SELECT DISTINCT(`user_activities`.`user_id`) FROM `user_activities` WHERE `user_activities`.`activity_id`=NEW.`id`;
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET v_finished = 1;

    IF NEW.`points` != OLD.`points` THEN
      OPEN user_id_cursor;

      get_user_ids: LOOP
        FETCH user_id_cursor INTO v_userId;
        IF v_finished = 1 THEN
          LEAVE get_user_ids;
        END IF;
        -- recalculate and store scores
        SET v_total = (SELECT SUM(`activities`.`points`)
                       FROM `user_activities`
                         INNER JOIN `activities` ON `activities`.`id`=`user_activities`.`activity_id`
                       WHERE `user_activities`.`user_id` = v_userId);
        UPDATE `users`
        SET `total_activity_points` = v_total
        WHERE `users`.`id` = v_userId;
      END LOOP get_user_ids;

      CLOSE user_id_cursor;
    END IF;
  END;
//
DELIMITER ;

DELIMITER //
DROP TRIGGER IF EXISTS `before_delete_activities` //
CREATE TRIGGER `before_delete_activities` BEFORE DELETE ON `activities`
  FOR EACH ROW
  BEGIN
    DECLARE v_finished INTEGER DEFAULT 0;
    DECLARE v_userId INT(11) UNSIGNED;
    DECLARE v_total INT(11) UNSIGNED;

    DECLARE user_id_cursor CURSOR FOR SELECT DISTINCT(`user_activities`.`user_id`) FROM `user_activities` WHERE `user_activities`.`activity_id`=OLD.`id`;
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET v_finished = 1;

    OPEN user_id_cursor;

    get_user_ids: LOOP
      FETCH user_id_cursor INTO v_userId;
      IF v_finished = 1 THEN
        LEAVE get_user_ids;
      END IF;
      -- recalculate and store scores
      SET v_total = (SELECT SUM(`activities`.`points`)
                     FROM `user_activities`
                       INNER JOIN `activities` ON `activities`.`id`=`user_activities`.`activity_id`
                     WHERE `user_activities`.`user_id` = v_userId
                     AND  `user_activities`.`activity_id` != OLD.`id`);
      UPDATE `users`
      SET `total_activity_points` = v_total
      WHERE `users`.`id` = v_userId;
    END LOOP get_user_ids;

    CLOSE user_id_cursor;
  END;
//
DELIMITER ;

当点值更改或删除活动时,活动表上的触发器将更新总数。删除前触发器是必需的,因为MySQL不会在级联删除上运行删除触发器。