Хранение данных | Tarantool
Документация на русском языке
поддерживается сообществом
Concepts Модель данных Хранение данных

Хранение данных

Tarantool обрабатывает данные в виде кортежей.

кортеж

Кортеж — это группа значений данных в памяти Tarantool. По сути, это «запись в базе данных» или «строка». Значения данных в кортеже называются полями.

Когда Tarantool выводит значение кортежа в консоль, по умолчанию используется формат YAML, например: [3, 'Ace of Base', 1993].

В Tarantool кортежи хранятся в виде массивов в формате MsgPack.

поле

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

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

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

Номер поля служит его идентификатором. В Lua и некоторых других языках нумерация начинается с 1, в других — с 0 (например, в PHP или C/C++). Таким образом, в некоторых контекстах у первого поля кортежа будет индекс 1 или 0.

Tarantool stores tuples in containers called spaces.

спейс

В Tarantool спейс — это первичный контейнер, хранящий данные. Он похож на таблицы в реляционных базах данных. Спейсы содержат кортежи — так в Tarantool называются записи в базе данных. Количество кортежей в спейсе не ограничено.

Для хранения данных с помощью Tarantool требуется хотя бы один спейс. У каждого спейса есть следующие атрибуты:

  • уникальное имя, указанное пользователем;
  • уникальный числовой идентификатор, обычно Tarantool назначает его автоматически, но пользователь может его указать сам, если посчитает нужным;
  • движок: memtx (по умолчанию) — движок «in-memory», быстрый, но ограниченный в размере, или vinyl — дисковый движок для огромных наборов данных.

Для работы спейсу нужен первичный индекс. Также он может использовать вторичные индексы.

Tarantool представляет собой систему управления базой данных и сервер приложений одновременно. Поэтому разработчику часто приходится работать с двумя системами типов данных: типы языка программирования (например, Lua) и типы формата хранения данных Tarantool (MsgPack).

Скалярный / составной MsgPack type Lua-тип Пример значения
скалярный nil cdata box.NULL
скалярный boolean boolean true
скалярный string string 'A B C'
скалярный integer number 12345
скалярный integer cdata 12345
скалярный float64 (double) number 1.2345
скалярный float64 (double) cdata 1.2345
скалярный binary cdata [!!binary 3t7e]
скалярный ext (for Tarantool decimal) cdata 1.2
скалярный ext (for Tarantool datetime) cdata '2021-08-20T16:21:25.122999906 Europe/Berlin'
скалярный ext (for Tarantool interval) cdata +1 months, 1 days
скалярный ext (for Tarantool uuid) cdata 12a34b5c-de67-8f90-123g-h4567ab8901
составной map (ассоциативный массив) table (with string keys) {'a': 5, 'b': 6}
составной array (массив) table (with integer keys) [1, 2, 3, 4, 5]
составной array (массив) tuple (cdata) [12345, 'A B C']

Примечание

Данные в формате MsgPack имеют переменный размер. Так, например, для наименьшего значения number потребуется только один байт, a для наибольшего потребуется девять байтов.

Примечание

The Lua nil type is encoded as MsgPack nil but decoded as msgpack.NULL.

In Lua, the nil type has only one possible value, also called nil. Tarantool displays it as null when using the default YAML format. Nil may be compared to values of any types with == (is-equal) or ~= (is-not-equal), but other comparison operations will not work. Nil may not be used in Lua tables; the workaround is to use box.NULL because nil == box.NULL is true. Example: nil.

A boolean is either true or false.

Example: true.

The Tarantool integer type is for integers between -9223372036854775808 and 18446744073709551615, which is about 18 quintillion. This type corresponds to the number type in Lua and to the integer type in MsgPack.

Example: -2^63.

The Tarantool unsigned type is for integers between 0 and 18446744073709551615. So it is a subset of integer.

Example: 123456.

The double field type exists mainly to be equivalent to Tarantool/SQL’s DOUBLE data type. In msgpuck.h (Tarantool’s interface to MsgPack), the storage type is MP_DOUBLE and the size of the encoded value is always 9 bytes. In Lua, fields of the double type can only contain non-integer numeric values and cdata values with double floating-point numbers.

Examples: 1.234, -44, 1.447e+44.

To avoid using the wrong kind of values inadvertently, use ffi.cast() when searching or changing double fields. For example, instead of space_object:insert{value} use ffi = require('ffi') ... space_object:insert({ffi.cast('double',value)}).

Example:

s = box.schema.space.create('s', {format = {{'d', 'double'}}})
s:create_index('ii')
s:insert({1.1})
ffi = require('ffi')
s:insert({ffi.cast('double', 1)})
s:insert({ffi.cast('double', tonumber('123'))})
s:select(1.1)
s:select({ffi.cast('double', 1)})

Арифметические операции с cdata формата double работают ненадёжно, поэтому для Lua лучше использовать тип number. Это не относится к Tarantool/SQL, так как Tarantool/SQL применяет неявное приведение типов.

The Tarantool number field may have both integer and floating-point values, although in Lua a number is a double-precision floating-point.

Tarantool по возможности сохраняет числа языка Lua в виде чисел с плавающей запятой, если числовое значение содержит десятичную запятую или если оно очень велико (более 100 триллионов = 1e14). В противном случае Tarantool сохраняет такое значение в виде целого числа. Чтобы даже очень большие величины гарантированно сохранялись как целые числа, используйте функцию tonumber64 или приписывайте в конце суффикс LL (Long Long) или ULL (Unsigned Long Long). Вот примеры записи чисел в обычном представлении, экспоненциальном, с суффиксом ULL и с использованием функции tonumber64: −55, −2.7e+20, 100000000000000ULL, tonumber64('18446744073709551615').

You can also use the ffi module to specify a C type to cast the number to. In this case, the number will be stored as cdata.

The Tarantool decimal type is stored as a MsgPack ext (Extension). Values with the decimal type are not floating-point values although they may contain decimal points. They are exact with up to 38 digits of precision.

Example: a value returned by a function in the decimal module.

Introduced in v. 2.10.0. The Tarantool datetime type facilitates operations with date and time, accounting for leap years or the varying number of days in a month. It is stored as a MsgPack ext (Extension). Operations with this data type use code from c-dt, a third-party library.

For more information, see Module datetime.

Since: v. 2.10.0

The Tarantool interval type represents periods of time. They can be added to or subtracted from datetime values or each other. Operations with this data type use code from c-dt, a third-party library. The type is stored as a MsgPack ext (Extension). For more information, see Module datetime.

A string is a variable-length sequence of bytes, usually represented with alphanumeric characters inside single quotes. In both Lua and MsgPack, strings are treated as binary data, with no attempts to determine a string’s character set or to perform any string conversion – unless there is an optional collation. So, usually, string sorting and comparison are done byte-by-byte, without any special collation rules applied. For example, numbers are ordered by their point on the number line, so 2345 is greater than 500; meanwhile, strings are ordered by the encoding of the first byte, then the encoding of the second byte, and so on, so '2345' is less than '500'.

Example: 'A, B, C'.

A bin (binary) value is not directly supported by Lua but there is a Tarantool type varbinary which is encoded as MsgPack binary. For an (advanced) example showing how to insert varbinary into a database, see the Cookbook Recipe for ffi_varbinary_insert.

Example: "\65 \66 \67".

The Tarantool uuid type is used for Universally Unique Identifiers. Since version 2.4.1 Tarantool stores uuid values as a MsgPack ext (Extension).

Example: 64d22e4d-ac92-4a23-899a-e5934af5479.

An array is represented in Lua with {...} (braces).

Examples: lists of numbers representing points in geometric figures: {10, 11}, {3, 5, 9, 10}.

Lua tables with string keys are stored as MsgPack maps; Lua tables with integer keys starting with 1 are stored as MsgPack arrays. Nils may not be used in Lua tables; the workaround is to use box.NULL.

Example: a box.space.tester:select() request will return a Lua table.

A tuple is a light reference to a MsgPack array stored in the database. It is a special type (cdata) to avoid conversion to a Lua table on retrieval. A few functions may return tables with multiple tuples. For tuple examples, see box.tuple.

Values in a scalar field can be boolean, integer, unsigned, double, number, decimal, string, uuid, or varbinary; but not array, map, or tuple.

Examples: true, 1, 'xxx'.

Values in a field of this type can be boolean, integer, unsigned, double, number, decimal, string, uuid, varbinary, array, map, or tuple.

Examples: true, 1, 'xxx', {box.NULL, 0}.

Примеры запросов вставки с разными типами полей:

tarantool> box.space.K:insert{1,nil,true,'A B C',12345,1.2345}
---
- [1, null, true, 'A B C', 12345, 1.2345]
...
tarantool> box.space.K:insert{2,{['a']=5,['b']=6}}
---
- [2, {'a': 5, 'b': 6}]
...
tarantool> box.space.K:insert{3,{1,2,3,4,5}}
---
- [3, [1, 2, 3, 4, 5]]
...

О том, какие значения можно хранить в индексированных полях, читайте в разделе об индексах»

Когда Tarantool сравнивает строки, по умолчанию он использует двоичные параметры сортировки (binary collation). При этом он учитывает только числовое значение каждого байта в строке. Например, код символа 'A' (раньше называлась «значение ASCII») — число 65, код 'B' — число 66, а код 'a' – число 98. Поэтому 'A' < 'B' < 'a', если строка закодирована в ASCII или UTF-8.

Двоичная сортировка — лучший выбор для быстрого детерминированного простого обслуживания и поиска с использованием индексов Tarantool.

Но если вы хотите такое упорядочение, как в телефонных справочниках и словарях, то вам нужна одна из дополнительных сортировок Tarantool: unicode или unicode_ci. Они обеспечивают 'a' < 'A' < 'B' и 'a' == 'A' < 'B' соответственно.

Дополнительные виды сортировки unicode и unicode_ci обеспечивают упорядочение в соответствии с Таблицей сортировки символов Юникода по умолчанию (DUCET) и правилами, указанными в Техническом стандарте Юникода №10 – Алгоритм сортировки по Юникоду (Unicode® Technical Standard #10 Unicode Collation Algorithm (UTS #10 UCA)). Единственное отличие между двумя видами сортировки — вес:

  • сортировка unicode принимает во внимание уровни веса L1, L2 и L3 (уровень = „tertiary“, третичный);
  • сортировка unicode_ci принимает во внимание только вес L1 (уровень = „primary“, первичный), поэтому, например, 'a' == 'A' == 'á' == 'Á'.

Для примера возьмем некоторые русские слова:

'ЕЛЕ'
'елейный'
'ёлка'
'еловый'
'елозить'
'Ёлочка'
'ёлочный'
'ЕЛь'
'ель'

…и покажем разницу в упорядочении и выборке по индексу:

  • с сортировкой по unicode:

    tarantool> box.space.T:create_index('I', {parts = {{field = 1, type = 'str', collation='unicode'}}})
    ...
    tarantool> box.space.T.index.I:select()
    ---
    - - ['ЕЛЕ']
      - ['елейный']
      - ['ёлка']
      - ['еловый']
      - ['елозить']
      - ['Ёлочка']
      - ['ёлочный']
      - ['ель']
      - ['ЕЛь']
    ...
    tarantool> box.space.T.index.I:select{'ЁлКа'}
    ---
    - []
    ...
    
  • с сортировкой по unicode_ci:

    tarantool> box.space.T:create_index('I', {parts = {{field = 1, type ='str', collation='unicode_ci'}}})
    ...
    tarantool> box.space.T.index.I:select()
    ---
    - - ['ЕЛЕ']
      - ['елейный']
      - ['ёлка']
      - ['еловый']
      - ['елозить']
      - ['Ёлочка']
      - ['ёлочный']
      - ['ЕЛь']
    ...
    tarantool> box.space.T.index.I:select{'ЁлКа'}
    ---
    - - ['ёлка']
    ...
    

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

Для английского, русского и большинства других языков используйте «unicode» и «unicode_ci». Если вам нужно, чтобы у кириллических букв „Е“ и „Ё“ веса 1 уровня были одинаковыми, попробуйте киргизскую сортировку.

Специализированные дополнительные виды сортировки: Для других языков Tarantool предлагает специализированные виды сортировки для любого современного языка, на котором говорят более миллиона человек. Кроме того, специализированные дополнительные виды сортировки возможны для особых случаев, когда слова в словаре упорядочиваются не так, как в телефонном справочнике. Чтобы увидеть полный список, выполните команду box.space._collation:select().

Названия специализированных видов сортировки имеют вид unicode_[language code]_[strength]], где language code — это стандартный код языка из 2 или 3 символов, а значение strength может быть s1 для уровня «primary» (вес уровня 1), s2 для уровня «secondary», s3 для уровня «tertiary». Tarantool использует те же коды языков, что указаны в списке специализированных вариантов языковых настроек на страницах руководств по Ubuntu и Fedora. Схемы, в деталях объясняющие отличия от упорядочения по DUCET, можно найти в Общем репозитории языковых данных (Common Language Data Repository).

For better control over stored data, Tarantool supports constraints – user-defined limitations on the values of certain fields or entire tuples. Together with data types, constraints allow limiting the ranges of available field values both syntactically and semantically.

For example, the field age typically has the number type, so it cannot store strings or boolean values. However, it can still have values that don’t make sense, such as negative numbers. This is where constraints come to help.

There are two types of constraints in Tarantool:

  • Field constraints check that the value being assigned to a field satisfies a given condition. For example, age must be non-negative.
  • Tuple constraints check complex conditions that can involve all fields of a tuple. For example, a tuple contains a date in three fields: year, month, and day. You can validate day values based on the month value (and even year if you consider leap years).

Field constraints work faster, while tuple constraints allow implementing a wider range of limitations.

Constraints use stored Lua functions or SQL expressions, which must return true when the constraint is satisfied. Other return values (including nil) and exceptions make the check fail and prevent tuple insertion or modification.

To create a constraint function, call box.schema.func.create() with the function definition specified in the body attribute.

Constraint functions take two parameters:

  • The tuple and the constraint name for tuple constraints.

    -- Define a tuple constraint function --
    box.schema.func.create('check_person', {
        language = 'LUA',
        is_deterministic = true,
        body = 'function(t, c) return (t.age >= 0 and #(t.name) > 3) end'
    })
    

    Предупреждение

    Tarantool doesn’t check field names used in tuple constraint functions. If a field referenced in a tuple constraint gets renamed, this constraint will break and prevent further insertions and modifications in the space.

  • The field value and the constraint name for field constraints.

    -- Define a field constraint function --
    box.schema.func.create('check_age', {
        language = 'LUA',
        is_deterministic = true,
        body = 'function(f, c) return (f >= 0 and f < 150) end'
    })
    

To create a constraint in a space, specify the corresponding function’s name in the constraint parameter:

  • Tuple constraints: when creating or altering a space.

    -- Create a space with a tuple constraint --
    customers = box.schema.space.create('customers', {constraint = 'check_person'})
    
  • Field constraints: when setting up the space format.

    -- Specify format with a field constraint --
    box.space.customers:format({
        {name = 'id', type = 'number'},
        {name = 'name', type = 'string'},
        {name = 'age',  type = 'number', constraint = 'check_age'},
    })
    

In both cases, constraint can contain multiple function names passed as a tuple. Each constraint can have an optional name:

-- Create one more tuple constraint --
box.schema.func.create('another_constraint',
    {language = 'LUA', is_deterministic = true, body = 'function(t, c) return true end'})

-- Set two constraints with optional names --
box.space.customers:alter{
    constraint = { check1 = 'check_person', check2 = 'another_constraint'}
}

Примечание

When adding a constraint to an existing space with data, Tarantool checks it against the stored data. If there are fields or tuples that don’t satisfy the constraint, it won’t be applied to the space.

Foreign keys provide links between related fields, therefore maintaining the referential integrity of the database.

Fields can contain values that exist only in other fields. For example, a shop order always belongs to a customer. Hence, all values of the customer field of the orders space must also exist in the id field of the customers space. In this case, customers is a parent space for orders (its child space). When two spaces are linked with a foreign key, each time a tuple is inserted or modified in the child space, Tarantool checks that a corresponding value is present in the parent space.

../../../_images/foreign_key.svg

Примечание

A foreign key can link a field to another field in the same space. In this case, the child field must be nullable. Otherwise, it is impossible to insert the first tuple in such a space because there is no parent tuple to which it can link.

There are two types of foreign keys in Tarantool:

  • Field foreign keys check that the value being assigned to a field is present in a particular field of another space. For example, the customer value in a tuple from the orders space must match an id stored in the customers space.
  • Tuple foreign keys check that multiple fields of a tuple have a match in another space. For example, if the orders space has fields customer_id and customer_name, a tuple foreign key can check that the customers space contains a tuple with both these values in the corresponding fields.

Field foreign keys work faster while tuple foreign keys allow implementing more strict references.

Важно

For each foreign key, there must exist a parent space index that includes all its fields.

To create a foreign key in a space, specify the parent space and linked fields in the foreign_key parameter. Parent spaces can be referenced by name or by id. When linking to the same space, the space can be omitted. Fields can be referenced by name or by number:

  • Field foreign keys: when setting up the space format.

    -- Create a space with a field foreign key --
    box.schema.space.create('orders')
    
    box.space.orders:format({
        {name = 'id',   type = 'number'},
        {name = 'customer_id', foreign_key = {space = 'customers', field = 'id'}},
        {name = 'price_total', type = 'number'},
    })
    
  • Tuple foreign keys: when creating or altering a space. Note that for foreign keys with multiple fields there must exist an index that includes all these fields.

    -- Create a space with a tuple foreign key --
    box.schema.space.create("orders", {
        foreign_key = {
            space = 'customers',
            field = {customer_id = 'id', customer_name = 'name'}
        }
    })
    
    box.space.orders:format({
        {name = "id", type = "number"},
        {name = "customer_id" },
        {name = "customer_name"},
        {name = "price_total", type = "number"},
    })
    

Примечание

Type can be omitted for foreign key fields because it’s defined in the parent space.

Foreign keys can have an optional name.

-- Set a foreign key with an optional name --
box.space.orders:alter{
    foreign_key = {
        customer = {
            space = 'customers',
            field = { customer_id = 'id', customer_name = 'name'}
        }
    }
}

A space can have multiple tuple foreign keys. In this case, they all must have names.

-- Set two foreign keys: names are mandatory --
box.space.orders:alter{
    foreign_key = {
        customer = {
            space = 'customers',
            field = {customer_id = 'id', customer_name = 'name'}
        },
        item = {
            space = 'items',
            field = {item_id = 'id'}
        }
    }
}

Tarantool performs integrity checks upon data modifications in parent spaces. If you try to remove a tuple referenced by a foreign key or an entire parent space, you will get an error.

Важно

Renaming parent spaces or referenced fields may break the corresponding foreign keys and prevent further insertions or modifications in the child spaces.

Нашли ответ на свой вопрос?
Обратная связь