Знание этих навыков проектирования MongoDB может повысить эффективность на 50%

MongoDB

Дизайн парадигмы или антипарадигма

Рассмотрим такой сценарий, данные нашего заказа такие

商品:
{
  "_id": productId,
  "name": name,
  "price": price,
}

订单:
{
  "_id": orderId,
  "user": userId,
  "items": [
    productId1,
    productId2,
    productId3
  ]
}

Когда мы запрашиваем содержимое заказа, мы сначала запрашиваем заказ через orderId, а затем запрашиваем соответствующую информацию о продукте через productId в информации о заказе. При таком дизайне следующий запрос не может получить полный порядок.

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

Вот взгляд на денормализованный дизайн

订单:
{
  "_id": orderId,
  "user": userId,
  "items": [
   {
    "_id": productId1,
    "name": name,
    "price": price,
   },
   {
    "_id": productId2,
    "name": name,
    "price": price,
   },
  ]
}

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

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

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

  1. Каково соотношение чтения и записи?

Информацию о продукте можно прочитать 10 000 раз, прежде чем изменять ее детали. Чтобы писать быстрее или обеспечить согласованность, стоит ли считывать 10 000 раз? И как часто, по вашему мнению, будут обновляться справочные данные? Чем меньше обновлений, тем лучше для денормализации. Некоторые редко меняющиеся данные вообще вряд ли стоит цитировать. Например, имя, пол, адрес и т. д.

  1. Имеет ли значение последовательность?

Если да, то его нужно нормализовать.

  1. Хотите быстро читать? Денормализация, если вы хотите читать как можно быстрее. В этой цитате это не имеет значения, так что это не фактор, и приложения реального времени должны быть максимально денормализованы.

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

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

Внедрение данных на определенный момент времени

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

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

Никогда не встраивайте постоянно растущие данные

Механизм MongoDB для хранения данных определяет неэффективность непрерывного добавления данных в массив. При нормальном использовании размеры массива и объекта должны быть относительно фиксированными.

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

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

Предварительно выделяйте место, когда это возможно

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

db.collection.insert({"_id" : 1,/* other fields */, "garbase": longString});
db.collection.update({"_id" : 1, });

Таким образом, MongDB выделит достаточно места для будущего роста документа.

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

Используйте массив для хранения встроенных данных, доступ к анонимно

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

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

{
  "_id": 1,
  "items" : {

    "slingshot": {
      "type" : "weapon",
      "damage" : 30,
      "ranged" : true
    },

    "jar" : {
      "type": "container",
      "contains": "fairy"
    }

  }
}

Предположим, вы хотите найти все оружие с уроном выше 20. Поддокумент не поддерживает этот метод поиска, вы можете найти информацию только о конкретном предмете, например {"items.jar.damage": {"$gt": 20 }}. Если вам не нужен идентификатор, используйте массив

{
  "_id": 1,
  "items" : [

    {
      "id" : "slingshot"
      "type" : "weapon",
      "damage" : 30,
      "ranged" : true
    },

    {
      "id" : "jar",
      "type": "container",
      "contains": "fairy"
    }

  ]
}

Например{"items.damage":{"$gt":20}}Вот и все. Если вам также нужен запрос с несколькими условиями, вы можете использовать $elemMatch.

Как использовать автоматически увеличивающийся идентификатор вместо ObjectId

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

Вы можете создать новую коллекцию, чтобы сохранить идентификатор автоинкремента

{
    "_id" : ObjectId("59ed8d3df772d09a67eb25f6"),
    "fieldName" : "user",
    "seq" : NumberLong(100064)
}

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

 public Long getNextSequence(String fieldName, long gap) {
    try {
        Query query = new Query();
        query.addCriteria(Criteria.where("fieldName").is(fieldName));

        Update update = new Update();
        update.inc("seq", gap);

        FindAndModifyOptions options = FindAndModifyOptions.options();
        options.upsert(true);
        options.returnNew(true);

        Counter counter = mongoTemplate.findAndModify(query, update, options, Counter.class);

        if (counter != null) {
            return counter.getSeq();
        }
    } catch (Throwable t) {
        log.error("Exception when getNextSequence from mongodb", t);
    }
    return gap;
}

Не используйте индексы везде

Индексы — это мощное средство, но имейте в виду, что не все запросы могут быть проиндексированы. Например, если вы хотите вернуть 90 % документов в коллекции вместо выборки некоторых записей, вам не следует использовать индекс.

Если для такого рода запросов используется индекс, результатом будет обход почти всего дерева индексов с загрузкой части, скажем, 40-гигабайтного индекса в память. Затем следуйте указателю в индексе, чтобы загрузить 200 ГБ данных документа в коллекцию, которая в конечном итоге загрузится. 200 ГБ + 40 ГБ = 240 ГБ данных, что больше, чем без индексации.

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

Если у вас уже есть индекс для поля и вы не хотите использовать его для крупномасштабных запросов (поскольку использование индекса может быть неэффективным), вы можете использовать естественное упорядочение, чтобы заставить MongoDB отключить индекс. Естественный порядок — это «возврат данных в том порядке, в котором они хранятся на диске», поэтому MongDB не использует индексы.

db.students.find().sort({"$natural" : 1});

Если запрос не использует индекс, MongoDB выполнит полное сканирование таблицы.

Запрос покрытия индекса

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

db.students.ensureIndex(x : 1, y : 1, z : 1);

Теперь запросите проиндексированные поля и попросите вернуть только эти поля, MongoDB не нужно загружать весь документ.

db.students.find({"x" : "xxx", "y" : "xxx"},{x : 1, y : 1, z : 1, "_id" : 0});

Обратите внимание, что поскольку _id возвращается по умолчанию и не является частью индекса, MongoDB необходимо получить _id из документа, удалить его и вернуть результаты только на основе индекса.

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

И точки запроса

Предположим, вы хотите запросить документы, удовлетворяющие условиям A, B и C. Существует 40 000 документов, удовлетворяющих требованиям A, 9 000 документов, удовлетворяющих требованиям B, и 200 документов, удовлетворяющих требованиям C. Если MongoDB выполняет запросы в таком порядке, эффективность невелика.

Если вы сначала поставите C, затем B, затем A, вам нужно всего лишь запросить до 200 документов для B и C.

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

Точки запроса типа ИЛИ

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

Максимально используйте репозиторий для запросов к одной таблице.

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

Преобразовательное предложение

При разработке мы должны писать в коллекцию, некоторые специальные типы (такие как перечисление) требуют от нас написания преобразователя, большинство из которых являются двусторонними, например БД -> Коллекция и Коллекция -> БД Если нужно преобразовать только один тип, мы можем преобразовать этот атрибут, как в примере ниже.


@WritingConverter
@Component
public class UserStatusToIntConverter implements Converter<UserStatus, Integer> {

    @Override
    public Integer convert(UserStatus userStatus) {
        return userStatus.getStatus();
    }
}


@ReadingConverter
@Component
public class UserStatusFromIntConverter implements Converter<Integer, UserStatus> {

    @Override
    public UserStatus convert(Integer source) {
        return UserStatus.findStatus(source);
    }
}

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

@ReadingConverter
@Component
public class OperateLogFromDbConverter extends AbstractReadingConverter<Document, OperateLog> {
  @Override
  public OperateLog convert(Document source) {

      OperateLog opLog = convertBasicField(source);

      if (source.containsKey("_id")) {
          opLog.setId(source.getLong("_id"));
      }

      if (source.containsKey("module")) {

          opLog.setModule(ModuleEnum.findModule(source.getInteger("module")));
      }

      if (source.containsKey("opType")) {
          opLog.setOpType(OpTypeEnum.findOpType(source.getInteger("opType")));
      }

      if (source.containsKey("level")) {
          opLog.setLevel(OpLevelEnum.findOpLevel(source.getInteger("level")));
      }

      return opLog;
  }

  private OperateLog convertBasicField(Document source) {
      Gson gson = new Gson();
      return gson.fromJson(source.toJson(), OperateLog.class);
  }
}

В приведенном выше коде я использовал GSON для преобразования общего поля.Если вы не напишите это так, вам нужно оценить каждое поле, а затем заполнить его.

Если вы хотите обратить внимание на новости статьи, то можете обратить внимание на номер паблика: