2011-02-24 2 views
4

При использовании CoreData следующий предикат индекса нескольких столбцов выполняется очень медленно - для 26 000 записей требуется почти 2 секунды.Является ли это ошибкой, которую я должен представить Apple, или это ожидаемое поведение?

Пожалуйста, обратите внимание, обе колонки индексируются, и я намеренно делаю запрос с> и < = вместо beginswith, чтобы сделать это быстро:

NSPredicate *predicate = [NSPredicate predicateWithFormat: 
    @"airportNameUppercase >= %@ AND airportNameUppercase < %@ \ 
     OR cityUppercase >= %@ AND cityUppercase < %@ \ 
    upperText, upperTextIncremented, 
    upperText, upperTextIncremented]; 

Однако, если я запускаю два отдельных fetchRequests, один для каждого столбца, а затем я объединяю результаты, тогда каждый fetchRequest занимает всего 1-2 сотки секунды, а слияние списков (которые сортируются) занимает около 1/10 секунды.

Является ли это ошибкой в ​​том, как CoreData обрабатывает несколько индексов или это ожидаемое поведение? Ниже мой полный, оптимизированный код, который работает очень быстро:

NSFetchRequest *fetchRequest = [[[NSFetchRequest alloc] init]autorelease]; 
[fetchRequest setFetchBatchSize:15]; 

// looking up a list of Airports 
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Airport" 
              inManagedObjectContext:context]; 
[fetchRequest setEntity:entity];  

// sort by uppercase name 
NSSortDescriptor *nameSortDescriptor = [[[NSSortDescriptor alloc] 
      initWithKey:@"airportNameUppercase" 
      ascending:YES 
       selector:@selector(compare:)] autorelease]; 
NSArray *sortDescriptors = [[[NSArray alloc] initWithObjects:nameSortDescriptor, nil]autorelease]; 
[fetchRequest setSortDescriptors:sortDescriptors]; 

// use > and <= to do a prefix search that ignores locale and unicode, 
// because it's very fast 
NSString *upperText = [text uppercaseString]; 
unichar c = [upperText characterAtIndex:[text length]-1]; 
c++;  
NSString *modName = [[upperText substringToIndex:[text length]-1] 
         stringByAppendingString:[NSString stringWithCharacters:&c length:1]]; 

// for the first fetch, we look up names and codes 
// we'll merge these results with the next fetch for city name 
// because looking up by name and city at the same time is slow 
NSPredicate *predicate = [NSPredicate predicateWithFormat: 
    @"airportNameUppercase >= %@ AND airportNameUppercase < %@ \ 
         OR iata == %@ \ 
         OR icao == %@", 
    upperText, modName, 
    upperText, 
    upperText, 
    upperText]; 
[fetchRequest setPredicate:predicate]; 

NSArray *nameArray = [context executeFetchRequest:fetchRequest error:nil]; 

// now that we looked up all airports with names beginning with the prefix 
// look up airports with cities beginning with the prefix, so we can merge the lists 
predicate = [NSPredicate predicateWithFormat: 
    @"cityUppercase >= %@ AND cityUppercase < %@", 
    upperText, modName]; 
[fetchRequest setPredicate:predicate]; 
NSArray *cityArray = [context executeFetchRequest:fetchRequest error:nil]; 

// now we merge the arrays 
NSMutableArray *combinedArray = [NSMutableArray arrayWithCapacity:[cityArray count]+[nameArray count]]; 
int cityIndex = 0; 
int nameIndex = 0; 
while( cityIndex < [cityArray count] 
     || nameIndex < [nameArray count]) { 

    if (cityIndex >= [cityArray count]) { 
    [combinedArray addObject:[nameArray objectAtIndex:nameIndex]]; 
    nameIndex++; 
    } else if (nameIndex >= [nameArray count]) { 
    [combinedArray addObject:[cityArray objectAtIndex:cityIndex]]; 
    cityIndex++; 
    } else if ([[[cityArray objectAtIndex:cityIndex]airportNameUppercase] isEqualToString: 
         [[nameArray objectAtIndex:nameIndex]airportNameUppercase]]) { 
    [combinedArray addObject:[cityArray objectAtIndex:cityIndex]]; 
    cityIndex++; 
    nameIndex++; 
    } else if ([[cityArray objectAtIndex:cityIndex]airportNameUppercase] < 
         [[nameArray objectAtIndex:nameIndex]airportNameUppercase]) { 
    [combinedArray addObject:[cityArray objectAtIndex:cityIndex]]; 
    cityIndex++; 
    } else if ([[cityArray objectAtIndex:cityIndex]airportNameUppercase] > 
         [[nameArray objectAtIndex:nameIndex]airportNameUppercase]) { 
    [combinedArray addObject:[nameArray objectAtIndex:nameIndex]]; 
    nameIndex++; 
    } 

} 

self.airportList = combinedArray; 
+0

Похоже, вы пытаетесь преждевременному оптимизируют ваш запрос предикатами. Вы пробовали более простые предикаты и обнаружили, что они еще медленнее? –

+0

В стороне, вы явно думаете об этом в терминах SQL, что приведет к горя в Core Data. Хотя Core Data часто использует хранилище SQLite, это последнее дополнение и фактически не имеет ничего общего с основной функцией Core Data, которая управляет графами объектов в памяти. – TechZen

ответ

7

CoreData не affordance для создания или использования индексов с несколькими столбцами. Это означает, что при выполнении запроса, соответствующего вашему предикату с несколькими свойствами, CoreData может использовать только один индекс для выбора. Впоследствии он использует индекс для одного из тестов свойств, но SQLite не может использовать индекс для сбора совпадений для второго свойства и, следовательно, должен делать все это в памяти вместо использования своей структуры индекса на диске.

Эта вторая фаза выбора заканчивается медленной, потому что она должна собирать все результаты в память с диска, а затем делать сравнения и падать результаты в памяти. Таким образом, вы в конечном итоге делаете потенциально больше ввода-вывода, чем если бы вы могли использовать индекс с несколькими столбцами.

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

Чтобы ответить на ваш вопрос, это поведение не является неожиданным для Apple; это просто решение дизайнерского решения не поддерживать многоколоночные индексы в CoreData. Но вы должны указать ошибку в http://radar.apple.com с запросом поддержки индексов с несколькими столбцами, если вы хотите увидеть эту функцию в будущем.

В то же время, если вы действительно хотите получить максимальную производительность базы данных на iOS, вы можете использовать SQLite напрямую вместо CoreData.

+0

+1 за рекомендацию о том, что он отправил ошибку с запросом на функцию. Если есть сомнения, напишите ошибку! –

+0

Из этого объяснения кажется, что любой запрос с оператором OR, использующим разные индексированные столбцы, должен обрабатывать всю базу данных в памяти. Но на практике это не так, потому что многие из этих операторов быстрее, чем обработка всей базы данных. Что мне не хватает? – lacker

+0

Им не нужно обрабатывать всю базу данных в памяти. CoreData может использовать один индекс для дисквалификации кучки строк на основе одной части предиката. Но после того, как он использует этот один индекс, он должен обрабатывать остальную часть предложения where в памяти. Например, если у вас есть таблица с столбцом is_male, а половина строк в этой таблице представляет женщин, тогда запрос с «is_male = 0 и age> 30» в предложении where должен будет обрабатывать только память - половина содержимого таблицы, которая является женской. – Ryan

1

Если у вас есть сомнения, вы должны указать ошибку.

В настоящее время нет API для указания Core Data для создания составного индекса. Если бы существовал составной индекс, он был бы использован без проблем.

Неиндексированные столбцы не обрабатываются целиком в памяти. Они приводят к сканированию таблицы, что не то же самое, что и загрузка всего файла (ну, если в вашем файле нет только 1 таблицы). Сканирование таблицы по строкам имеет тенденцию быть очень медленным.

Сам SQLite ограничен количеством индексов, которые будут использоваться для каждого запроса. В основном всего 1, дайте или примите некоторые обстоятельства.

Вы должны использовать флаг [n] для этого запроса, чтобы выполнить двоичный поиск по нормализованному тексту. В ADC есть пример проекта под названием «DerivedProperty». Он покажет, как нормализовать текст, чтобы вы могли использовать бинарные сопоставления, в отличие от интеграции ICU по умолчанию для фантастических локализованных сравнений текста в Unicode.

Там очень много больше дискуссии о быстрой последовательности ищущего в Core Data в https://devforums.apple.com/message/363871

Смежные вопросы