Коли і чому приєднання до бази даних дорого?


354

Я займаюся деякими дослідженнями баз даних і переглядаю деякі обмеження реляційних БД.

Я розумію, що приєднання до великих столів дуже дороге, але я не зовсім впевнений, чому. Що потрібно зробити СУБД для виконання операції приєднання, де вузьке місце?
Як денормалізація може допомогти подолати ці витрати? Як допомагають інші методи оптимізації (наприклад, індексація)?

Особистий досвід вітається! Якщо ви збираєтесь розміщувати посилання на ресурси, уникайте Вікіпедії. Я знаю, де це вже знайти.

У зв'язку з цим мені цікаво денормалізований підхід, який використовують бази даних хмарних служб типу BigTable та SimpleDB. Дивіться це питання .


3
Ви також розглядаєте переваги? ;)
Девід Олдрідж

Я розглядаю об'єктивне (якщо таке є) порівняння. Професіонали, шахрайці, що-що-маєш.
Рік

Заздалегідь запропоновані підходи до хмарних обчислень розраховані на те, що можна робити ставки всіма способами, уникаючи проблеми "неправильного приєднання". У Google є кілька відомостей про власні системи. Досить цікаво - способи розширити можливість застосування особливих справ.
Пітер Вон

@PeterWone - хочете вказати посилання на деякі з цих робіт? ps, щоб відповісти на запитання у вашому профілі, Android - це Open Source - ну, принаймні частково, тому вундерки стрибали на цій смузі. Побачені технічно вдосконаленими великими немитими, за ними стежили, як лемінги, в тісні та потужні обійми Google! Бетамакс когось? Ближче до мого власного серця (та покоління), як MySQL (без FOREGIN KEYFFS) став (і залишається) найпопулярнішою СУБД "R" у світі, коли він мав конкуренцію з PostgreSQL (відсутність рідної версії Windows) та Firebird (фіаско Opensourcing) чи навіть SQLite?
Vérace

Потрібно сказати, що я вважаю PostgreSQL і Firebird як надзвичайно перевагу MySQL для багатокористувацьких систем і SQLite як зоряну в єдиній користувацькій сфері. SQLite обробляє сайт sqlite.org (400,00 звернень на день!).
Vérace

Відповіді:


470

Денормалізація для підвищення продуктивності? Це звучить переконливо, але це не тримає води.

Кріс Дат, який у компанії з доктором Тедом Коддом був оригінальним прихильником моделі реляційних даних, вичерпав терпіння з неправильно аргументованими аргументами проти нормалізації та систематично їх зносив науковим методом: він отримав великі бази даних і перевірив ці твердження.

Я думаю , що він написав в реляційної базі даних Писання 1988-1991 , але ця книга була пізніше згорнутим в видання шостого Введення в бази даних системи , яка є остаточним текстом по теорії база даних і дизайну, в восьмому виданні , як я пишу і , ймовірно, залишуся у друку на наступні десятиліття. Кріс Дата був експертом у цій галузі, коли більшість із нас ще бігали босоніж.

Він виявив, що:

  • Деякі з них стосуються особливих справ
  • Усі вони не платять за загальне використання
  • Усі вони значно гірші для інших особливих випадків

Все повертається до зменшення розміру робочого набору. Приєднання за допомогою правильно підібраних ключів з правильно встановленими індексами коштує дешево, а не дорого, оскільки вони дозволяють істотно підрізати результат перед матеріалізацією рядків.

Матеріалізація результату передбачає масові читання дисків, які є найдорожчим аспектом вправи на порядок. З іншого боку, виконання з'єднання логічно вимагає пошуку лише клавіш . На практиці навіть не отримано навіть ключових значень: ключові хеш-значення використовуються для порівняння об'єднань, зменшуючи витрати на приєднання кількох стовпців і кардинально зменшуючи вартість з'єднань, що включають порівняння рядків. Мало того, що набагато більше вміститься в кеші, буде набагато менше читання дисків.

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

Безумовно, такий тип оптимізації може бути застосований і до денормалізованих баз даних, але такі люди, які хочуть денормалізувати схему, зазвичай не замислюються про кардинальність, коли (якщо) вони встановлюють індекси.

Важливо розуміти, що сканування таблиць (обстеження кожного рядка в таблиці під час створення з'єднання) на практиці рідкісні. Оптимізатор запитів вибере сканування таблиці лише тоді, коли виконано одне або більше із наведених нижче.

  • У співвідношенні менше 200 рядків (у цьому випадку сканування буде дешевшим)
  • У стовпцях приєднання немає відповідних індексів (якщо доречно приєднатися до цих стовпців, то чому їх не індексувати? Виправити це)
  • Тип примус потрібно до того , як стовпці можна порівняти (WTF?! Виправити його або піти додому) SEE END ПРИМІТКИ ДЛЯ ADO.NET ВИПУСКУ
  • Одним із аргументів порівняння є вираз (без індексу)

Виконання операції дорожче, ніж її виконання. Однак виконання неправильної операції, примушування до безглуздого дискового вводу / виводу, а потім викидання дроселя перед виконанням з'єднання вам дійсно потрібно, набагато дорожче. Навіть коли попередньо обчислюється "неправильна" операція та розумно застосовуються індекси, все ще залишається значна пеня. Денормалізація для попереднього обчислення об'єднання - незважаючи на пов'язані з цим аномалії оновлення - є зобов'язанням до певного приєднання. Якщо вам потрібно інше приєднання, це зобов'язання обійдеться вам у велику суму .

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

Для всіх вас, хто працює над платіжними системами або генераторами небажаної пошти (ганьба над вами) і з обуренням налаштовуєте руку на клавіатуру, щоб сказати мені, що ви знаєте на той факт, що денормалізація швидша, вибачте, але ви живете в одному із спеціальних випадки - конкретно, випадок, коли ви обробляєте всі дані, в порядку. Це не загальний випадок, і ви будете виправдані в вашої стратегії.

Ви не виправдані фальшивим узагальненням. Див. Кінець розділу приміток для отримання додаткової інформації про належне використання денормалізації в сценаріях зберігання даних.

Я також хотів би відповісти

Приєднання - це лише декартові вироби з деяким блиском

Який вантаж болоків. Обмеження застосовуються якомога раніше, перш за все обмежуючі. Ви читали теорію, але не зрозуміли її. Приєднання трактуються як "декартові продукти, до яких застосовуються предикати" лише оптимізатором запитів. Це символічне подання (фактично нормалізація) для полегшення символьної декомпозиції, щоб оптимізатор міг виробляти всі еквівалентні перетворення та класифікувати їх за вартістю та вибірковістю, щоб він міг вибрати найкращий план запитів.

Єдиний спосіб, коли ви завжди отримаєте оптимізатор для виготовлення декартового продукту, - це не забезпечити присудок: SELECT * FROM A,B


Примітки


Девід Олдрідж надає важливу додаткову інформацію.

Окрім індексів та сканувань таблиць, існує безліч інших стратегій, а сучасний оптимізатор обійдеться їм усім перед тим, як скласти план виконання.

Практична порада: якщо його можна використовувати як зовнішній ключ, то індексуйте його, щоб оптимізатор став доступним для стратегії індексів .

Раніше я був розумнішим за оптимізатор MSSQL. Це змінилося дві версії тому. Зараз це взагалі мене вчить . Це, в дуже реальному сенсі, експертна система, кодифікуючи всю мудрість багатьох дуже розумних людей у ​​достатньо закритій галузі, що система, заснована на правилах, є ефективною.


"Боллоки", можливо, були нетактичними. Мене просять бути менш вибагливим і нагадали, що математика не бреше. Це правда, але не всі наслідки математичних моделей обов'язково слід сприймати буквально. Квадратні корені від’ємних чисел дуже зручні, якщо ви ретельно уникаєте вивчення їх абсурдності (каламбур) і переконуєтесь, що ви скасуєте їх усе, перш ніж спробувати інтерпретувати своє рівняння.

Причиною того, що я відповів настільки жорстоко, було те, що це твердження говорить про це

Приєднання - декартові продукти ...

Це не може бути те , що мав в виду , але це те , що було написано, і це категорично НЕ відповідає дійсності. Декартовий продукт - це відношення. Об'єднання - це функція. Більш конкретно, з'єднання - це функція, що оцінюється відношенням. З порожнім предикатом він створить декартовий продукт, і перевірка того, що це робиться, - це одна перевірка правильності роботи механізму запитів бази даних, але ніхто не пише невпинно приєднується на практиці, оскільки вони не мають практичної цінності поза аудиторією.

Я закликав це, тому що я не хочу, щоб читачі потрапляли в древню пастку плутати модель з річчю, що моделюється. Модель - це наближення, навмисно спрощене для зручного маніпулювання.


Межа для вибору стратегії приєднання до сканування таблиці може відрізнятися між двигунами бази даних. На нього впливає ряд рішень щодо реалізації, таких як коефіцієнт заповнення дерева-вузла, розмір ключового значення та тонкощі алгоритму, але в широкому сенсі високоефективна індексація має час виконання k log n + c . Термін C - це фіксований наклад, який здебільшого складається з часу встановлення, а форма кривої означає, що ви не отримаєте окупності (порівняно з лінійним пошуком), поки n не стане сотнями.


Іноді денормалізація є хорошою ідеєю

Денормалізація - це прихильність до певної стратегії приєднання. Як було сказано раніше, це заважає іншим стратегіям приєднання. Але якщо у вас є відра дискового простору, передбачувані схеми доступу та схильність обробляти багато або все це, то попередньо обчислити об'єднання може бути дуже вартим.

Ви також можете визначити шляхи доступу, які ваша операція зазвичай використовує, і попередньо обчислити всі з'єднання для цих шляхів доступу. Це передумова для сховищ даних, або, принаймні, це тоді, коли їх будують люди, які знають, чому вони роблять те, що роблять, а не лише заради дотримання мовних слів.

Правильно сконструйований сховище даних періодично виробляється об'ємним перетворенням із нормалізованої системи обробки транзакцій. Такий поділ баз даних операцій та звітів надає дуже бажаний ефект усунення зіткнення між OLTP та OLAP (обробка в Інтернеті транзакцій, тобто введення даних, та онлайн-аналітична обробка, тобто звітність).

Важливим моментом тут є те, що крім періодичних оновлень, сховище даних читається лише . Це викликає суперечку з питаннями аномалій оновлення.

Не робіть помилки денормалізації вашої бази даних OLTP (бази даних, в якій відбувається введення даних). Це може бути швидше для запуску рахунків, але якщо ви зробите це, ви отримаєте аномалії оновлення. Ви коли-небудь намагалися отримати Reader's Digest, щоб перестати надсилати вам речі?

Простір на диску сьогодні дешевий, тому вибивайте себе. Але денормалізація - це лише частина історії для сховищ даних. Набагато більший показник підвищення продуктивності отримується від попередньо обчислених зведених значень: щомісячні підсумки, такі речі. Йдеться завжди про зменшення робочого набору.


Проблема ADO.NET з невідповідностями типів

Припустимо, у вас є таблиця SQL Server, що містить індексований стовпець типу varchar, і ви використовуєте AddWithValue для передачі параметра, що обмежує запит у цьому стовпчику. Рядки C # - це Unicode, тому виведений тип параметра буде NVARCHAR, який не відповідає VARCHAR.

VARCHAR до NVARCHAR - це конверсія, що розширюється, і це відбувається неявно - але попрощайтеся з індексуванням і удачі, розробивши чому.


"Порахуйте хіти диска" (Рік Джеймс)

Якщо все запам’ятовано в оперативній пам’яті, JOINsдосить дешево. Тобто за нормалізацію не передбачено особливих штрафних санкцій .

Якщо "нормалізована" схема спричиняє JOINsудари диска дуже сильно, але еквівалентна "денормалізована" схема не повинна була б потрапляти на диск, тоді денормалізація виграє конкуренцію за продуктивність.

Коментар від оригінального автора: Сучасні двигуни бази даних дуже добре організовують послідовність доступу, щоб мінімізувати пропуски кешу під час операцій приєднання. Вищезазначене, хоча це правда, може бути неправильним, оскільки це означає, що великі дані приєднання обов'язково є досить дорогими. Це призведе до поганого прийняття рішень з боку недосвідчених розробників.


7
Sonme цих тверджень характерні для конкретної СУБД, чи не так? напр. "У співвідношенні менше 200 рядків"
Девід Олдрідж

2
Чи впливає використання сурогатних ключів (чи ні) на все це суттєво?
Девід Пламптон

3
Великий EF Codd несе повну відповідальність за реляційну модель. CJ Date і останнім часом H Darwen - обидва ідіоти, які не розуміють RM, і надають масу інформації про "як покращити" RM, і все це можна звільнити, тому що не можна виправити те, що не розуміє . Вони служать лише для пошкодження актуальності RM, припускаючи, що щось "відсутнє".
PerformanceDBA

7
Крім того, не забувайте, що багато баз даних NoSQL - це фактично ті самі бази даних, які ми відкинули 40 років тому. Молоді люди завжди думають, що відкрили щось нове. Фабіан Паскаль: dbdebunk.com/2014/02/thinking-logical-sql-nosql-and.html
N West

3
Агресивний. Це було хорошим рахунком, але агресія та мікроагресія не додають до вмісту чи цінності контенту.
MrMesees

46

Що більшість коментаторів не помічають, - це широкий спектр методологій з'єднання, доступних у складній RDBMS, і денормалізатори незмінно замовчують більш високу вартість ведення денормалізованих даних. Не кожне об'єднання базується на індексах, а в базах даних є багато оптимізованих алгоритмів та методологій приєднання, які призначені для зменшення витрат на приєднання.

У будь-якому випадку, вартість приєднання залежить від його типу та кількох інших факторів. Це зовсім не повинно бути дорогим - деякі приклади.

  • Хеш-з'єднання, в якому об'ємні дані зрівняні, дійсно є дуже дешевим, а вартість набуває значного значення лише в тому випадку, якщо хеш-таблицю неможливо зберегти в пам'яті. Індекс не потрібен. Рівні розподіли між об'єднаними наборами даних можуть стати великою підмогою.
  • Вартість приєднання сортування злиття визначається за рахунок вартості сортування, а не злиття - метод доступу на основі індексу може практично виключати вартість сортування.
  • Вартість приєднання вкладеного циклу до індексу визначається висотою індексу b-дерева та доступом до самого блоку таблиці. Це швидко, але не підходить для масових приєднань.
  • Об'єднання вкладеного циклу на основі кластера набагато дешевше, менша кількість логічного IO'S вимагається на рядок з'єднання - якщо об'єднані таблиці знаходяться в одному кластері, то приєднання стає дуже дешевим через розміщення об'єднаних рядків.

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


Я думаю, що це зводиться до "якщо ви сумніваєтесь, запитайте у DBA". Сучасні бази даних є складними звірами і потребують вивчення, щоб зрозуміти. Я використовую Oracle лише з 1996 року, і це робота на повний робочий день, в курсі нових функцій. SQLserver також дуже дорогий з 2005 року. Це не чорна скринька!
Хлопець

2
Гммм, на моєму скромному досвіді є надто багато DBA, які ніколи не чули про хеш-приєднання, або думають, що вони є Універсально поганою справою.
Девід Олдрідж

28

Я думаю, що все питання ґрунтується на помилковій передумові. Приєднання до великих столів не обов’язково дороге. Насправді, ефективне з'єднання є однією з головних причин, що взагалі існують реляційні бази даних . Приєднання до великих наборів часто коштує дорого, але дуже рідко ви хочете з'єднати весь вміст великої таблиці А з усім вмістом великої таблиці В. Натомість запит записуєте таким чином, що використовуються лише важливі рядки кожної таблиці та фактичний набір, що зберігається з'єднанням, залишається меншим.

Крім того, у вас є ефективність, згадана Пітером Уоне, така, що лише важливі частини кожного запису повинні зберігатись у пам'яті до моменту остаточного набору кінцевих результатів. Крім того, у великих запитах з багатьма об'єднаннями ти зазвичай хочеш починати з менших наборів таблиць і працювати над великими, щоб набір, що зберігається в пам'яті, залишався якомога меншим.

При правильному виконанні з'єднання, як правило, є найкращим способом порівняння, об'єднання чи фільтрування великої кількості даних.


1
@joel. Зворотна також правда. Об'єднання великих наборів даних може бути дорогим, але іноді потрібно, але ви не хочете робити це занадто часто, якщо: a) ви можете обробляти необхідні IO та оперативної пам’яті; б) ви не робите це занадто часто. Розгляньте матеріалізовані погляди, системи звітності, звіти в реальному часі проти звітів КоБ.
Хлопець

11

Вузьке місце - це завжди завжди введення / виведення диска, а ще конкретніше - випадковий диск вводу / виводу (для порівняння, послідовне зчитування відбувається досить швидко і може бути кешоване за допомогою стратегій читання вперед).

Приєднання може збільшити випадкові пошуки - якщо ви стрибаєте, читаючи невеликі частини великої таблиці. Але оптимізатори запитів шукають це і перетворять його на послідовне сканування таблиці (відкидання непотрібних рядків), якщо він вважає, що це буде краще.

Єдина денормована таблиця має подібну проблему - рядки великі, і тому менше вміщуються на одній сторінці даних. Якщо вам потрібні рядки, розташовані далеко від іншого (а великий розмір рядка робить їх ще більше), у вас буде більше випадкових вводу-виводу. Знову-таки, сканування таблиці може змусити цього уникнути. Але цього разу сканування таблиці має прочитати більше даних через великий розмір рядків. Додайте до цього той факт, що ви копіюєте дані з одного місця в декілька локацій, а RDBMS має ще багато чого для читання (та кешування).

За допомогою двох таблиць ви також отримуєте 2 кластеризовані індекси - і, як правило, можуть індексувати більше (через менші накладні витрати / оновлення), що дозволяє вам значно збільшити продуктивність (головним чином, знову ж таки тому, що індекси (відносно) невеликі, швидко зчитувати диск (або дешево кешувати) та зменшити кількість рядків таблиць, які потрібно прочитати з диска).

Про єдині накладні витрати з з'єднанням походить від з'ясування відповідних рядків. Sql Server використовує 3 різних типи об'єднань, в основному на основі розмірів набору даних, щоб знайти відповідні рядки. Якщо оптимізатор вибирає неправильний тип з'єднання (через неточну статистику, неадекватні індекси або просто помилку оптимізатора чи кращий регістр), це може різко вплинути на час запитів.

  • Приєднання до циклу є надзвичайно дешевим для (принаймні 1) невеликого набору даних.
  • Об'єднання злиття спочатку вимагає свого роду обидва набори даних. Якщо ви приєднаєтесь до індексованого стовпця, то індекс вже відсортований і більше не потрібно робити жодної роботи. В іншому випадку у сортуванні є кілька центральних процесорів та пам'яті.
  • Хеш-з'єднання вимагає як пам'яті (для зберігання хештелю), так і процесора (для складання хеша). Знову ж таки, це досить швидко стосовно дискового вводу / виводу. Однак якщо оперативної пам’яті недостатньо для зберігання хештеля, сервер Sql використовуватиме tempdb для зберігання частин хештелю та знайдених рядків, а потім обробляє лише частини хештеля за раз. Як і всі диски, це досить повільно.

В оптимальному випадку вони не викликають вводу / виводу диска - і тому незначні з точки зору продуктивності.

Загалом, в гіршому випадку - насправді має бути швидше зчитувати таку ж кількість логічних даних з x приєднаних таблиць, як це з однієї денормалізованої таблиці через менших читання дисків. Для того, щоб прочитати однакову кількість фізичних даних, може виникнути невелике накладне покриття.

Оскільки час запиту зазвичай переважає за витратами на введення / виведення, а розмір ваших даних не змінюється (мінус деякі дуже дрібні накладні рядки) з денормалізацією, не просто отримати велику користь від простого злиття таблиць разом. Тип денормалізації, що має тенденцію до підвищення продуктивності, IME, кешує обчислені значення замість зчитування 10000 рядків, необхідних для їх обчислення.


Скорочення випадкових пошуків: хороша точка, хоча хороший RAID-контролер з великим кешем зробить ліфт читанням / записом.
Пітер Вун

3

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

Для деяких баз даних це не має значення, наприклад, MS SQL знає правильний порядок з'єднання більшу частину часу. Для деяких (наприклад, IBM Informix) порядок робить усе різницю.


1
Взагалі на пристойний оптимізатор запитів не впливатиме порядок переліку з'єднань або таблиць, і він сам визначить найбільш ефективний спосіб виконати з'єднання.
Девід Олдрідж

5
MySQL, Oracle, SQL Server, Sybase, postgreSQL тощо. дбайте не про порядок приєднань. Я працював з DB2, і це, наскільки мені відомо, не байдуже, в якому порядку ви їх розмістили. Це не корисна порада в загальному випадку
Метт Рогіш

Кластеризація MySQL за допомогою двигуна NDB (правда, кращий випадок, і лише просунуті розробники збираються поряд з NDB) не вгадує належним чином порядок приєднання, тому вам доведеться додавати заяви "USE INDEX" до більшості об'єднаних запитів, інакше вони будуть бути жахливо неефективним. Документи MySQL охоплюють це.
joelhardi

@iiya, розуміння того, що вибере оптимізатор, важливіше, ніж узагальнені твердження чи "міфи" щодо упорядкування таблиць. Не покладайтеся на певну хитрість у вашому SQL, оскільки поведінка часто змінюється під час оновлення RDBMS. Oracle декілька разів змінював поведінку, починаючи з v7.
Хлопець

1
@Matt Я бачив, що Oracle 9i виконує дуже різні оптимізації та плани запитів, просто коригуючи порядок приєднання. Може, це змінилося від версії 10i і далі?
Каміло Діас Репка

0

Вирішення питання про те, чи денормалізувати чи нормалізувати, є досить простим процесом, коли враховувати клас складності приєднання. Наприклад, я схильний проектувати свої бази даних з нормалізацією, коли запити є O (k log n), де k відносно бажаної величини виводу.

Простий спосіб денормалізації та оптимізації продуктивності - це думати про те, як зміни вашої нормалізованої структури впливають на вашу денормалізовану структуру. Однак це може бути проблематично, оскільки може знадобитися транзакційна логіка для роботи над денормалізованою структурованою.

Дебати про нормалізацію та денормалізацію не закінчаться, оскільки проблеми величезні. Існує багато проблем, коли природне рішення вимагає обох підходів.

Як правило, я завжди зберігав нормалізовану структуру та денормалізовані кеші, які можна реконструювати. Зрештою, ці кеші врятують мою дупу, щоб вирішити майбутні проблеми нормалізації.


-8

Розвиваючи те, що сказали інші,

Приєднання - це лише декартові вироби з деяким блиском. {1,2,3,4} X {1,2,3} дасть нам 12 комбінацій (nXn = n ^ 2). Цей обчислюваний набір виступає як орієнтир, щодо якого застосовуються умови. СУБД застосовує умови (наприклад, коли ліворуч і праворуч 2 або 3), щоб дати нам відповідні умови. Насправді вона більш оптимізована, але проблема та сама. Зміна розміру наборів збільшить розмір результату експоненціально. Кількість споживаних циклів пам'яті та процесорних процесів здійснюється в експоненціальному вираженні.

Коли ми денормалізуємось, ми взагалі уникаємо цього обчислення, думаємо про кольоровий клей, прикріплений до кожної сторінки вашої книги. Ви можете зробити висновок про інформацію за допомогою посилання. Штраф, який ми сплачуємо, полягає в тому, що ми порушуємо суть СУБД (оптимальна організація даних)


3
-1: Цей пост є чудовим прикладом того, чому ви дозволяєте СУБД виконувати приєднання - адже дизайнери СУБД постійно думають над цими питаннями і придумують більш ефективні способи зробити це, ніж метод compsci 101.
Девід Олдрідж

2
@David: Погоджено. Програмісти оптимізатора СУБД - це декілька розумних файлів cookie
Метт Рогіш

Ця відповідь невірна. Якщо ваш запит виконаний на основі нормалізованої, індексованої бази даних і має будь-який вид фільтра або умови приєднання, оптимізатор знайде спосіб уникнути декартового продукту та мінімізувати використання пам’яті та цикли процесора. Якщо ви дійсно маєте намір вибрати декартовий продукт, ви будете використовувати ту саму пам'ять у нормалізованому або денормалізованому db.
rileymcdowell
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.