2012-06-11 3 views
1

В программе я создал следующую логику для чтения данных из базы данных и хранить его в список <>:Есть ли способ ускорить чтение данных?

   NpgsqlCommand cmd = new NpgsqlCommand(query, conn); 
       List<UserInfo> result = new List<UserInfo>(); 
       Npgsql.NpgsqlDataReader rdr = cmd.ExecuteReader(); 
       while (rdr.Read()) 
       { 
        string userId = rdr[0].ToString(); 
        string sex = rdr[1].ToString(); 
        string strDateBirth = rdr[2].ToString(); 
        string zip = rdr[3].ToString(); 

        UserInfo userInfo = new UserInfo(); 
        userInfo.Msisdn = userId; 
        userInfo.Gender = sex; 
        try 
        { 
         userInfo.BirthDate = Convert.ToDateTime(strDateBirth); 
        } 
        catch (Exception ex) 
        { 
        } 
        userInfo.ZipCode = zip; 
        userInfo.DemographicsKnown = true; 
        userInfo.AgeGroup = getAgeGroup(strDateBirth); 
        if (result.Count(x => x.Id== userId) == 0) 
         result.Add(userInfo); 
       } 

Выполнение этого кода действительно бедных. Есть более 2 млн записей, и через полчаса список userInfo содержит всего 300 000 записей.

Кто-нибудь знает, как ускорить чтение данных из базы данных?

+0

Я заметил, что вы приняли мой ответ. Каковы были результаты после изменения? – Matthew

+0

Вот копия из файла журнала: 20:48:00 Начиная с поиска данных из postgres ..., 11.6.2012 20:50:42 извлечено 50000 записей из postgres –

+0

Итак, 50 000 записей за 3 минуты ... хорошее улучшение по сравнению с предыдущим уровнем в 300 000 записей за 30 минут ... на 60% больше ... Это все еще кажется очень медленным! – Matthew

ответ

3

Вы используете .Count, когда вы на самом деле означает .Any()
Всякий раз, когда вы звоните .Count вы перечисляете всю коллекцию только чтобы увидеть, если у вас есть один матч ....

Рассмотрим вопрос, который вы» re ask:
«Сколько строк у вас соответствует этому условию? Это число равно нулю?»

Что вы на самом деле имеете в виду:
«Все ли строки соответствуют этому условию?»

В этом контексте вы можете создать Hashset значений userId. Проверка наличия в Hashset (или словаре) может быть намного быстрее, чем проверка того же в списке.

Кроме того, если у вас do уже есть userId, вы проанализировали и прочитали все значения без причины. Сначала проверьте на myHashset.Contains(userId), затем добавьте.

Это основная причина, по которой она медленная. Для n строк вы выполняете nth-треугольные перечисления коллекции!

EDIT: Рассмотрит эти непроверенные изменения: Я не знаю, если ваш читатель поддерживает набранные методы чтения, как GetString() так, если это не то просто использовать то, что вы были раньше.

NpgsqlCommand cmd = new NpgsqlCommand(query, conn); 
List<UserInfo> result = new List<UserInfo>(); 
Npgsql.NpgsqlDataReader rdr = cmd.ExecuteReader(); 
HashSet<string> userHash = new HashSet<string>(); // is this actually an int? 

while (rdr.Read()) 
{ 
    string userId = rdr.GetString(0); 
    If (!userHash.Contains(userId)) 
    { 
     string strDateBirth = rdrGetString(2); 
     UserInfo userInfo = new UserInfo(); 
     userInfo.Msisdn = userId; 
     userInfo.Gender = rdr.GetString(1); 
     datetime parseddate; // this is not used if the parse fails 
     if (Datetime.TryParse(strDateBirth, out parseddate)) 
     { 
      userInfo.BirthDate = parseddate; 
      // userInfo.AgeGroup = getAgeGroup(strDateBirth); // why take the string? 
      // rewrite your getAgeGroup method to take the datetime 
      userInfo.AgeGroup = getAgeGroup(parseddate); 
     } 
     userInfo.ZipCode = rdr.GetString(3); 
     userInfo.DemographicsKnown = true; 
     result.Add(userInfo); 
     userHash.Add(userId); 
    } 
} 

Это всегда будет держать первый экземпляр строки пользователя вы найдете (что ваш текущий код делает). Если вы хотите сохранить последний экземпляр, то вы можете использовать словарь и полностью исключить вызов .Contains().

EDIT: Я только заметил, что мой образец никогда не добавлял userId к хешу ... whoops ... добавил его туда.

+0

Хорошая точка на проверке хэша! –

+1

Я бы выделил часть 'HashSet' над частью' Any' vs 'Count'. В то время как 'Any' будет лучше, чем' Count', все равно O (n^2), если повторяющиеся пользователи будут достаточно редкими. Изменить. Другим решением может быть просто сделать запрос 'DISTINCT ON (userId)'. – FunctorSalad

+0

@FunctorSalad Я полностью согласен, я только поднял его, чтобы проиллюстрировать вопрос, который он задает в его «IEnumerable» ... кроме того, если есть * no * дублирующие пользователи, тогда '.Any' и' .Count' одинаковы. .. Я просто думаю, что ваш код должен отражать то, что вы имеете в виду. Он означает 'Any', а не' Count' – Matthew

2

Вся эта обработка исключений замедляет вашу программу LOT. Исключения составляют Исключительные случаи Если ваш код содержит более 10 экземпляров, вам нужно заново подумать о своем дизайне.

Вместо того чтобы бросать выполнение каждый раз, когда есть некорректная дата, используйте вместо этого DateTime.TryParse(string, DateTime). Это ускорит ваш код.

////Replace This 
//try 
//{ 
// userInfo.BirthDate = Convert.ToDateTime(strDateBirth); 
//} 
//catch (Exception ex) 
//{ 
//} 

//With this 
DateTime bithDate; 
if(DateTime.TryParse(strDateBirth, out bithDate) 
{ 
    userInfo.BirthDate = bithDate; 
} 

Кроме того, что тип данных столбца в rdr[2]? Это уже DateTime? Другое дело - перестать называть ToString объектами повсюду и использовать правильные методы.

while(rdr.Read()) 
{ 
    UserInfo userInfo = new UserInfo(); 
    userInfo.Msisdn = rdr.GetString(0); 
    userInfo.Gender = rdr.GetString(1); 

    DateTime? birthdate = null; //This is a nullable DateTime see http://msdn.microsoft.com/en-us/library/b3h38hb0.aspx 

    if(rdr.IsDbNull() == false) 
    { 
     birthdate = rdr.GetDateTime(2); 
     userInfo.BirthDate = birthdate.Value; 
    } 
    userInfo.ZipCode = rdr.GetString(3); 
    userInfo.DemographicsKnown = true; 
    userInfo.AgeGroup = getAgeGroup(birthdate); //You may need to edit getAgeGroup to take in a nullable DateTime 

    if (result.Any(x => x.Id== userId)) //Any is much faster than count for your check, see Matthew PK's answer. 
     result.Add(userInfo); 
} 
+0

Это хороший момент, но мне интересно, зачем вообще разбирать его ... Если это недействительный 'DATE', тогда просто возвращаем' NULL' из sproc, затем загружаем в 'NULL' в объект (который как он кажется, он все равно!) Datareaders ** набрали ** методы чтения, забудьте о написании собственных реализаций TryParse. – Matthew

+0

@MatthewPK Да, обновите экран, я поставил большую длинную часть, показывающую, как использовать типизированные методы. Однако, если дата хранится в виде строки в базе данных, он все равно должен будет ее преобразовать (если он не может выполнить трансляцию в запросе выбора) –

+0

, но это не то, что у него есть сейчас ... если дата не правильно отформатирован, тогда он оставляет свойство объекта установленным по умолчанию значением ... нет причины сделать его нулевым. – Matthew

0

Чтобы ускорить получение данных из базы данных, вы можете захотеть рассмотреть другой способ чтения данных вместо циклического считывания.

DataSet my_dataset = new DataSet(); 
NpgsqlDataAdapter my_dataadapter = default(NpgsqlDataAdapter); 

NpgsqlCommand cmd = new NpgsqlCommand(query, conn); 
my_dataadapter = new NpgsqlDataAdapter(cmd); 
my_dataadapter.Fill(my_dataset, "mydataset"); 

Затем сделайте все с набором данных.
Вы можете быть очень удивлены разницей в скорости.