@kuza2000

Создаю таблицу в sql lite в обычном варианте, и в варианте c WITHOUT ROWID получается огромная разница в размерах. Почему размеры так отличаются?

В таблице два поля: целое (key) и блоб (data) 300 байт. В первом случае обычная таблица с rowID.
Во втором случае используется WITHOUT ROWID и по key сделан кластерный индекс. Записи вставляются строго по возрастанию ключа. То есть, ситуации практически идентичные. Код для воспроизведения на питоне - в конце.

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

Вот что происходит в sql lite, как я понимаю ситуацию.

Первый вариант. Создаются два B-дерева.
Первое дерево: поля rowID, key, bl. В ключе - rowID.
Второе дерево: поля key, rowID. В ключе - key.

Второй вариант. Одно B-дерево. Поля key, bl. В ключе - key.

Вопрос - почему в первом варианте база с таблицей получилась 692 КБ, а во втором - 2347 КБ, более чем в 3 раза больше!

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

SQLite version 3.37.2 2022-01-06 13:25:41

Код для воспроизведения:
import sqlite3 as sql

base1 = sql.connect('test1.db', isolation_level=None)
base2 = sql.connect('test2.db', isolation_level=None)

cursor1 = base1.cursor()
cursor1.execute("""
    CREATE TABLE test(
        key INT,
        data BLOB
    )""")

cursor2 = base2.cursor()
cursor2.execute("""
    CREATE TABLE test(
        key INT,
        data BLOB,
        PRIMARY KEY (key)
    ) WITHOUT ROWID""")

bl = b'\xFE' * 1200

for i in range(500):
    
    query = 'insert into test(key, data) values (?, ?)'
    params = (i + 1000, bl)
    cursor1.execute(query, params)
    cursor2.execute(query, params)
    
base1.close()
base2.close()


UPD:
По советам в комментариях попробовал увеличить число строк в 10 раз. Результат:
6852К
23416К
Пропорция сохраняется.
Данные одинаковые (это видно в коде). Вставка идет в том же порядке. Индекс всегда возрастает.
  • Вопрос задан
  • 133 просмотра
Решения вопроса 1
@kuza2000 Автор вопроса
В общем, разобрались.
Когда размер блоба превышает определенный порог, он переносится на overflow pages. Это отдельная страница или несколько. Больше одного блоба на странице переполнения быть не может. А вот на странице данных (в записях) могут быть много блобов.

Вот тут, в разделе 1.6 описаны пороги, при превышения которых блоб переносится на страницы переполнения: https://www.sqlite.org/fileformat.html

Вот главная формула:
X is U-35 for table btree leaf pages or ((U-12)*64/255)-23 for index pages.

Тут U - это размер страницы. Размер страницы у меня 4096, получается, порог переноса будет 4096 - 12 = 4084 для листовой страницы и ((4096-12)*64/255)-23 = 1002 для страницы указателей. Это полный размер записи. За вычетом размера блоба остальная часть записи - 5 байт (подглядел в дампе страницы базы). Значит, пороги переноса блоба будут 4079 и 997.

Провел эксперименты с разным размером блоба. База с WITHOUT ROWID перестает пухнуть при размере блоба 996 байт. А 997 - уже проблема. Вывод: в таблицах с WITHOUT ROWID применяется порог для блобов, который предназначен для страницы индекса.

Вообще, ничто не мешает хранить блобы большего размера на страницах таблиц с WITHOUT ROWID. Вполне возможно, что это ошибка, которую позже поправят. Но пока в sql lite в таблицах с WITHOUT ROWID блобы лучше не хранить.

UPD:
Спросил у на форуме sql lite, ответили разработчики: https://www.sqlite.org/forum/forumpost/ad86fbaa04
Как справедливо поправили - блобы тут не причем, имеет значение общий размер записи (payload). Ответили, что таблицы WITHOUT ROWID сделаны как покрывающие индексы, и унаследовали соответствующие ограничения. В общем, на мой взгляд, не стоит их использовать, если может произойти spliting. Для этого общий размер записи не должен приближаться к 1/4 размера страницы.
Ответ написан
Комментировать
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы