Преимущества гибкой типизации
Содержание
1. Введение
SQLite предоставляет разработчикам возможность хранить данные в любом желаемом формате, независимо от объявленного типа данных столбца. Некоторые считают эту функцию проблематичной. Некоторые разработчики с удивлением обнаруживают, что можно вставить текст в столбец, помеченный как INTEGER.
В этой статье обосновывается гибкость правил типизации в SQLite.
2. О гибкой типизации
Подробности о гибкой системе типов SQLite см. в отдельном документе Типы данных в SQLite. Вот краткий обзор:
Имена типов данных в определениях столбцов необязательны. Определение столбца может состоять только из имени столбца.
Если имена типов данных указаны, они могут быть практически любым текстом. SQLite пытается вывести предпочтительный тип данных для столбца на основе имени типа данных в определении столбца, но этот предпочтительный тип данных является рекомендацией, а не обязательным. Предпочтительный тип данных известен как «сродство столбца».
Делается попытка преобразовать входящие данные в предпочтительный тип данных столбца. (Все СУБД SQL это делают, а не только SQLite.) Если преобразование успешно, все в порядке. Но если преобразование не удается, вместо того, чтобы выдать ошибку, SQLite просто хранит содержимое с использованием его исходного типа данных.
-
Вышесказанное может привести к ситуациям, которые сторонники жёсткой типизации находят неудобными:
Тип данных столбца Допустимые типы в этом столбце INTEGER INTEGER, REAL, TEXT, BLOB REAL REAL, TEXT, BLOB TEXT TEXT, BLOB BLOB INTEGER, REAL, TEXT, BLOB Обратите внимание, что целое число или вещественное значение никогда не будут храниться в текстовом столбце, так как целое число или вещественное значение всегда можно преобразовать в его эквивалентное текстовое представление. Аналогично, целое число никогда не будет храниться в вещественном столбце, потому что оно всегда будет преобразовано в вещественное. Но текст не всегда выглядит как целое число или вещественное значение, и поэтому не всегда может быть преобразован. А BLOB не может быть преобразован ни во что, и ничего другого нельзя преобразовать в BLOB.
3. Случаи, где гибкая типизация полезна
Некоторые читатели, впервые столкнувшись с гибкой типизацией в SQLite, задаются вопросом: «Как это может быть полезным?». Вот попытка ответить на этот вопрос:
3.1. Таблицы атрибутов
Многие приложения, особенно те, которые используют SQLite в качестве формата файлов приложения, нуждаются в месте для хранения различных атрибутов, таких как миниатюры изображений (как значения BLOB), короткие текстовые фрагменты (например, имя пользователя), а также числовые, даты и JSON-значения. Удобно создать одну таблицу для обработки такого хранения:
CREATE TABLE attribute(name TEXT PRIMARY KEY, value) WITHOUT ROWID;
Без гибкой типизации такая таблица потребовала бы более сложной структуры, с отдельными столбцами для каждого возможного типа данных. Гибкая типизация столбца «значение» упрощает таблицу концептуально, экономит место и облегчает доступ и обновление.
В системе управления версиями Fossil каждый репозиторий имеет таблицу CONFIG, используемую для хранения всех видов настроек с любым возможным типом данных. Пользовательский конфигурационный файл Fossil (файл ~/.fossil) — это отдельная база данных SQLite, содержащая одну таблицу атрибутов, которая хранит пользовательское состояние в рамках всех репозиториев.
Некоторые приложения используют базу данных SQLite как чистый хранилище ключ-значение. Схема базы данных содержит одну таблицу, которая выглядит примерно так:
CREATE TABLE storage(name TEXT PRIMARY KEY, value ANYTHING);
3.2. Столбец «значение» вывода из виртуальных таблиц json_tree
Таблица-функции json_tree и json_each, встроенные в SQLite, обе имеют столбец «значение», который может содержать значения типа INTEGER, REAL или TEXT в зависимости от типа соответствующего поля JSON. Например:
SELECT typeof(value) FROM json_each('{"a":1,"b":2.5,"c":"hello"}');
Вышеприведённый запрос возвращает три строки одного столбца со значениями «целое число», «вещественное число» и «текст» соответственно.
3.3. Хранение необработанных данных
Аналитики иногда сталкиваются с CSV-файлами, в которых некоторые столбцы содержат смесь целых чисел, вещественных чисел и текстовых данных. Например, CSV-файлы, полученные из экспорта электронных таблиц Excel, часто обладают этой чертой. При импорте таких «необработанных данных» в базу данных SQL удобно иметь гибко типизированные столбцы для импорта.
Конечно, необработанные данные не ограничиваются CSV-файлами из Excel. Существует множество источников данных, в которых один столбец может содержать смесь типов. Например, столбец данных может иногда содержать количество секунд с 1970 года, а в других случаях — текстовую строку даты. Желательно очистить эти несогласованные представления, но при этом удобно хранить все разные представления в одном столбце промежуточной базы данных, пока идёт очистка.
3.4. Языки динамического программирования
SQLite начинался как расширение TCL, которое позже стало самостоятельным продуктом. TCL — это динамический язык в том смысле, что программист не обязан знать о типах данных. Под капотом TCL тщательно отслеживает тип каждого значения, но для разработчика и пользователя программы TCL всё выглядит как строка. Гибкая типизация хорошо подходит для использования с динамическими языками программирования, такими как TCL и другими, так как с динамическим языком программирования нельзя всегда заранее предсказать, какой тип данных будет у переменной. Поэтому, когда вам нужно сохранить значение этой переменной в базе данных, наличие базы данных, поддерживающей гибкую типизацию, значительно упрощает хранение.
3.5. Перекрестная совместимость имен типов данных
Кажется, у каждой СУБД SQL свой собственный набор поддерживаемых имён типов данных:
- BIGINT
- UNSIGNED SMALL INT
- TEXT
- VARCHAR
- VARYING CHARACTER
- NATIONAL VARYING CHARACTER
- NVARCHAR
- JSON
- REAL
- FLOAT
- DOUBLE PRECISION
- ... и так далее ...
Тот факт, что SQLite примет любое из этих имён как допустимое имя типа и позволит вам хранить любые данные в столбце, увеличивает вероятность того, что скрипт, написанный для работы в какой-то другой СУБД SQL, также будет работать в SQLite.
3.6. Переназначение неиспользуемых или устаревших столбцов в базах данных предыдущих версий
Поскольку файл базы данных SQLite — это один файл на диске, некоторые приложения используют SQLite как формат файла приложения. Это означает, что один экземпляр приложения может в течение своего жизненного цикла взаимодействовать со сотнями или тысячами отдельных баз данных, каждая в отдельном файле. Когда такие приложения развиваются в течение многих лет, значение некоторых столбцов в базовой базе данных будет слегка меняться. Или может быть целесообразно переназначить существующий столбец для выполнения двух или более задач. Это намного проще сделать, если столбец имеет гибкий тип данных.
4. Предполагаемые недостатки гибкой типизации (с опровержениями)
Следующие предполагаемые недостатки гибкой типизации были собраны из бесчисленных сообщений на Hacker News и Reddit, и подобных форумов, где разработчики обсуждают подобные вещи. Если вы можете придумать другие причины, по которым гибкая типизация — плохая идея, свяжитесь с разработчиками SQLite или оставьте сообщение на форуме SQLite, чтобы ваша идея была добавлена в список.
4.1. Мы никогда так не делали
Многие скептики по поводу гибкой типизации просто выражают удивление и недоверие, не предлагая никаких обоснований, почему они считают гибкую типизацию плохой идеей. Без аргументации, нужно предположить, что их неприятие гибкой типизации связано с тем, что она отличается от того, к чему они привыкли.
Вероятно, многие разработчики, которые возмущены гибкой типизацией SQLite, чувствуют это так, потому что они просто никогда раньше не сталкивались с чем-то подобным. Все предыдущие контакты с базами данных, особенно базами данных SQL, предполагали жёсткую типизацию, и в ментальной модели читателя SQL жёсткая типизация является фундаментальной характеристикой. Гибкая типизация нарушает их мировоззрение.
Да, гибкая типизация — это новый подход к данным в базе данных SQL. Но новое не обязательно плохо. Иногда, особенно в случае с гибкой типизацией, инновации ведут к улучшению.
4.2. Жесткая проверка типов помогает предотвратить ошибки в приложениях
Многие программисты считают, что наилучший способ предотвращения ошибок в приложениях — жёсткая проверка типов. Но я не нашёл никаких доказательств в поддержку этой точки зрения.
Чтобы убедиться, строгая проверка типов помогает предотвратить некоторые виды ошибок в языках низкого уровня, таких как C и C++, которые представляют модель, близкую к аппаратной части машины. Но это, кажется, не так для языков с более высокой абстракцией, в которых все данные передаются в суперклассе «Значение» (Value) какого-то рода, который подклассифицируется для различных типов данных более низкого уровня. Когда все является объектом Value, конкретные типы данных перестают быть важными.
Эта техническая заметка написана автором оригинальной реализации SQLite. Я пишу программы TCL в течение 27 лет. TCL не имеет никакой проверки типов. Класс «Значение» (Value) в TCL (называемый Tcl_Obj) может содержать множество различных типов данных, но он представляет содержимое программе и пользователю приложения в виде строки. И у меня было много ошибок в этих программах TCL на протяжении многих лет. Но я не припомню ни одного случая, когда ошибки могли бы быть пойманы жёсткой системой типов. Я также написал много кода на C за 35 лет, в том числе сам SQLite. Я обнаружил, что система типов в C очень полезна для поиска и предотвращения проблем. Для системы контроля версий Fossil, которая написана на C, я даже реализовал дополнительные программы статического анализа, которые сканируют исходный код Fossil перед компиляцией, ищут проблемы, которые компиляторы пропускают. Это хорошо работает для компилируемых программ.
Модель языка SQL — более высокая абстракция, чем C/C++. В SQLite каждый элемент данных хранится в памяти как объект «sqlite3_value». Существуют подклассы этого объекта для строк, целых чисел, чисел с плавающей точкой, блобов и других представлений. Всё передаётся внутри языка SQL, реализованного в SQLite, как объекты «sqlite3_value», так что базовый тип данных на самом деле не важен. Я никогда не находил жёсткой проверки типов полезной в языках, таких как TCL и SQLite, которые имеют один суперкласс «Значение» (Value), используемый для представления любого элемента данных. Fossil широко использует SQLite в своей реализации. За 14 лет существования Fossil было много ошибок, но я не могу припомнить ни одной ошибки, которая могла бы быть предотвращена жёсткой проверкой типов в SQLite. Некоторые ошибки языка C могли бы быть пойманы с помощью лучшей проверки типов (поэтому я написал дополнительные сканеры исходного кода), но не ошибки SQL.
Основываясь на многолетнем опыте, я отвергаю тезис о том, что жёсткая проверка типов помогает предотвратить ошибки приложений. Я приму и поверю в слегка изменённый тезис: жёсткая проверка типов помогает предотвратить ошибки приложений в языках, которые не имеют одного суперкласса верхнего уровня «Значение» (Value). Но в SQLite есть единственный суперкласс «sqlite3_value», поэтому это изречение не применяется.
4.3. Жёсткая проверка типов предотвращает загрязнение данных
Некоторые люди утверждают, что если у вас есть жёсткие ограничения на схему, а особенно строгая проверка типов столбцов, это поможет предотвратить добавление неправильных данных в базу данных. Это не так. Действительно, проверка типов может помочь предотвратить грубые ошибки данных, которые попадают в систему. Но проверка типов не помогает предотвратить тонкие ошибки данных, которые записываются.
Например, жёсткая проверка типов может успешно предотвратить вставку имени клиента (текст) в столбец Customer.creditScore (целое число). С другой стороны, если эта ошибка произойдёт, то найти проблему и все затронутые строки очень легко. Но проверка типов не поможет предотвратить ошибку, где фамилия и имя клиента переставлены местами, так как оба являются текстовыми полями.
Подавляя легко обнаруживаемые ошибки и пропускаю только трудно обнаруживаемые ошибки, жёсткая проверка типов на самом деле может затруднить поиск и исправление ошибок. Ошибки данных имеют тенденцию к группированию. Если у вас 20 различных источников данных, большинство ошибок данных, как правило, поступает только из 2–3 из этих источников. Наличие грубых ошибок (например, текст в столбце целых чисел) является удобным ранним сигналом о том, что что-то не так. Источник проблемы можно быстро отследить и применить дополнительный контроль к источнику грубых ошибок, тем самым, надеясь также исправить и тонкие ошибки. Когда грубые ошибки подавляются, вы теряете важный сигнал, который помогает обнаружить и исправить тонкие ошибки.
Ошибки данных неизбежны. Они будут происходить независимо от того, насколько жёсткой будет проверка типов. Жёсткая проверка типов может поймать только небольшую подмножество этих случаев — самые очевидные. Она ничего не делает, чтобы помочь найти и исправить более тонкие случаи. И, подавляя сигнал о том, какие источники данных являются проблематичными, она иногда может затруднить поиск тонких ошибок.
4.4. Другие СУБД SQL работают по-другому
Поскольку SQLite менее жёсткий и позволяет делать больше, SQL-скрипты, которые работают с другими СУБД, обычно также будут работать и с SQLite, но скрипты, первоначально написанные для SQLite, могут не работать с более жёсткими СУБД. Это может вызвать проблемы, когда разработчики используют SQLite для прототипирования и тестирования, а затем мигрируют своё приложение в более жёсткую SQL-систему для развертывания. Если приложение (неосознанно) использовало гибкость типов, доступную в SQLite, то оно потерпит неудачу при миграции.
Люди используют эту проблему, чтобы утверждать, что SQLite должен быть более жёстким относительно типов данных. Но вы можете так же легко повернуть этот аргумент и сказать, что другие СУБД должны быть более гибкими по отношению к типам данных. В конце концов, приложение работало правильно в SQLite до миграции. Если жёсткая проверка типов действительно так полезна, то почему она сломала приложение, которое работало ранее?
5. Если вы настаиваете на жёсткой проверке типов...
Начиная с версии SQLite 3.37.0 (2021-11-27), SQLite поддерживает этот стиль разработки, используя строгие таблицы (STRICT tables).
Если вы обнаружите реальный случай, когда строгие таблицы предотвратили или могли бы предотвратить ошибку в приложении, пожалуйста, отправьте сообщение на форум SQLite, чтобы мы могли добавить вашу историю в этот документ.
6. Примите свободу
Если гибкость типов в базе данных SQL для вас нова, я рекомендую вам попробовать. Вероятно, это не вызовет у вас никаких проблем, и это может сделать вашу программу проще и удобнее в написании и обслуживании. Я думаю, что даже если вы сначала скептически настроены, если вы просто попробуете гибкие типы, вы, в конечном счёте, поймёте, что это лучший подход и начнёте поощрять других поставщиков баз данных к поддержке по крайней мере типа ANY, а не полной гибкости типов в стиле SQLite.
В большинстве случаев гибкость типов не важна, потому что столбец хранит один хорошо определённый тип. Но иногда вы столкнётесь с ситуациями, где наличие гибкой системы типов делает решение вашей проблемы более чистым и простым.
Эта страница была в последний раз изменена 14 июля 2024 г. 22:39:43 по UTC
SQLite is in the Public Domain.
https://sqlite.org/flextypegood.html