Как обрабатывать огромные наборы результатов из базы данных - PullRequest
6 голосов
/ 24 октября 2008

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

Средний уровень обслуживания выполняет фактические запросы к базе данных. Пользовательский интерфейс просто запрашивает определенные данные и не имеет понятия, что он поддерживается базой данных.

Вопрос в том, как обрабатывать большие наборы данных? Пользовательский интерфейс запрашивает данные, но результаты могут быть огромными, возможно, слишком большими, чтобы поместиться в памяти. Например, приложение для уличных знаков может иметь уровень обслуживания:

StreetSign getStreetSign(int identifier)
Collection<StreetSign> getStreetSigns(Street street)
Collection<StreetSign> getStreetSigns(LatLonBox box)

Уровень пользовательского интерфейса просит, чтобы все дорожные знаки соответствовали некоторым критериям. В зависимости от критериев набор результатов может быть огромным. Слой пользовательского интерфейса может разделить результаты на отдельные страницы (для браузера) или просто представить их все (обслуживая вплоть до Goolge Earth). Потенциально огромным набором результатов может быть проблема производительности и ресурсов (нехватка памяти).

Одно из решений - не возвращать полностью загруженные объекты (объекты StreetSign). Скорее возвращает какой-то набор результатов или итератор, который лениво загружает каждый отдельный объект.

Другое решение - изменить API службы, чтобы он возвращал подмножество запрошенных данных:

Collection<StreetSign> getStreetSigns(LatLonBox box, int pageNumber, int resultsPerPage)

Конечно, пользовательский интерфейс все еще может запросить огромный набор результатов:

getStreetSigns(box, 1, 1000000000)

Мне любопытно, что является стандартным шаблоном промышленного дизайна для этого сценария?

Ответы [ 10 ]

6 голосов
/ 24 октября 2008

Самый первый вопрос должен быть:

¿Пользователь должен или способен управлять этим объемом данных?

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

Я работал над проектами J2EE по системам здравоохранения, которые имеют дело с огромным количеством хранимых данных, буквально миллионами пациентов, посещений, форм и т. Д., И общее правило - не показывать более 100 или 200 строк для любого пользователя поиск, сообщающий пользователю, что этот набор критериев дает больше информации, которую он может понять.

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

Будь осторожен! Это не означает, что каждый метод на уровне службы должен выдавать исключение, если его результирующие размеры превышают 100, это общее правило применяется только к результирующим наборам, которые показаны непосредственно пользователю, что является лучшей причиной для размещения элемента управления в пользовательском интерфейсе. вместо этого на уровне обслуживания.

2 голосов
/ 24 октября 2008

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

Вот пример SQL Server 2000, использующий переменную таблицы (обычно быстрее, чем временная таблица) вместе с вашим примером дорожных знаков:

CREATE PROCEDURE GetPagedStreetSigns
(
  @Page int = 1,
  @PageSize int = 10
)
AS
  SET NOCOUNT ON

  -- This memory-variable table will control paging
  DECLARE @TempTable TABLE (RowNumber int identity, StreetSignId int)

  INSERT INTO @TempTable
  (
     StreetSignId
  )
  SELECT [Id]
  FROM   StreetSign
  ORDER BY [Id]

  -- select only those rows belonging to the requested page
  SELECT SS.*
  FROM   StreetSign SS
         INNER JOIN @TempTable TT ON TT.StreetSignId = SS.[Id]
  WHERE  TT.RowNumber BETWEEN ((@Page - 1) * @PageSize + 1) 
                      AND (@Page * @PageSize)

В SQL Server 2005 вы можете стать более умным с такими вещами, как Common Table Expressions и новыми функциями SQL Ranking. Но общая тема заключается в том, что вы используете сервер для возврата только информации, принадлежащей текущей странице.

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

1 голос
/ 24 октября 2008

При работе с доморощенными классами-обёртками строк, которые, как вы (очевидно), есть, следует опасаться, это код, который делает дополнительные обращения к базе данных без вашего ведома. Например, вы можете вызвать метод, который возвращает коллекцию объектов Person, и подумать, что единственное, что происходит под капотом, - это один вызов «SELECT * FROM PERSONS». В действительности вызываемый вами метод может перебирать возвращенную коллекцию объектов Person и делать дополнительные вызовы БД для заполнения каждой коллекции Orders.

Как вы говорите, одно из ваших решений - не возвращать полностью загруженные объекты, поэтому вы, вероятно, знаете об этой потенциальной проблеме. Одна из причин, по которой я стараюсь избегать использования упаковщиков строк, заключается в том, что они неизменно затрудняют настройку вашего приложения и минимизируют размер и частоту трафика базы данных.

1 голос
/ 24 октября 2008

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

Вы все еще можете установить МАКС, который вы не хотите, чтобы они прошли.

например. SO использует размеры страниц 15, 30, 50 ...

0 голосов
/ 24 октября 2008

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

Если хотите, вы можете наложить на него компоненты, которые выполняют подкачку.

0 голосов
/ 24 октября 2008

Я делал похожие вещи на двух разных продуктах. В одном случае источник данных может быть разбит на страницы - для java реализован интерфейс Pageable, подобный следующему:

public interface Pageable
{
    public void setStartIndex( int index );
    public int getStartIndex();
    public int getRowsPerPage() throws Exception;
    public void setRowsPerPage( int rowsPerPage );
}

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

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

0 голосов
/ 24 октября 2008

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

Я живу в мире Microsoft, поэтому моей основной средой является ASP.Net с SQL Server. Вот две статьи о разбивке по страницам (в которых упоминаются некоторые методы разбивки на страницы с наборами результатов), которые могут быть полезны:

Эффективная (и Ajax) обработка больших объемов данных с ASP.NET 2.0 Эффективный обмен данными с помощью элемента управления ASP.NET 2.0 DataList и ObjectDataSource

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

0 голосов
/ 24 октября 2008

JSF или JavaServerFaces имеют виджеты для разбиения больших наборов результатов в браузере. Это может быть параметризовано, как вы предлагаете. Я бы не назвал это «стандартным шаблоном промышленного дизайна», но стоит посмотреть, как кто-то еще решил эту проблему.

0 голосов
/ 24 октября 2008

В ASP.NET я использовал бы подкачку на стороне сервера, где вы извлекаете только ту страницу данных, которую пользователь запросил из хранилища данных. Это противоположно извлечению всего набора результатов, помещению его в память и поиску по запросу.

...