Pokazywanie postów oznaczonych etykietą PostgreSql. Pokaż wszystkie posty
Pokazywanie postów oznaczonych etykietą PostgreSql. Pokaż wszystkie posty

środa, 12 września 2012

Dodawanie dużej ilości rekordów do bazy PostgreSql

Zadaniem jest wstawienie 80 tys. rekordów do przykładowej tabeli której strukturę podano poniżej:

CREATE TABLE Table2(
  
Id serial NOT NULL,
  
Name1 CHARacter VARYING(60),
  
Name3 CHARacter VARYING(60),
  
Name2 CHARacter VARYING(60),
  
Name4 CHARacter VARYING(60),
  
CONSTRAINT "Table2_pkey"PRIMARY KEY (Id)
);


Kod tworzący dane testowe:


Code:
    public class Table1
    {
        public int Id { get; set; }
        public string Name1 { get; set; }
        public string Name2 { get; set; }
        public string Name3 { get; set; }
        public string Name4 { get; set; }

        public Table1(string name1, string name2, string name3, string name4)
        {
            Name1 = name1;
            Name2 = name2;
            Name3 = name3;
            Name4 = name4;
        }
    }



Code:
    public class Repository
    {
         public static List<Table1> GetData(int rowCount)
         {
             var data = new List<Table1>();
             for (int i = 0; i < rowCount; i++)
             {
                 data.Add(new Table1(Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), Guid.NewGuid().ToString(),
                                     Guid.NewGuid().ToString()));
             }

             return data;
         }
    }


Podam tutaj 3 znane mi sposoby, które można użyć w tym celu.

1. Zwykłe inserty:
Standardowy sposób dodawania rekordów, który sprawdza się w przypadku dodania pojedynczego rekordu  do bazy, jednak w przypadku ich dużej ilości powoduje zamulenie całego systemu (co później zobaczymy w wynikach):


Code:
        private static long InsertAsSingleRows(List<Table1> data, NpgsqlConnection connection)
        {
            var stopwatch = new Stopwatch();
            stopwatch.Start();
            using (var cmd = connection.CreateCommand())
            {
                cmd.Parameters.AddWithValue(Name1, "");
                cmd.Parameters.AddWithValue(Name2, "");
                cmd.Parameters.AddWithValue(Name3, "");
                cmd.Parameters.AddWithValue(Name4, "");
                cmd.CommandText = "INSERT INTO Table2(Name1,Name2,Name3,name4) VALUES(@Name1,@Name2,@Name3,@Name4)";
                for (int i = 0; i < data.Count; i++)
                {
                    cmd.Parameters[Name1].Value = data[i].Name1;
                    cmd.Parameters[Name2].Value = data[i].Name2;
                    cmd.Parameters[Name3].Value = data[i].Name3;
                    cmd.Parameters[Name4].Value = data[i].Name4;
                    cmd.ExecuteNonQuery();
                }
            }
            stopwatch.Stop();

            return stopwatch.ElapsedMilliseconds;
        }


2. Transakcja
W przypadku dużej ilości dodawanych rekordów lepiej skorzystać z transakcji, którą na końcu zatwierdzamy:


Code:
        private static long InsertAsTransaction(List<Table1> data, NpgsqlConnection connection)
        {
            var stopwatch = new Stopwatch();
            stopwatch.Start();
            using (var cmd = connection.CreateCommand())
            {
                cmd.Transaction = connection.BeginTransaction();
                cmd.Parameters.AddWithValue(Name1, "");
                cmd.Parameters.AddWithValue(Name2, "");
                cmd.Parameters.AddWithValue(Name3, "");
                cmd.Parameters.AddWithValue(Name4, "");
                cmd.CommandText = "INSERT INTO Table2(Name1,Name2,Name3,name4) VALUES(@Name1,@Name2,@Name3,@Name4)";
                for (int i = 0; i < data.Count; i++)
                {
                    cmd.Parameters[Name1].Value = data[i].Name1;
                    cmd.Parameters[Name2].Value = data[i].Name2;
                    cmd.Parameters[Name3].Value = data[i].Name3;
                    cmd.Parameters[Name4].Value = data[i].Name4;
                    cmd.ExecuteNonQuery();
                }
                cmd.Transaction.Commit();
            }
            stopwatch.Stop();

            return stopwatch.ElapsedMilliseconds;
        }


3. Insert batchowy
Mechanizm ten służy przede wszystkim ładowaniu bardzo dużej ilości rekordów z plików. Jego wydajność jest nieporównywalnie większa niż przedstawionych dwóch poprzednich:


Code:
        private static long InsertAsBatch(List<Table1> data, NpgsqlConnection connection)
        {
            var stopwatch = new Stopwatch();
            stopwatch.Start();
            using (var cmd = connection.CreateCommand())
            {
                var dataAsFile = new StringBuilder();
                for (int i = 0; i < data.Count; i++)
                {
                    dataAsFile.AppendFormat("{0}\t{1}\t{2}\t{3}\n", data[i].Name1, data[i].Name2, data[i].Name3,
                                            data[i].Name4);
                }
                var memoryStream = new MemoryStream(new UTF8Encoding().GetBytes(dataAsFile.ToString()));
                cmd.CommandText = "COPY Table2(Name1,Name2,Name3,name4) FROM STDIN";
                var npgsqlCopyIn = new NpgsqlCopyIn(cmd, connection, memoryStream);
                npgsqlCopyIn.Start();

                stopwatch.Stop();

                return stopwatch.ElapsedMilliseconds;
            }
        }



Czas na wyniki pomiarów czasu:


bardzo dobrze widać to na wykresie:


Podsumowując:
Jeżeli wstawiamy bardzo dużą ilość rekordów warto skorzystać z transakcji. Jeżeli dysponujemy ogromnymi zbiorami danych które chcemy umieścić w bazie możemy skorzystać z inserta batchowego.