简体   繁体   English

同时更新mysql MyISAM表中的计数器

[英]Concurrently update counter in a mysql MyISAM table

I have a client-server application that send user data to the cloud (Amazon EC2 + RDS + S3). 我有一个客户端服务器应用程序,可将用户数据发送到云(Amazon EC2 + RDS + S3)。

  1. Every user can have multiple devices connecting to the cloud & sending data at the same time 每个用户可以有多个设备连接到云并同时发送数据
  2. Client application installed on each device is multi-threaded and eventually upload multiple data snippets at the same time. 安装在每个设备上的客户端应用程序是多线程的,最终会同时上传多个数据片段。

I'd like to reliably track disk usage used in this context and I wonder how to do this in this context? 我想可靠地跟踪在这种情况下使用的磁盘使用情况,我想知道如何在这种情况下执行此操作?

I have two ideas so far, but I'm not even sure they are correct: 到目前为止,我有两个想法,但我什至不确定它们是正确的:

Option 1: Add a trigger to mysql table? 选项1:向mysql表添加触发器? ie. 即。

CREATE TRIGGER DiskUsage AFTER UPDATE OF Fully_Updated_File_Flag ON Files
BEGIN
    for each row
    begin
        UPDATE Users SET SpaceUsed = SpaceUsed + new.Size WHERE (new.Fully_Updated_File_Flag = 1) And UserID=
    end
END;

If I opt to use triggers, how am I supposed to dynamically inject the user id? 如果我选择使用触发器,应该如何动态注入用户ID?


Option 2: Update mysql table via PHP? 选项2:通过PHP更新mysql表? ie. 即。

<?php

  SendFileToS3($file_name);
  mysql_query('UPDATE Stats SET Value = Value + ' . filesize($file_name) . ' WHERE user_id=' . $user_id);

?>

What if two instances are trying to update the same record? 如果两个实例试图更新同一记录怎么办? (I'm using Mysql 5.5.27-log / MyISAM), would this still work. (我正在使用Mysql 5.5.27-log / MyISAM),这是否仍然有效。


Note #1 Although I didn't yet release my application, I still need something that scales well. 注意#1尽管我尚未发布应用程序,但仍需要可扩展的功能。 Even if it means changing db engine all together. 即使这意味着一起更改数据库引擎。

Note #2 DB-related code is encapsulated in modular functions (ie. InsertIntoDB(), UpdateDB() & DeleteFromDB()). 注意#2与DB相关的代码封装在模块化函数(即InsertIntoDB(),UpdateDB()和DeleteFromDB())中。 Plus all of these routines relies on CodeIgniter 2.1 with active record class. 另外,所有这些例程都依赖于具有活动记录类的CodeIgniter 2.1。

This is to say that I could always make the switch if I have to (although I'd like to avoid that) 这就是说, 如果需要的话,我总是可以进行切换(尽管我想避免这种情况)

If you're tracking files, size & ownership, doing a SELECT SUM(Size) FROM Files WHERE UserID = ? 如果要跟踪文件,大小和所有权,请SELECT SUM(Size) FROM Files WHERE UserID = ? would be blazingly fast on a properly indexed table unless users have a brazillion files associated with them. 除非用户拥有与其相关联的不计其数的文件,否则它将在索引正确的表上快速增长。 No need to store a number you can calculate that easily. 无需存储数字即可轻松计算出该数字。

You should use MySQL Triggers instead of PHP code and you have to store the related user_id into diskusage table. 您应该使用MySQL触发器而不是PHP代码,并且必须将相关的user_id存储到diskusage表中。

I use InnoDB engine because of the CONSTRAINT . 由于使用CONSTRAINT我使用InnoDB引擎。 You can also use MyISAM , but you should remove the CONSTRAINT . 您也可以使用MyISAM ,但应删除CONSTRAINT

REMARK 备注

I would use InnoDB because of Transactions and (more important here) Row-Locking . 由于事务和(这里更重要的是) 行锁定,我将使用InnoDB

Table Structure (InnoDB) 表结构(InnoDB)

-- ----------------------------
--  Table structure for `users`
-- ----------------------------
DROP TABLE IF EXISTS `users`;
CREATE TABLE `users` (
  `id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
  `Name` VARCHAR(10) NOT NULL DEFAULT '',
  `SpaceUsed` BIGINT(20) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;

-- ----------------------------
--  Table structure for `diskusage`
-- ----------------------------
DROP TABLE IF EXISTS `diskusage`;
CREATE TABLE `diskusage` (
  `id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
  `Filename` VARCHAR(50) NOT NULL DEFAULT '',
  `Size` BIGINT(20) NOT NULL,
  `user_id` INT(11) UNSIGNED DEFAULT NULL,
  `Fully_Updated_File_Flag` TINYINT(4) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `fk_diskusage_user` (`user_id`),
  CONSTRAINT `fk_diskusage_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE SET NULL ON UPDATE CASCADE
) ENGINE=INNODB DEFAULT CHARSET=utf8;

Table Structure (MyISAM) 表结构(MyISAM)

-- ----------------------------
--  Table structure for `users`
-- ----------------------------
DROP TABLE IF EXISTS `users`;
CREATE TABLE `users` (
  `id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
  `Name` VARCHAR(10) NOT NULL DEFAULT '',
  `SpaceUsed` BIGINT(20) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

-- ----------------------------
--  Table structure for `diskusage`
-- ----------------------------
DROP TABLE IF EXISTS `diskusage`;
CREATE TABLE `diskusage` (
  `id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
  `Filename` VARCHAR(50) NOT NULL DEFAULT '',
  `Size` BIGINT(20) NOT NULL,
  `user_id` INT(11) UNSIGNED DEFAULT NULL,
  `Fully_Updated_File_Flag` TINYINT(4) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `fk_diskusage_user` (`user_id`),
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

Thats all, together with some triggers on table diskusage . diskusage ,还有表diskusage上的一些触发器。

INSERT TRIGGER 插入触发

-- ----------------------------
--  AFTER INSERT TRIGGER for `diskusage`
-- ----------------------------
delimiter ;;
CREATE TRIGGER `diskusage_after_insert` AFTER INSERT ON `diskusage` FOR EACH ROW BEGIN
  IF NEW.Fully_Updated_File_Flag = 1 THEN
    UPDATE users
    SET
      SpaceUsed = SpaceUsed + NEW.Size
    WHERE
      id = NEW.user_id;
  END IF;
END;
 ;;
delimiter ;

UPDATE TRIGGER 更新触发

-- ----------------------------
--  AFTER UPDATE TRIGGER for `diskusage`
-- ----------------------------
delimiter ;;
CREATE TRIGGER `diskusage_after_update` AFTER UPDATE ON `diskusage` FOR EACH ROW BEGIN

  -- same to DELETE TRIGGER

  -- decrease SpaceUsed with OLD Size for OLD user

  IF OLD.Fully_Updated_File_Flag = 1 THEN
    UPDATE users
    SET
      SpaceUsed = SpaceUsed - OLD.Size
    WHERE
      id = OLD.user_id;
  END IF;

  -- same to INSERT TRIGGER

  -- increase SpaceUsed with NEW Size for NEW user

  IF NEW.Fully_Updated_File_Flag = 1 THEN
    UPDATE users
    SET
      SpaceUsed = SpaceUsed + NEW.Size
    WHERE
      id = NEW.user_id;
  END IF;

END;
 ;;
delimiter ;

DELETE TRIGGER 删除触发器

-- ----------------------------
--  AFTER DELETE TRIGGER for `diskusage`
-- ----------------------------
delimiter ;;
CREATE TRIGGER `diskusage_after_delete` AFTER DELETE ON `diskusage` FOR EACH ROW BEGIN

  IF OLD.Fully_Updated_File_Flag = 1 THEN
    UPDATE users
    SET
      SpaceUsed = SpaceUsed - OLD.Size
    WHERE
      id = OLD.user_id;
  END IF;

END;
 ;;
delimiter ;

Operations for MyISAM tables are atomic . MyISAM表的操作是原子的 Basically, the queries are automatically committed after they happen. 基本上,查询是在查询发生后自动提交的。

In addition, UPDATE s are blocking, meaning that only one can occur at a time. 另外, UPDATE处于阻塞状态,这意味着一次只能发生一次。

This means that the read-write cycle of the UPDATE will not be interrupted. 这意味着UPDATE的读写周期不会被中断。

MySQL uses table locking for MyISAM tables. MySQL对MyISAM表使用表锁定

This is fairly quick and will ensure that concurrent updates work correctly. 这相当快,可以确保并发更新正常工作。

However, lots of updates may result in the table spending a lot of time being locked. 但是,大量更新可能导致表花费大量时间被锁定。 If you have many rows in your table, this may become problematic. 如果表中有很多行,则可能会出现问题。

InnoDB tables support row-locking. InnoDB表支持行锁定。 This takes more resources, but may be much more appropriate if your table gets large. 这会占用更多资源,但如果表很大,可能会更合适。 It gives you finer control over the locking and would allow multiple, unrelated, processes to access the table, without excessive lock contention. 它使您可以更好地控制锁定,并允许多个不相关的进程访问表,而不会产生过多的锁定争用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM