我已经阅读了很多关于此的问题,但我找不到足够快的问题。我认为有更好的方法可以将大量行插入MySQL数据库
我使用以下代码将100k插入我的MySQL数据库:
public static void CSVToMySQL()
{
string ConnectionString = "server=192.168.1xxx";
string Command = "INSERT INTO User (FirstName, LastName ) VALUES (@FirstName, @LastName);";
using (MySqlConnection mConnection = new MySqlConnection(ConnectionString))
{
mConnection.Open();
for(int i =0;i< 100000;i++) //inserting 100k items
using (MySqlCommand myCmd = new MySqlCommand(Command, mConnection))
{
myCmd.CommandType = CommandType.Text;
myCmd.Parameters.AddWithValue("@FirstName", "test");
myCmd.Parameters.AddWithValue("@LastName", "test");
myCmd.ExecuteNonQuery();
}
}
}
这需要100k行约40秒。我怎样才能更快或更高效?
通过DataTable / DataAdapter或一次插入多行可能会更快:
INSERT INTO User (Fn, Ln) VALUES (@Fn1, @Ln1), (@Fn2, @Ln2)...
由于安全问题,我无法将数据加载到文件中而MySQLBulk会加载它。
答案 0 :(得分:46)
这是我的&#34;多个插入&#34; -code。
插入100k行而不是40秒仅 3秒 !!
public static void BulkToMySQL()
{
string ConnectionString = "server=192.168.1xxx";
StringBuilder sCommand = new StringBuilder("INSERT INTO User (FirstName, LastName) VALUES ");
using (MySqlConnection mConnection = new MySqlConnection(ConnectionString))
{
List<string> Rows = new List<string>();
for (int i = 0; i < 100000; i++)
{
Rows.Add(string.Format("('{0}','{1}')", MySqlHelper.EscapeString("test"), MySqlHelper.EscapeString("test")));
}
sCommand.Append(string.Join(",", Rows));
sCommand.Append(";");
mConnection.Open();
using (MySqlCommand myCmd = new MySqlCommand(sCommand.ToString(), mConnection))
{
myCmd.CommandType = CommandType.Text;
myCmd.ExecuteNonQuery();
}
}
}
创建的SQL语句如下所示:
INSERT INTO User (FirstName, LastName) VALUES ('test','test'),('test','test'),... ;
更新:感谢 Salman A 我添加了MySQLHelper.EscapeString
以避免在使用参数时内部使用的代码注入。
答案 1 :(得分:10)
我使用MySqlDataAdapter,transactions和UpdateBatchSize这三个东西做了一个小测试。它比你的第一个例子快约30倍。 Mysql在单独的盒子上运行,因此涉及延迟。 batchsize可能需要一些调整。代码如下:
string ConnectionString = "server=xxx;Uid=xxx;Pwd=xxx;Database=xxx";
string Command = "INSERT INTO User2 (FirstName, LastName ) VALUES (@FirstName, @LastName);";
using (var mConnection = new MySqlConnection(ConnectionString))
{
mConnection.Open();
MySqlTransaction transaction = mConnection.BeginTransaction();
//Obtain a dataset, obviously a "select *" is not the best way...
var mySqlDataAdapterSelect = new MySqlDataAdapter("select * from User2", mConnection);
var ds = new DataSet();
mySqlDataAdapterSelect.Fill(ds, "User2");
var mySqlDataAdapter = new MySqlDataAdapter();
mySqlDataAdapter.InsertCommand = new MySqlCommand(Command, mConnection);
mySqlDataAdapter.InsertCommand.Parameters.Add("@FirstName", MySqlDbType.VarChar, 32, "FirstName");
mySqlDataAdapter.InsertCommand.Parameters.Add("@LastName", MySqlDbType.VarChar, 32, "LastName");
mySqlDataAdapter.InsertCommand.UpdatedRowSource = UpdateRowSource.None;
var stopwatch = new Stopwatch();
stopwatch.Start();
for (int i = 0; i < 50000; i++)
{
DataRow row = ds.Tables["User2"].NewRow();
row["FirstName"] = "1234";
row["LastName"] = "1234";
ds.Tables["User2"].Rows.Add(row);
}
mySqlDataAdapter.UpdateBatchSize = 100;
mySqlDataAdapter.Update(ds, "User2");
transaction.Commit();
stopwatch.Stop();
Debug.WriteLine(" inserts took " + stopwatch.ElapsedMilliseconds + "ms");
}
}
答案 2 :(得分:10)
在Transaction
中执行命令,并为每次迭代重用相同的命令实例。要进一步优化性能,请在一个命令中发送100个查询。进行并行执行可以提供更好的性能(Parallel.For
),但要确保每个并行循环都有自己的MySqlCommand
实例。
public static void CSVToMySQL()
{
string ConnectionString = "server=192.168.1xxx";
string Command = "INSERT INTO User (FirstName, LastName ) VALUES (@FirstName, @LastName);";
using (MySqlConnection mConnection = new MySqlConnection(ConnectionString))
{
mConnection.Open();
using (MySqlTransaction trans = mConnection.BeginTransaction())
{
using (MySqlCommand myCmd = new MySqlCommand(Command, mConnection, trans))
{
myCmd.CommandType = CommandType.Text;
for (int i = 0; i <= 99999; i++)
{
//inserting 100k items
myCmd.Parameters.Clear();
myCmd.Parameters.AddWithValue("@FirstName", "test");
myCmd.Parameters.AddWithValue("@LastName", "test");
myCmd.ExecuteNonQuery();
}
trans.Commit();
}
}
}
}
答案 3 :(得分:7)
如果public function devUserCreateTest()
{
DB::beginTransaction();
DB::connection('some_connection')->beginTransaction(); // same as the one used in model ChildUser
try {
$childUser = new ChildUser; // Exists in database B
$parentUser = new User; // Exists in database A
$parentUser->setEmailAttribute('mike@example.com');
$parentUser->save();
$childUser->parent_user_id = $parentUser->id;
$message = sprintf('Parent user id: %s', serialize($childUser->id));
$childUser->save();
$message = sprintf('Core user id: %s | hta user id: %s', serialize($parentUser->id), serialize($childUser->id));
throw new Exception('Testing....');
DB::commit();
} catch (Exception $e) {
Log::warning(sprintf('Exception: %s', $e->getMessage()));
DB::rollback();
DB::connection('some_connection')->rollback();
}
return $this->buildResponse(array('message' => $message));
}
的{{1}}没有转义字符串,则必须提前执行此操作以避免SQL注入和语法错误。
一次只有1000行构建Add
语句。这应该比你开始时的速度快10倍(每AddWithValue
行1行)。一次完成所有100K是有风险的,可能更慢。冒险,因为你可能会吹出一些限制(数据包大小等);因为需要巨大的INSERT
日志而变慢。每批次后INSERT
,或使用ROLLBACK
。
答案 4 :(得分:5)
加速的一种方法是将所有插入包装到一个事务(SQL-Server代码)中:
using (SqlConnection connection = new SqlConnection(CloudConfigurationManager.GetSetting("Sql.ConnectionString")))
{
conn.Open();
SqlTransaction transaction = conn.BeginTransaction();
try
{
foreach (string commandString in dbOperations)
{
SqlCommand cmd = new SqlCommand(commandString, conn, transaction);
cmd.ExecuteNonQuery();
}
transaction.Commit();
} // Here the execution is committed to the DB
catch (Exception)
{
transaction.Rollback();
throw;
}
conn.Close();
}
另一种方法是将CSV文件加载到数据表中,并使用DataAdapter的批处理功能
DataTable dtInsertRows = GetDataTable();
SqlConnection connection = new SqlConnection(connectionString);
SqlCommand command = new SqlCommand("sp_BatchInsert", connection);
command.CommandType = CommandType.StoredProcedure;
command.UpdatedRowSource = UpdateRowSource.None;
// Set the Parameter with appropriate Source Column Name
command.Parameters.Add("@PersonId", SqlDbType.Int, 4, dtInsertRows.Columns[0].ColumnName);
command.Parameters.Add("@PersonName", SqlDbType.VarChar, 100, dtInsertRows.Columns[1].ColumnName);
SqlDataAdapter adpt = new SqlDataAdapter();
adpt.InsertCommand = command;
// Specify the number of records to be Inserted/Updated in one go. Default is 1.
adpt.UpdateBatchSize = 2;
connection.Open();
int recordsInserted = adpt.Update(dtInsertRows);
connection.Close();
你找到一个很好的例子here。
或者您可以使用MySQL BulkLoader C#类:
var bl = new MySqlBulkLoader(connection);
bl.TableName = "mytable";
bl.FieldTerminator = ",";
bl.LineTerminator = "\r\n";
bl.FileName = "myfileformytable.csv";
bl.NumberOfLinesToSkip = 1;
var inserted = bl.Load();
Debug.Print(inserted + " rows inserted.");
如果在一个命令中执行多次插入,则可能仍然使用StringBuilder而不是string来挤出一两英寸。
答案 5 :(得分:5)
这种方式可能不比stringbuilder方法快,但它是参数化的:
/// <summary>
/// Bulk insert some data, uses parameters
/// </summary>
/// <param name="table">The Table Name</param>
/// <param name="inserts">Holds list of data to insert</param>
/// <param name="batchSize">executes the insert after batch lines</param>
/// <param name="progress">Progress reporting</param>
public void BulkInsert(string table, MySQLBulkInsertData inserts, int batchSize = 100, IProgress<double> progress = null)
{
if (inserts.Count <= 0) throw new ArgumentException("Nothing to Insert");
string insertcmd = string.Format("INSERT INTO `{0}` ({1}) VALUES ", table,
inserts.Fields.Select(p => p.FieldName).ToCSV());
StringBuilder sb = new StringBuilder();
using (MySqlConnection conn = new MySqlConnection(ConnectionString))
using (MySqlCommand sqlExecCommand = conn.CreateCommand())
{
conn.Open();
sb.AppendLine(insertcmd);
for (int i = 0; i < inserts.Count; i++)
{
sb.AppendLine(ToParameterCSV(inserts.Fields, i));
for (int j = 0; j < inserts[i].Count(); j++)
{
sqlExecCommand.Parameters.AddWithValue(string.Format("{0}{1}",inserts.Fields[j].FieldName,i), inserts[i][j]);
}
//commit if we are on the batch sizeor the last item
if (i > 0 && (i%batchSize == 0 || i == inserts.Count - 1))
{
sb.Append(";");
sqlExecCommand.CommandText = sb.ToString();
sqlExecCommand.ExecuteNonQuery();
//reset the stringBuilder
sb.Clear();
sb.AppendLine(insertcmd);
if (progress != null)
{
progress.Report((double)i/inserts.Count);
}
}
else
{
sb.Append(",");
}
}
}
}
这使用如下的帮助程序类:
/// <summary>
/// Helper class to builk insert data into a table
/// </summary>
public struct MySQLFieldDefinition
{
public MySQLFieldDefinition(string field, MySqlDbType type) : this()
{
FieldName = field;
ParameterType = type;
}
public string FieldName { get; private set; }
public MySqlDbType ParameterType { get; private set; }
}
///
///You need to ensure the fieldnames are in the same order as the object[] array
///
public class MySQLBulkInsertData : List<object[]>
{
public MySQLBulkInsertData(params MySQLFieldDefinition[] fieldnames)
{
Fields = fieldnames;
}
public MySQLFieldDefinition[] Fields { get; private set; }
}
这个辅助方法:
/// <summary>
/// Return a CSV string of the values in the list
/// </summary>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
private string ToParameterCSV(IEnumerable<MySQLFieldDefinition> p, int row)
{
string csv = p.Aggregate(string.Empty,
(current, i) => string.IsNullOrEmpty(current)
? string.Format("@{0}{1}",i.FieldName, row)
: string.Format("{0},@{2}{1}", current, row, i.FieldName));
return string.Format("({0})", csv);
}
也许不是超级优雅,但效果很好。我需要进度跟踪,以便随身携带,随时删除该部分。
这将生成类似于所需输出的SQL命令。
编辑:ToCSV:
/// <summary>
/// Return a CSV string of the values in the list
/// </summary>
/// <param name="intValues"></param>
/// <param name="separator"></param>
/// <param name="encloser"></param>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
public static string ToCSV<T>(this IEnumerable<T> intValues, string separator = ",", string encloser = "")
{
string result = String.Empty;
foreach (T value in intValues)
{
result = String.IsNullOrEmpty(result)
? string.Format("{1}{0}{1}", value, encloser)
: String.Format("{0}{1}{3}{2}{3}", result, separator, value, encloser);
}
return result;
}
答案 6 :(得分:4)
正如Stefan Steiger所说,Bulk Insert适合您的情况。
另一个技巧是使用登台表,因此您不会直接写入生产表,而是写入临时表(具有相同的结构)。 写完所有信息后,你只需交换表格。 使用staging aproach,您将避免锁定表以进行插入(也可用于更新和删除),并且此模式在某些项目中大量用于MySQL。
此外,禁用表键可能会加快插入速度,但在启用它们时也会引入一些问题(仅适用于MyISAM引擎)。
<强>加强>:
我们假设你有表Products
:
对于暂存目的,您可以使用相同的列集创建名为ProductsStaging
的临时表。
您在临时桌上执行的所有操作:
UpdateStagingTable();
SwapTables();
UpdateStagingTable();
因为在交换后您的临时表没有新数据,您再次调用相同的方法。
在SwapTables()
方法中,您执行一个SQL语句:
RENAME TABLE Products TO ProductsTemp,
ProductsStaging TO Products,
ProductsTemp TO ProductsStagin;
数据操作的速度取决于MySql引擎(例如InnoDB,MyISAM等),因此您还可以通过更改引擎来加速插入。
答案 7 :(得分:2)
我在使用EF-MySQL时偶然发现了类似的问题。 EF插入太慢了,因此使用fubo提到的方法。首先,性能大幅提升(~10K记录在~10秒内插入)但随着表的大小增加而降低,表中有大约1M条记录,插入时间约为250秒。
终于找到了问题!表的PK是GUID类型(UUID - 炭(36))。由于UUID无法按顺序编制索引,并且每个插入都需要重建索引,因此速度会降低。
修复是用bigint(或int)替换PK并将其设置为标识列。这提高了性能,插入平均需要约12秒,表中有~2M +记录!
我想在这里分享这个发现以防万一有人遇到类似的问题!
答案 8 :(得分:1)
我的建议是一个想法,而不是示例或解决方案。如果您不使用INSERT但将数据作为多个参数传递(不必一次全部为100K,例如,您可以使用1K的捆绑包),则STORED PROCEDURE本身会执行INSERT,该怎么办?
答案 9 :(得分:0)
批量操作将是实现此目标的好方法。会先读取您的属性,然后为您创建一个批量查询...
有一个github存储库,其中包含两个有用的方法:使用MySql和EF6 +的BulkInsert和BulkUpdate。
BulkUpdate / BulkInsert基本上从通用实体读取所有属性,然后为您创建bulkquery。
Ps:此项目是根据我的需要开发的,并且该项目向有意改善或更改它的人开放,以寻求更好的解决方案,这对于社区来说是值得的。
Ps²:如果不能解决问题,请尝试对项目进行更改以改进并实现您想要的目标,至少这是一个好的开始。
请看看here
答案 10 :(得分:0)
我找到了避免将文件用于批量插入的方法。在this connector中是实现者从流中加载。 这样可以完成类似的加载
public void InsertData(string table, List<string> columns, List<List<object>> data) {
using (var con = OpenConnection() as MySqlConnection) {
var bulk = new MySqlBulkLoader(con);
using (var stream = new MemoryStream()) {
bulk.SourceStream = stream;
bulk.TableName = table;
bulk.FieldTerminator = ";";
var writer = new StreamWriter(stream);
foreach (var d in data)
writer.WriteLine(string.Join(";", d));
writer.Flush();
stream.Position = 0;
bulk.Load();
}
}
}