Значения, превышающие 1/3 страницы буфера, не могут быть проиндексированы

9

Я не очень хорошо разбираюсь в БД, поэтому, пожалуйста, потерпите меня.

Я пытаюсь поместить очень длинные данные JSON в таблицу, эта таблица была создана фреймворком Django.

Я использую Postgres на Heroku. Итак, когда я пытаюсь поместить данные, я получаю следующую ошибку:

File "/app/.heroku/python/lib/python3.6/site-packages/django/db/backends/utils.py", line 64, in execute
    return self.cursor.execute(sql, params)
psycopg2.OperationalError: index row size 3496 exceeds maximum 2712 for index "editor_contentmodel_content_2192f49c_uniq"
HINT:  Values larger than 1/3 of a buffer page cannot be indexed.
Consider a function index of an MD5 hash of the value, or use full text indexing.

Моя БД и таблица выглядят примерно так:

gollahalli-me-django-test::DATABASE=> \dt
                      List of relations
 Schema |            Name            | Type  |     Owner
--------+----------------------------+-------+----------------
 public | auth_group                 | table | ffnyjettujyfck
 public | auth_group_permissions     | table | ffnyjettujyfck
 public | auth_permission            | table | ffnyjettujyfck
 public | auth_user                  | table | ffnyjettujyfck
 public | auth_user_groups           | table | ffnyjettujyfck
 public | auth_user_user_permissions | table | ffnyjettujyfck
 public | django_admin_log           | table | ffnyjettujyfck
 public | django_content_type        | table | ffnyjettujyfck
 public | django_migrations          | table | ffnyjettujyfck
 public | django_session             | table | ffnyjettujyfck
 public | editor_contentmodel        | table | ffnyjettujyfck
(11 rows)


gollahalli-me-django-test::DATABASE=> \d+ editor_contentmodel
                            Table "public.editor_contentmodel"
  Column   |           Type           | Modifiers | Storage  | Stats target | Description
-----------+--------------------------+-----------+----------+--------------+-------------
 ref_id    | character varying(120)   | not null  | extended |              |
 content   | text                     | not null  | extended |              |
 timestamp | timestamp with time zone | not null  | plain    |              |
Indexes:
    "editor_contentmodel_pkey" PRIMARY KEY, btree (ref_id)
    "editor_contentmodel_content_2192f49c_uniq" UNIQUE CONSTRAINT, btree (content, ref_id)
    "editor_contentmodel_ref_id_8f74b4f3_like" btree (ref_id varchar_pattern_ops)

Похоже, я должен изменить, "editor_contentmodel_content_2192f49c_uniq" UNIQUE CONSTRAINT, btree (content, ref_id)чтобы взятьmd5(content)

Кто-нибудь может мне с этим помочь? Я понятия не имею, как это сделать.

Обновить:

JSONсодержание - https://gist.github.com/akshaybabloo/0b3dc1fb4d964b10d09ccd6884fe3a40

Обновление 2:

Я создал следующий UNIQUEиндекс, что я должен удалить в этом?

gollahalli_me_django=> create unique index on editor_contentmodel (ref_id, md5(content::text));
CREATE INDEX
gollahalli_me_django=> \d editor_contentmodel;
        Table "public.editor_contentmodel"
  Column   |           Type           | Modifiers
-----------+--------------------------+-----------
 ref_id    | character varying(120)   | not null
 content   | jsonb                    | not null
 timestamp | timestamp with time zone | not null
Indexes:
    "editor_contentmodel_pkey" PRIMARY KEY, btree (ref_id)
    "editor_contentmodel_content_2192f49c_uniq" UNIQUE CONSTRAINT, btree (content, ref_id) <---- 1
    "editor_contentmodel_ref_id_md5_idx" UNIQUE, btree (ref_id, md5(content::text))
    "editor_contentmodel_ref_id_8f74b4f3_like" btree (ref_id varchar_pattern_ops) <----2

Должен ли я удалить 1или 2(см. Стрелки)?

Акшай
источник
Вы пытаетесь индексировать столбец TEXT, и PostgreSQL (как и все остальные) имеет ограничения для индекса 2713, так что да - вы можете попробовать изменить его на хеш MD5, чтобы уменьшить его
a_vlad
@a_vlad Как мне это сделать? Не знаю, как это сделать.
17
Что такое контент? Это текст или JSON?
Эван Кэрролл
Кроме того, есть ли у вас два содержимого для одного и того же ref_id? Если так, то какова цель этого?
Эван Кэрролл
согласен с @EvanCarroll - может быть вам этот индекс вообще не нужен?
17

Ответы:

7

У вас есть уникальный индекс (content, ref_id), называетсяeditor_contentmodel_content_2192f49c_uniq

"editor_contentmodel_content_2192f49c_uniq" UNIQUE CONSTRAINT, btree (content, ref_id)

Я не уверен, почему это там для начала. Итак, давайте вернемся назад и рассмотрим, что это делает. Это гарантирует, что contentи ref_idявляются уникальными. Однако в PostgreSQL UNIQUEограничение реализовано с помощью btree, что делает его плохим решением. Используя этот метод, вы создаете btree с содержимым, которое по сути дублирует размер этой маленькой таблицы и создает гигантский индекс. Гигантский индекс, который все еще ограничен размером контента, как вы уже нашли. Возникает несколько вопросов

  • Вас волнует, что контент уникален? Если вас волнует, что контент уникален для ref_id, то вам, вероятно, нужно сохранить хэш контента. Что-то вроде..

    CREATE TABLE foo ( ref_id int, content text );
    CREATE UNIQUE INDEX ON foo (ref_id,md5(content));

    Вместо этого он будет хранить md5sum контента на btree. Пока у ref_id есть контент с уникальным md5 над этим ref_id, у вас все хорошо.

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

Может быть ничего не стоит, что когда вы реализуете UNIQUEограничение с помощью btree (как это делает PostgreSQL), вы получаете дополнительный индекс бесплатно. При нормальных обстоятельствах это имеет дополнительное преимущество.

CREATE TABLE foo ( ref_id int, content text );
CREATE UNIQUE INDEX ON foo (ref_id,content);

Ускорит запрос

SELECT *
FROM foo
WHERE ref_id = 5
  AND content = 'This content'

Однако, когда вы можете использовать функциональный md5()вариант, индекс контента больше не существует, поэтому теперь для использования этого индекса вам придется

  1. Только запрос по ref_id,
  2. Добавьте к ref_id пункт, который md5(content) = md5('This content')

Весь text = textпереоценен. Это почти никогда не то, что вы хотите. Если вы хотите сократить время запроса по тексту, btree довольно бесполезен. Вы, вероятно, хотите посмотреть на

  1. pgtrgm
  2. text_pattern_ops
  3. Полнотекстовый поиск (FTS)

ОБНОВЛЕНИЕ 1

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

ALTER TABLE public.editor_contentmodel
  ALTER COLUMN content
  SET DATA TYPE jsonb
  USING content::jsonb;

CREATE UNIQUE INDEX ON foo (ref_id,md5(content::text));

ОБНОВЛЕНИЕ 2

Вы спрашиваете, какие индексы вы должны удалить

gollahalli_me_django=> create unique index on editor_contentmodel (ref_id, md5(content::text));
CREATE INDEX
gollahalli_me_django=> \d editor_contentmodel;
        Table "public.editor_contentmodel"
  Column   |           Type           | Modifiers
-----------+--------------------------+-----------
 ref_id    | character varying(120)   | not null
 content   | jsonb                    | not null
 timestamp | timestamp with time zone | not null
Indexes:
    "editor_contentmodel_pkey" PRIMARY KEY, btree (ref_id)
    "editor_contentmodel_content_2192f49c_uniq" UNIQUE CONSTRAINT, btree (content, ref_id) <---- 1
    "editor_contentmodel_ref_id_md5_idx" UNIQUE, btree (ref_id, md5(content::text))
    "editor_contentmodel_ref_id_8f74b4f3_like" btree (ref_id varchar_pattern_ops) <----2

Вот неожиданный ответ: вы должны удалить их все, кроме : editor_contentmodel_pkeyкоторый говорит, что все ref_idдолжно быть уникальным.

  1. editor_contentmodel_content_2192f49c_uniqэтот индекс гарантирует, что вы находитесь UNIQUEна ref_idAND content, но если у вас не может быть дубликата, у ref_idвас никогда не будет дублированного контента для этого ref_id. Таким образом, вы никогда не сможете нарушить этот индекс без нарушения editor_contentmodel_pkey. Это делает это бессмысленным.
  2. editor_contentmodel_ref_id_md5_idxэтот индекс также бессмыслен по той же причине. Вы никогда не можете иметь дубликат за md5(content::text)кадр , ref_idпотому что независимо от того, что значения md5(content::text)есть вы никогда не можете иметь дубликата ref_id.
  3. editor_contentmodel_ref_id_8f74b4f3_likeЭто также плохая идея, потому что вы дублируете индекс ref_id. Это не бесполезно, просто неоптимально. Вместо этого, если вам нужно varchar_pattern_opsиспользовать его вместо всего contentполя.

В заключение отметим, что мы не очень часто используем varcharPostgreSQL, потому что он реализован как varlena с проверочным ограничением. В этом нет никакой выгоды, и ничего не теряется, когда вы просто используете text. Так что, если нет конкретной причины, почему ref_idкогда-либо может быть 120 символов, но это может быть 119 символов, тогда я просто использовал бы textтип.

ОБНОВЛЕНИЕ 3

Давайте вернемся к вашей предыдущей проблеме ..

psycopg2.OperationalError: index row size 3496 exceeds maximum 2712 for index "editor_contentmodel_content_2192f49c_uniq"

Это говорит вам, что проблема именно с индексом"editor_contentmodel_content_2192f49c_uniq" . Вы определили это как

"editor_contentmodel_content_2192f49c_uniq" UNIQUE CONSTRAINT, btree (content, ref_id)

Проблема в том, что вы пытаетесь создать индекс content. Но, опять же, сам индекс хранит фактическое содержание json content, и это то, что превышает предел. На самом деле это не проблема, потому что даже если бы этого ограничения не editor_contentmodel_content_2192f49c_uniqбыло, оно было бы совершенно бесполезным. Почему? Опять же, вы не можете добавить больше уникальности в строку, которая уже гарантированно будет на 100% уникальной. Вы, кажется, не получаете это. Давайте будем простыми.

ref_id | content
1      | 1
1      | 1
1      | 2
2      | 1

Выше (ref_id, content)имеет смысл использовать единственный уникальный индекс / ограничение (без других индексов), поскольку он остановит дублирование (1,1). Индекс over (ref_id, md5(content))также имеет смысл, потому что он остановит дублирование с (1,1)помощью прокси-сервера и остановит дублирование (1, md5(1)). Однако все это работает , потому что в этом примере я дал ref_idэто НЕ гарантируется UNIQUE. Вы ref_idне это ref_id. Вы ref_idэто PRIMARY KEY. Это означает, что он гарантированно будет УНИКАЛЬНЫМ.

Это означает, что копия (1,1)и строка (1,2)никогда не могут быть вставлены. Это также означает, что индексы над чем-либо, кроме ref_id, не могут гарантировать большую уникальность. Они должны быть менее строгими, чем индекс, который у вас есть в настоящее время. Таким образом, ваш стол может выглядеть только так

ref_id | content
1      | 1
2      | 1
Эван Кэрролл
источник
Могу ли я изменить editor_contentmodelтаблицы columnи добавить уникальность md5? или мы не можем просто изменить CONSTRAINT editor_contentmodel_content_2192f49c_uniq UNIQUE (content, ref_id)? Почему я должен создать для него новую таблицу?
Акшай
Вам не нужно создавать новую таблицу, я просто показал вам, как она будет выглядеть с упрощенной версией таблицы, которую вы получили. Просто проигнорируйте CREATE TABLEкоманду и введите CREATE UNIQUE INDEXправо под ней. Тогда DROPтвой старый индекс.
Эван Кэрролл
Последний вопрос, вы могли бы увидеть мойUpdate 2
akshay
@akshay обновлен.
Эван Кэрролл
1
Большое спасибо, Эван, это мне очень помогло. Концепция все еще немного шаткая (не моя область вообще). Я постараюсь выучить это.
Акшай
2

"editor_contentmodel_pkey" ПЕРВИЧНЫЙ КЛЮЧ, btree (ref_id) "editor_contentmodel_content_2192f49c_uniq" УНИКАЛЬНОЕ СОЗДАНИЕ, btree (content, ref_id)

Поскольку ref_id является первичным ключом, вы не можете иметь его дублирующиеся значения. Это означает, что уникальное ограничение на комбинацию (content, ref_id) бесполезно, так как все, что нарушает его, также нарушает ограничение первичного ключа. Просто избавься от этого.

jjanes
источник
Вы имеете в виду избавиться от этого и поставить что-то вроде create unique index on editor_contentmodel (ref_id, md5(content::text))? или я мог бы воссоздать таблицу и удалить первичный ключ.
17
Я не знаю, чего ты хочешь. Если вам нужен первичный ключ для ref_id, сохраните его. Но если вы сохраните его, то editor_contentmodel_content_2192f49c_uniq бесполезен, и удаление его решит проблему с названием. Кроме того, если вы сохраняете первичный ключ, то предлагаемый вами новый индекс также бесполезен (бесполезен в качестве ограничения, он может быть полезен в качестве индекса, но это маловероятно).
Джанес