Должен ли я определить отношения между таблицами в базе данных или просто в коде?


60

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

В базе данных:

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

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

В исходном коде:

  • Более гибкий.

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

  • Больше контроля над целостностью данных. База данных не должна проверять каждый раз, когда приложение изменяет данные (сложность может быть O (n) или O (n log n) (?)). Вместо этого он делегирован приложению. И я думаю, что обработка целостности данных в приложении приведет к более подробным сообщениям об ошибках, чем при использовании базы данных. Например: когда вы создаете сервер API, если вы определяете отношения в базе данных, и что-то идет не так (как ссылка на сущность не существует), вы получите исключение SQL с сообщением. Простым способом будет вернуть клиенту 500, что есть «Внутренняя ошибка сервера», и клиент не будет знать, что происходит не так. Или сервер может проанализировать сообщение, чтобы выяснить, в чем дело, что, на мой взгляд, является уродливым и подверженным ошибкам способом. Если вы позволите приложению справиться с этим,

Что-нибудь еще?

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

Редактировать: спасибо всем. Все ответы, которые я получил, указывают на то, что ограничения / отношения должны быть определены в базе данных. :). У меня есть еще один вопрос, так как он выходит за рамки этого вопроса, я просто разместил его как отдельный вопрос: Обработка ошибок базы данных для сервера API . Пожалуйста, оставьте некоторые идеи.


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

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

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

3
В исходном коде: в конечном итоге вы пишете большую часть базы данных.
Blrfl

7
Однажды я задал очень талантливому программисту похожий вопрос, который он сказал мне: «Это похоже на тормоза на машине. Суть не в том, чтобы сделать машину медленнее, а в том, чтобы она стала быстрее и безопаснее». Конечно, можно работать без ограничений, но если плохие данные каким-то образом попадут, это может вызвать серьезный сбой
mercurial

Ответы:


70

TL; DR: ограничения отношений должны идти в базу данных.


Ваше приложение недостаточно велико.

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

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

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

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

Ваша заявка не является правильной.

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

И какой из них вы меняете чаще всего?

Я бы поставил на правильность базы данных в любое время .

Ваши разработчики не думают, что распределены достаточно.

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

Красный флаг ! 1

Если вы думаете:

  • проверьте, существует ли запись
  • если нет, вставьте запись

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

Если вы думаете:

  • проверьте, существует ли запись
  • если нет, вставьте запись
  • проверьте, была ли запись вставлена ​​как дубликат

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

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

1 Если ваша база данных правильно не реализует свойство Serializable; но немногие на самом деле делают.


Прошлой:

И я думаю, что обработка целостности данных в приложении позволит получить более подробное сообщение об ошибке, чем при использовании базы данных. Например: когда вы создаете сервер API. Если вы определите отношения в базе данных, и что-то пойдет не так (например, сущность, на которую ссылаются, не существует), вы получите исключение SQL с сообщением.

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

Обратите внимание, что в большинстве случаев кода достаточно: если у вас есть код ошибки, сообщающий, что ссылочный внешний ключ не существует, то, вероятно, эта таблица имеет только один внешний ключ, поэтому вы знаете в коде, в чем проблема ,

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

... который просто связан с точкой правильности выше. Каждый раз, когда вы видите «500: Внутренняя ошибка сервера», потому что ограничение базы данных сработало и не было обработано, это означает, что база данных спасла вас, поскольку вы просто забыли обработать ее в коде.


3
Ха-ха, ты написал это, когда я писал свой комментарий, иронически подчеркивая мысль, которую мы оба выдвигаем. Я полностью согласен: Вы не можете даже сделать целостность или ограничения в коде без БД. Транзакции не могут видеть результаты других, пока они не зафиксированы (и даже тогда, возможно, нет). Вы можете получить иллюзию целостности, но это зависит от времени или серьезных проблем с масштабируемостью из-за блокировок. Только база данных может сделать это правильно.
LoztInSpace

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

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

1
Ваш аргумент о MVCC не точен в тех БД, которые правильно реализуют изоляцию SERIALIZABLE (например, современные версии PostgreSQL - хотя многие основные РСУБД этого не делают). В такой БД, даже первый, наивный подход будет работать правильно - если конфликт записи, они будут откатываться как сбой сериализации.
James_pic

1
В тех БД, которые правильно реализуют SERIALIZABLE, если вы берете все успешно зафиксированные транзакции, то существует некоторое упорядочение (которое может не совпадать с упорядочением настенных часов), например, если вы запустили все из них последовательно (без параллелизма) в этом порядке все результаты были бы точно такими же. Это сложно сделать правильно, а спецификации SQL достаточно расплывчаты, чтобы вы могли убедить себя, что можно разрешить асимметрию записи на уровне SERIALIZABLE, поэтому многие производители БД рассматривают SERIALIZABLE как SNAPSHOT ISOLATION (я смотрю на вас, Oracle).
James_pic

118

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

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


5
@ dan1111 Я не понимаю ваш комментарий ... вы говорите: база данных накладывает простые ограничения, поэтому они не являются проблемой для кода приложения, более сложные ограничения слишком сложны для реализации, поэтому просто откажитесь от них? Или вы говорите, что реализация сложных ограничений с использованием триггеров базы данных (и аналогичного механизма) слишком сложна, и поэтому лучше реализовать их в коде приложения?
Бакуриу

47
Вы не можете даже сделать целостность или ограничения в не БД кода. Транзакции не могут видеть результаты других, пока они не зафиксированы (и даже тогда, возможно, нет). Вы можете получить иллюзию целостности, но это зависит от времени или серьезных проблем с масштабируемостью из-за блокировок. Только база данных может сделать это правильно.
LoztInSpace

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

9
@ dan1111 Вы никогда не пишите ошибки в приложении, верно?
user253751 26.10.16

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

51

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

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

Кроме того, вы можете получить другие требования, например: «Большому клиенту X действительно нужен этот лист данных Excel, импортированный в нашу базу данных приложений сегодня днем», где вы не сможете позволить себе адаптировать код приложения в соответствии с выполнением грязного сценария SQL. во время.

Это где целостность уровня базы данных спасет ваш бекон.


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

Будет ли он вас ненавидеть, если в базе данных нет ограничений FK, чтобы он мог сказать, какие отношения у таблицы, прежде чем он ее изменит? ( Подсказка, ответ - да )


33
О брат. Я не могу сказать вам, сколько раз мне приходилось объяснять людям, что в базе данных более одного клиента! Даже если сейчас существует только один клиент и только один канал для ввода данных в систему, разработка вашего приложения и схемы на основе этого предположения - лучший способ для Future Yoshi ненавидеть Past Yoshi.
Грег Бургхардт

9
@nikonyrh Я бы так не делал. Существуют ограничения, чтобы приложения могли полагаться на согласованные данные. Отключить ФК «просто чтобы получить его» - это безумие.
Пэдди

5
Согласовано. Кроме того, даже если ваше приложение является единственным клиентом, у вас могут быть разные версии приложения, пытающиеся навязать немного другие ограничения. Обычно возникает веселье (ну, не совсем. Это больше похоже на «хаос и полное разочарование», чем «веселье»).
Икер

5
Я могу абсолютно подтвердить это. В моем случае я застрял на MyISAM, который фактически не поддерживает внешние ключи, поэтому я получил 250 ГБ данных с целостностью, обеспечиваемой приложением. Когда дело дошло до сокращения данных, чтобы получить резерв, который стал более управляемым, и когда стало ясно, что само приложение вообще не сможет справиться с этим, начался хаос. Я не знаю, почему я использую прошедшее время; это все еще происходит сейчас, и проблема (два года спустя) еще не решена. * нюхает *
Легкость гонок с Моникой

1
Я бы сказал, что приличная кодовая база должна облегчить написание одноразового скрипта с использованием уровня персистентности из вашего приложения как минимум так же быстро, как и для написания необработанного SQL. «Изменение кода вашего приложения» никогда не должно быть необходимым для одноразовых сценариев.
Джонатан В ролях

17

Вы должны иметь отношения в базе данных.

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

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


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

13
  • Мы больше не живем в одном бэк-энде <-> одном фронт-мире.
  • Большинство решений включают веб-интерфейс, мобильный интерфейс, пакетный интерфейс, интерфейс iPad и т. Д.
  • Механизмы баз данных уже имеют тысячи протестированных строк кода, оптимизированных для обеспечения ссылочной целостности.

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


2
«Мы больше не живем в одном бэк-энде <-> одном фронт-мире». Мы когда нибудь? Несколько лет назад я работал над системой баз данных, в которой имелись программы, написанные как минимум на двух десятках разных языков. Некоторые из программ были выпущены в 1970-х годах.
Майк Шеррилл 'Cat Recall'

2

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

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

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


1

Большинство людей по сути говорят: «Да, в общем, вы всегда должны определять отношения в базе данных». Но если бы дисциплины в области компьютерных наук были такими простыми, нас называли бы «Читатели программного обеспечения» вместо «Инженеры-программисты». Я действительно согласен с тем, что ограничения должны быть в базе данных, если только нет веской причины, по которой они не должны этого делать, поэтому позвольте мне привести пару причин, которые могут считаться хорошими в определенных ситуациях:

Дубликат кода

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

усилие

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

КПД

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

контроль

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

Закрытие пунктов

  • Базы данных написаны в коде. Они не делают ничего волшебного, чего вы не можете сделать в своем собственном коде.
  • Ничто не бесплатно. Ограничения, отношения и т. Д. Используют циклы ЦП.
  • Люди в мире NoSQL прекрасно обходятся без традиционных реляционных функций. Например, в MongoDB структура документов JSON достаточно хороша для поддержки всей базы данных.
  • Слепое и невежественное использование расширенных функций базы данных не может гарантировать никаких преимуществ. Вы можете случайно заставить что-то работать, только чтобы сломать это позже.
  • Вы задали очень общий вопрос без перечисления конкретных требований или ограничений. Реальный ответ на ваш вопрос "это зависит".
  • Вы не указали, была ли это проблема масштаба предприятия. Другие ответы говорят о таких вещах, как клиенты и целостность данных, но иногда эти вещи не важны.
  • Я предполагаю, что вы говорите о традиционной реляционной базе данных SQL.
  • Моя перспектива заключается в том, что я отказался от использования множества ограничений и внешних ключей в небольших (до 50 таблиц) проектах и не заметил никаких недостатков .

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


1
Если люди отвергают продуманные ответы, потому что они не согласны с их догмой, биржа SE StackExchange становится еще хуже.
Карл Лет

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

1
«Базы данных написаны в коде. Нет ничего волшебного в том, что вы не можете сделать в своем собственном коде». Нет, вы не можете обеспечить ссылочную целостность в коде приложения (и если вам не нужно применять его, зачем вообще использовать сервер базы данных?). Дело не в том, что может сделать код, а в том, где это можно сделать.
Хайд

0

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

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

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

Глядя на ваши точки, особенно ссылки на базы данных . В этом случае, да, должна быть ссылка, реализованная не в самой СУБД, а в службе. Но прежде чем идти по этому пути, не лучше ли обдумать это уже во время разработки?

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

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

Проблема с производительностью, связанная с проверками целостности в БД , не является реальной. СУБД может проверять такие вещи намного быстрее, чем любая ваша реализация. Почему? Что ж, вам приходится иметь дело со сбоем средств массовой информации, а RDBMS - нет. И он может оптимизировать такие проверки, используя свою статистику также

Итак, вы видите, все это возвращается к дизайну. Конечно, вы можете сказать сейчас, но что, если появляется неизвестное требование, меняющее правила игры? Да, это может произойти, но такие изменения должны быть разработаны и спланированы так же. ; О)


0

У вас есть очень хорошие ответы, но есть еще несколько моментов

Целостность данных - это то, для чего предназначена база данных

Выполнение правильного параллелизма типа удаления FK на уровне приложения было бы ужасным

Экспертиза в целостности данных с DBA

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

Скажем, вас взломали - у вас проблемы в любом случае, но хакер может нанести большой ущерб через маленькую дыру, если в базе данных нет защиты целостности

Возможно, вам придется манипулировать данными напрямую через SQL или TSQL.
Никто не помнит все правила данных


0

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

База данных, которую вы можете изменить, содержит столько же кода, сколько любая строка ruby, javascript, c # или ada.

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


0

Здесь множество хороших ответов. Я добавлю, что если у вас есть приложение, написанное на языке Y, вы можете создать в Y код, похожий на ограничение базы данных. А затем кто-то захочет получить доступ к вашей базе данных с использованием языка Z, вам придется снова написать тот же код. Да поможет вам Бог, если реализации не совсем такие же. Или когда опытный бизнес-пользователь подключается к вашей базе данных с помощью Microsoft Access.

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

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.