在C#中的多个表中有效地将数据插入MySQL

fub*_*ubo 15 c# mysql insert

我需要在mySQL数据库中将一个巨大的CSV文件插入到2个具有1:n关系的表中.

CSV文件每周发送一次,大约1GB,需要附加到现有数据.其中每个表都有一个自动增量主键.

我试过了:

  • 实体框架(占用所有方法的大部分时间)
  • 数据集(相同)
  • 批量上传(不支持多个表)
  • 带参数的MySqlCommand(需要嵌套,我当前的方法)
  • 带有StoredProcedure的MySqlCommand包括一个Transaction

还有什么建议?

让我们说简化这是我的数据结构:

public class User
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public List<string> Codes { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

我需要从csv插入到这个数据库中:

       User   (1-n)   Code     
+---+-----+-----+ +---+---+-----+        
|PID|FName|LName| |CID|PID|Code | 
+---+-----+-----+ +---+---+-----+
| 1 |Jon  | Foo | | 1 | 1 | ed3 | 
| 2 |Max  | Foo | | 2 | 1 | wst | 
| 3 |Paul | Foo | | 3 | 2 | xsd | 
+---+-----+-----+ +---+---+-----+ 
Run Code Online (Sandbox Code Playgroud)

这是CSV文件的示例行

Jon;Foo;ed3,wst
Run Code Online (Sandbox Code Playgroud)

LOAD DATA LOCAL INFILE由于我的写作权限受到限制,因此无法进行批量加载

Byy*_*yyo 6

参考你的答案我会替换

using (MySqlCommand myCmdNested = new MySqlCommand(cCommand, mConnection))
{
    foreach (string Code in item.Codes)
    {
        myCmdNested.Parameters.Add(new MySqlParameter("@UserID", UID));
        myCmdNested.Parameters.Add(new MySqlParameter("@Code", Code));
        myCmdNested.ExecuteNonQuery();
    }
}
Run Code Online (Sandbox Code Playgroud)

List<string> lCodes = new List<string>();
foreach (string code in item.Codes)
{
    lCodes.Add(String.Format("('{0}','{1}')", UID, MySqlHelper.EscapeString(code)));
}
string cCommand = "INSERT INTO Code (UserID, Code) VALUES " + string.Join(",", lCodes);
using (MySqlCommand myCmdNested = new MySqlCommand(cCommand, mConnection))
{
    myCmdNested.ExecuteNonQuery();
}
Run Code Online (Sandbox Code Playgroud)

生成一个插入语句而不是 item.Count

  • @fubo:这个答案描述了`InsertInto`方法的样子. (2认同)

Tas*_* K. 3

考虑到数据量很大,最好的方法(性能方面)是将尽可能多的数据处理留给数据库而不是应用程序。

创建一个临时表,用于临时保存 .csv 文件中的数据。

CREATE TABLE `imported` (
    `id` int(11) NOT NULL,
    `firstname` varchar(45) DEFAULT NULL,
    `lastname` varchar(45) DEFAULT NULL,
    `codes` varchar(450) DEFAULT NULL,
    PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Run Code Online (Sandbox Code Playgroud)

将数据从表加载.csv到该表中非常简单。我建议使用MySqlCommand(这也是您当前的方法)。此外,对所有语句使用相同的对象 将减少总执行时间MySqlConnectionINSERT

然后,为了进一步处理数据,您可以创建一个存储过程来处理它。

假设这两个表(取自您的简化示例):

CREATE TABLE `users` (
  `PID` int(11) NOT NULL AUTO_INCREMENT,
  `FName` varchar(45) DEFAULT NULL,
  `LName` varchar(45) DEFAULT NULL,
  PRIMARY KEY (`PID`)
) ENGINE=InnoDB AUTO_INCREMENT=3737 DEFAULT CHARSET=utf8;
Run Code Online (Sandbox Code Playgroud)

CREATE TABLE `codes` (
  `CID` int(11) NOT NULL AUTO_INCREMENT,
  `PID` int(11) DEFAULT NULL,
  `code` varchar(45) DEFAULT NULL,
  PRIMARY KEY (`CID`)
) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8;
Run Code Online (Sandbox Code Playgroud)

您可以拥有以下存储过程。

CREATE DEFINER=`root`@`localhost` PROCEDURE `import_data`()
BEGIN
    DECLARE fname VARCHAR(255);
    DECLARE lname VARCHAR(255);
    DECLARE codesstr VARCHAR(255);
    DECLARE splitted_value VARCHAR(255);
    DECLARE done INT DEFAULT 0;
    DECLARE newid INT DEFAULT 0;
    DECLARE occurance INT DEFAULT 0;
    DECLARE i INT DEFAULT 0;

    DECLARE cur CURSOR FOR SELECT firstname,lastname,codes FROM imported;
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;

    OPEN cur;

    import_loop: 
        LOOP FETCH cur INTO fname, lname, codesstr;
            IF done = 1 THEN
                LEAVE import_loop;
            END IF;

            INSERT INTO users (FName,LName) VALUES (fname, lname);
            SET newid = LAST_INSERT_ID();

            SET i=1;
            SET occurance = (SELECT LENGTH(codesstr) - LENGTH(REPLACE(codesstr, ',', '')) + 1);

            WHILE i <= occurance DO
                SET splitted_value =
                    (SELECT REPLACE(SUBSTRING(SUBSTRING_INDEX(codesstr, ',', i),
                    LENGTH(SUBSTRING_INDEX(codesstr, ',', i - 1)) + 1), ',', ''));

                INSERT INTO codes (PID, code) VALUES (newid, splitted_value);
                SET i = i + 1;
            END WHILE;
        END LOOP;
    CLOSE cur;
END
Run Code Online (Sandbox Code Playgroud)

对于源数据中的每一行,它都会INSERTuser表创建一个语句。然后有一个WHILE循环来分割逗号分隔的代码,并为每个代码创建一个INSERT语句codes

关于 的使用LAST_INSERT_ID(),它在每个连接的基础上都是可靠的(请参阅此处的文档)。如果用于运行此存储过程的MySQL连接没有被其他事务使用,则使用LAST_INSERT_ID()是安全的。

生成的 ID 按每个连接保存在服务器中。这意味着该函数返回给给定客户端的值是为该客户端影响 AUTO_INCRMENT 列的最新语句生成的第一个 AUTO_INCRMENT 值。该值不会受到其他客户端的影响,即使它们生成自己的 AUTO_INCRMENT 值。此行为确保每个客户端都可以检索自己的 ID,而无需关心其他客户端的活动,也不需要锁或事务。

编辑:这是省略了 temp-table 的 OP 变体imported。您无需将 .csv 中的数据插入到表中imported,而是调用 SP 将它们直接存储到数据库中。

CREATE DEFINER=`root`@`localhost` PROCEDURE `import_data`(IN fname VARCHAR(255), IN lname VARCHAR(255),IN codesstr VARCHAR(255))
BEGIN
    DECLARE splitted_value VARCHAR(255);
    DECLARE done INT DEFAULT 0;
    DECLARE newid INT DEFAULT 0;
    DECLARE occurance INT DEFAULT 0;
    DECLARE i INT DEFAULT 0;

    INSERT INTO users (FName,LName) VALUES (fname, lname);
    SET newid = LAST_INSERT_ID();

    SET i=1;
    SET occurance = (SELECT LENGTH(codesstr) - LENGTH(REPLACE(codesstr, ',', '')) + 1);

    WHILE i <= occurance DO
        SET splitted_value =
            (SELECT REPLACE(SUBSTRING(SUBSTRING_INDEX(codesstr, ',', i),
            LENGTH(SUBSTRING_INDEX(codesstr, ',', i - 1)) + 1), ',', ''));

        INSERT INTO codes (PID, code) VALUES (newid, splitted_value);
        SET i = i + 1;
    END WHILE;
END
Run Code Online (Sandbox Code Playgroud)

注意:分割代码的代码取自这里(MySQL不提供字符串分割功能)。