Это плохая идея, чтобы сохранить промежуточное поле в базе данных - PullRequest
6 голосов
/ 24 сентября 2011

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

Каждая партия имеет ряд затрат, например:

  • ItemCost
  • ShippingCost
  • HandlingCost
  • TaxCost

В приложении есть много мест, где мне нужно получить сводную информацию для заказа, например:

  • TotalItemCost
  • TotalShippingCost
  • TotalHandlingCost
  • TotalTaxCost
  • TotalCost
  • TotalPaid
  • TotalProfit

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

На мой взгляд, есть несколько основных способов:

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

  2. Создайте представление, которое предоставляет подзапросы в виде простых полей. Это делает отчеты, которые нуждаются в них, простыми.

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

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

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

Между прочим, я занял третье место в основном из-за производительности и простоты запросов.

Обновление:

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

Ответы [ 5 ]

4 голосов
/ 24 сентября 2011

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

3 голосов
/ 24 сентября 2011

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

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

Если запуск триггера занимает много времени, убедитесь, что вы делаете эти обновления в непиковые часы.

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

Не выставлять счета-фактуры для заказов, которые не были обработаны запросом проверки
Добавьте дополнительное поле даты в таблицу order с именем timeoflastsuccesfullvalidation и установите для него значение null, если проверка не удалась.
Только счета-фактуры с dateoflastsuccesfullvalidation меньше, чем 24 часа назад.
Конечно, вам не нужно проверять полностью обработанные заказы, только ожидающие обработки заказы.

Вариант 1 может быть достаточно быстрым
Относительно # 1

Это также медленно.

Это во многом зависит от того, как вы запрашиваете БД.
Вы упоминаете подвыборы, в приведенном ниже основном законченном скелетном запросе я не вижу необходимости во многих подвыборах, поэтому вы меня немного озадачили.

SELECT field1,field2,field3
       , oifield1,oifield2,oifield3
       , NettItemCost * (1+taxrate) as TotalItemCost
       , TotalShippingCost
       , TotalHandlingCost
       , NettItemCost * taxRate as TotalTaxCost
       , (NettItemCost * (1+taxrate)) + TotalShippingCost + TotalHandlingCost as TotalCost
       , TotalPaid
       , somethingorother as TotalProfit
FROM (

  SELECT o.field1,o.field2, o.field3
         , oi.field1 as oifield1, i.field2 as oifield2 ,oi.field3 as oifield3
         , SUM(c.productprice * oi.qty) as NettItemCost
         , SUM(IFNULL(sc.shippingperkg,0) * oi.qty * p.WeightInKg) as TotalShippingCost
         , SUM(IFNULL(hc.handlingperwhatever,0) * oi.qty) as TotalHandlingCost
         , t.taxrate as TaxRate
         , IFNULL(pay.amountpaid,0) as TotalPaid
  FROM orders o
  INNER JOIN orderitem oi ON (oi.order_id = o.id)
  INNER JOIN products p ON (p.id = oi.product_id)
  INNER JOIN prices c ON (c.product_id = p.id 
                       AND o.orderdate BETWEEN c.validfrom AND c.validuntil)
  INNER JOIN taxes t ON (p.tax_id = t.tax_id 
                       AND o.orderdate BETWEEN t.validfrom AND t.validuntil) 
  LEFT JOIN shippingcosts sc ON (o.country = sc.country
                       AND o.orderdate BETWEEN sc.validfrom AND sc.validuntil)
  LEFT JOIN handlingcost hc ON (hc.id = oi.handlingcost_id
                       AND o.orderdate BETWEEN hc.validfrom AND hc.validuntil)
  LEFT JOIN (SELECT SUM(pay.payment) as amountpaid FROM payment pay 
             WHERE pay.order_id = o.id) paid ON (1=1)
  WHERE o.id BETWEEN '1245' AND '1299'
  GROUP BY o.id DESC, oi.id DESC ) AS sub  

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

Советы по скорости
Убедитесь, что у вас есть индексы для всех полей, включенных в критерии соединения.
Используйте таблицу MEMORY для таблиц меньшего размера, например tax и shippingcost, и используйте индекс hash для id в таблицах памяти.

3 голосов
/ 24 сентября 2011

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

Я бы использовал представление только для вычисления их на SELECT, если бы количество строк было обычно довольно маленьким и доступ был несколько редким. Производительность будет намного лучше, если вы кешируете их.

2 голосов
/ 24 сентября 2011

Я бы избегал # 3, насколько это возможно.Я предпочитаю это по разным причинам:

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

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

  3. Для целей отчетности можно использовать другую схему базы данных.База данных отчетов может быть сильно нормализована для повышения производительности без усложнения основного приложения.

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

1 голос
/ 24 сентября 2011

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

...