Сначала сделаем самым простым способом, который первый приходит в голову :
SELECT tmp.id,
sq.amount
FROM tmp
LEFT JOIN
(SELECT docid,
SUM(amount) AS amount
FROM line
GROUP BY 1) sq ON sq.docid = tmp.id
А разве вот так не проще?
SELECT tmp.id,
SUM(line.amount) AS amount
FROM tmp
LEFT JOIN line ON line.docid = tmp.id
GROUP BY 1;
Сразу с правильным планом. Или это особенности построения запросов в ORM?
О кривом расчете n_distinct для line.docid читал здесь. Вроде ничего лучше не придумали, как вручную выставлять долю через alter table .. alter column .. set (n_disctinct= -0.xxx).
Спасибо, что подробно описали свой опыт сертификации.
Как человеку, имеющему отношение к созданию Программы сертификации, особенно приятно было прочитать раздел "Как бы я готовился сейчас". Практически все пункты этого раздела точно описывают то, что мы задумывали и рекомендуем при подготовке к сертификации.
Но небольшое уточнение внесу. Касательно последнего абзаца:
Многие вопросы составлены с подковыркой, главное не попасться. Например, в вопросе был сделан огромный select из pg_class + какой-то join из таблицы pg_database и тд. выведен результат этого запроса, а сам вопрос звучал так. С какими БД был инициализирован кластер? Если вы уже посмотрели курс, или знаете postgres, то понимаете, что этот select нужен был, только что бы вас запутать.
Я нашел этот вопрос. Вот он:
Некоторые базы данных были созданы уже после инициализации кластера:
\list
List of databases
Name | Owner | Encoding | Collate | Ctype | Access privileges
-----------+----------+----------+-------------+-------------+-----------------------
demo | student | UTF8 | en_US.UTF-8 | en_US.UTF-8 |
postgres | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 |
student | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 |
template0 | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 | =c/postgres +
| | | | | postgres=CTc/postgres
template1 | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 | =c/postgres +
| | | | | postgres=CTc/postgres
(5 rows)
Какие базы данных были созданы после инициализации кластера?
В вопросе нет лишних, запутывающих, запросов. Возможно от стресса во время теста, Вы что-то не так запомнили. Но более важно, что ответили на этот вопрос правильно :-).
Кстати, посмотрел Вашу первую попытку, в ней было как минимум два вопроса (-4%), где ответы даны зеркально противоположные. Т.е. скорее всего это следствие невнимательности чтения вопроса. И Вы это учли в своих рекомендациях.
Внешний пользователь всё-таки подключается к серверу БД.
Настройка подключения именно к серверу выполняется в pg_hba.conf. Триггер on-login срабатывает позже.
Это только потом для него устанавливается текущая БД.
После pg_hba.conf еще проверяется разрешение на подключение к указанной базе данных и только потом сработает on-login. Такая вот цепочка.
... в другую БД можно обратиться только через расширения типа dblink или fdw. Да, поскольку любое такое расширение устанавливает отдельное соединение, вероятно, на это событие тоже срабатывает on-login триггер?
Всё верно. Когда dblink или postgres_fdw подключаются к удаленной БД с триггером on-login, то триггер сработает.
...а где именно регистрируется такой триггер, объектом чего он является? И переносится ли он в составе бэкапа отдельной базы данных?
Триггер и триггерная функция регистрируются в системном каталоге конкретной базы данных данных. И они попадут в резервную копию этой БД.
Наверное и с пулами соединений от триггера на login может быть польза. С большой корпоративной БД могут работать сразу несколько приложений, каждое со своим пулом/middleware. Тогда в on-login можно настраивать пулы сеансов каждого приложения.
Как Егор уже написал, концептуальной проблемы не было.
А Вы вероятно писали систему, основанную на табличных триггерах? Для PostgreSQL, кстати, триггерных систем репликации общего назначения было создано несколько: Slony, Londiste, Bucardo. Последняя, судя по описанию, также поддерживает двунаправленную репликацию.
Оставлю для тех, кто дочитал до этого места. Действительно при переходе на generic план блокируются все секции секционированной таблицы. Вот пример с подготовленным запросом:
create table t (a int) partition by list (a);
select format('create table %I partition of t for values in (%s)', 'p'||x, x)
from generate_series(0,2) x
\gexec
\echo Ключ секционирования передается параметром.
prepare s (int) as select * from t where a = $1;
begin;
\echo Generic plan
set local plan_cache_mode = force_generic_plan;
explain (analyze) execute s(0);
select relation::regclass, mode
from pg_locks
where pid = pg_backend_pid() and
locktype = 'relation' and relation::regclass <> 'pg_locks'::regclass;
commit;
CREATE TABLE
CREATE TABLE
CREATE TABLE
CREATE TABLE
Ключ секционирования передается параметром.
PREPARE
BEGIN
Generic plan
SET
QUERY PLAN
--------------------------------------------------------------------------------------------------------
Append (cost=0.00..125.82 rows=39 width=4) (actual time=0.008..0.009 rows=0 loops=1)
Subplans Removed: 2
-> Seq Scan on p0 t_1 (cost=0.00..41.88 rows=13 width=4) (actual time=0.007..0.007 rows=0 loops=1)
Filter: (a = $1)
Planning Time: 0.357 ms
Execution Time: 0.035 ms
(6 rows)
relation | mode
----------+-----------------
p2 | AccessShareLock
p1 | AccessShareLock
p0 | AccessShareLock
t | AccessShareLock
(4 rows)
COMMIT
Но мне что-то не удалось повторить на своем примере. Переделал вот так и всё равно блокируется только нужная секция:
load 'auto_explain';
set auto_explain.log_min_duration=0;
set auto_explain.log_analyze=on;
set auto_explain.log_nested_statements=on;
set auto_explain.log_level='NOTICE';
\echo Ключ секционирования передается параметром.
begin;
set local plan_cache_mode = force_generic_plan;
do $$begin execute 'select * from t where a = $1' using 0; end;$$;
set auto_explain.log_min_duration=-1;
select relation::regclass, mode
from pg_locks
where pid = pg_backend_pid() and
locktype = 'relation' and relation::regclass <> 'pg_locks'::regclass;
commit;
LOAD
SET
SET
SET
SET
Ключ секционирования передается параметром.
BEGIN
SET
psql:s2.sql:26: NOTICE: duration: 0.005 ms plan:
Query Text: select * from t where a = $1
Query Parameters: $1 = '0'
Seq Scan on p0 t (cost=0.00..41.88 rows=13 width=4) (actual time=0.004..0.004 rows=0 loops=1)
Filter: (a = 0)
DO
SET
relation | mode
----------+-----------------
p0 | AccessShareLock
t | AccessShareLock
(2 rows)
COMMIT
@Kazzman, Постгресу нужно гарантировать что секции не будут удалены/изменены во время выполнения запроса, поэтому на каждую из них накладывается AccessShareLock. Тут вряд ли что-то изменится.
Но можно подставлять значение ключа секционирования константой в запрос, тогда будут блокироваться только секции участвующие в запросе плюс родительская. Если ключ секционирования нельзя вычислить заранее - увеличивайте max_locks_per_transaction, как и советует сервер.
Я использовал такой пример:
create table t (a int) partition by list (a);
select format('create table %I partition of t for values in (%s)', 'p'||x, x)
from generate_series(0,2) x
\gexec
\echo Ключ секционирования вычисляется во время выполнения запроса.
begin;
explain (costs off, analyze) select * from t where a = (select 0);
select relation::regclass, mode
from pg_locks
where pid = pg_backend_pid() and
locktype = 'relation' and relation::regclass <> 'pg_locks'::regclass;
commit;
\echo Ключ секционирования задан константой.
begin;
explain (costs off, analyze) select * from t where a = 0;
select relation::regclass, mode
from pg_locks
where pid = pg_backend_pid() and
locktype = 'relation' and relation::regclass <> 'pg_locks'::regclass;
commit;
Вывод скрипта:
DROP TABLE
CREATE TABLE
CREATE TABLE
CREATE TABLE
CREATE TABLE
Ключ секционирования вычисляется во время выполнения запроса.
BEGIN
QUERY PLAN
--------------------------------------------------------------------
Append (actual time=0.013..0.014 rows=0 loops=1)
InitPlan 1 (returns $0)
-> Result (actual time=0.001..0.001 rows=1 loops=1)
-> Seq Scan on p0 t_1 (actual time=0.006..0.007 rows=0 loops=1)
Filter: (a = $0)
-> Seq Scan on p1 t_2 (never executed)
Filter: (a = $0)
-> Seq Scan on p2 t_3 (never executed)
Filter: (a = $0)
Planning Time: 0.257 ms
Execution Time: 0.039 ms
(11 rows)
relation | mode
----------+-----------------
p2 | AccessShareLock
p1 | AccessShareLock
p0 | AccessShareLock
t | AccessShareLock
(4 rows)
COMMIT
Ключ секционирования задан константой.
BEGIN
QUERY PLAN
------------------------------------------------------------
Seq Scan on p0 t (actual time=0.003..0.003 rows=0 loops=1)
Filter: (a = 0)
Planning Time: 0.059 ms
Execution Time: 0.013 ms
(4 rows)
relation | mode
----------+-----------------
p0 | AccessShareLock
t | AccessShareLock
(2 rows)
COMMIT
Как пишет автор патча Питер Эйзентраут, это изменение включено в свежий проект стандарта SQL. Вероятно многие производители СУБД будут поддерживать такой синтаксис. Постгрес уже в их числе.
Имелось ввиду, что есть таблица tenants и во всех остальных таблицах условно employees первичный ключ составной ?
Да.
Я не понимаю, как фича set null помогает в случае бд которые используются разными клиентами.
Суть патча в том, что после ON DELETE SET NULL в скобках можно прописать список столбцов, которые нужно сбросить в NULL и не включать в него tenant_id.
С zheap понятно только то, что затихло. Конкретика и планы неизвестны.
А на merge посмотрим, может в 15 попадет.
Лично мне еще нравится патч schema variables. Эта штука, помимо прочего, даст возможность реализовать что-то вроде ораклового контекста, без чего полноценно использовать RLS сложно.
Следим. Высокая активность разработчиков увеличивает шансы на включение в 18-ю версию.
Планировщик оценит и сам решит как дешевле. В явном виде ничего указывать не нужно.
А разве вот так не проще?
Сразу с правильным планом. Или это особенности построения запросов в ORM?
О кривом расчете n_distinct для line.docid читал здесь. Вроде ничего лучше не придумали, как вручную выставлять долю через alter table .. alter column .. set (n_disctinct= -0.xxx).
Спасибо, что подробно описали свой опыт сертификации.
Как человеку, имеющему отношение к созданию Программы сертификации, особенно приятно было прочитать раздел "Как бы я готовился сейчас". Практически все пункты этого раздела точно описывают то, что мы задумывали и рекомендуем при подготовке к сертификации.
Но небольшое уточнение внесу. Касательно последнего абзаца:
Я нашел этот вопрос. Вот он:
В вопросе нет лишних, запутывающих, запросов. Возможно от стресса во время теста, Вы что-то не так запомнили. Но более важно, что ответили на этот вопрос правильно :-).
Кстати, посмотрел Вашу первую попытку, в ней было как минимум два вопроса (-4%), где ответы даны зеркально противоположные. Т.е. скорее всего это следствие невнимательности чтения вопроса. И Вы это учли в своих рекомендациях.
Удачи!
Настройка подключения именно к серверу выполняется в pg_hba.conf. Триггер on-login срабатывает позже.
После pg_hba.conf еще проверяется разрешение на подключение к указанной базе данных и только потом сработает on-login. Такая вот цепочка.
Всё верно. Когда dblink или postgres_fdw подключаются к удаленной БД с триггером on-login, то триггер сработает.
Триггер и триггерная функция регистрируются в системном каталоге конкретной базы данных данных. И они попадут в резервную копию этой БД.
Наверное и с пулами соединений от триггера на login может быть польза. С большой корпоративной БД могут работать сразу несколько приложений, каждое со своим пулом/middleware. Тогда в on-login можно настраивать пулы сеансов каждого приложения.
Кстати по описанным в статье причинам, параметр force_parralel_mode в 16-й версии переименовали в debug_parallel_query.
Эх, OTUS, OTUS. Годы идут ничего не меняется.
Картинку с архитектурой (слайд 4) я уже видел в очень старой версии курса DBA1.
Как Егор уже написал, концептуальной проблемы не было.
А Вы вероятно писали систему, основанную на табличных триггерах? Для PostgreSQL, кстати, триггерных систем репликации общего назначения было создано несколько: Slony, Londiste, Bucardo. Последняя, судя по описанию, также поддерживает двунаправленную репликацию.
Оставлю для тех, кто дочитал до этого места. Действительно при переходе на generic план блокируются все секции секционированной таблицы. Вот пример с подготовленным запросом:
Идея о переходе на generic план любопытная.
Но мне что-то не удалось повторить на своем примере. Переделал вот так и всё равно блокируется только нужная секция:
Это что. Сейчас активно делают поддержку DDL команд в логической репликации!
@Kazzman,
Постгресу нужно гарантировать что секции не будут удалены/изменены во время выполнения запроса, поэтому на каждую из них накладывается AccessShareLock. Тут вряд ли что-то изменится.
Но можно подставлять значение ключа секционирования константой в запрос, тогда будут блокироваться только секции участвующие в запросе плюс родительская. Если ключ секционирования нельзя вычислить заранее - увеличивайте max_locks_per_transaction, как и советует сервер.
Я использовал такой пример:
Вывод скрипта:
Спасибо, поправил. Заодно и настойки лс поменял ))
Как пишет автор патча Питер Эйзентраут, это изменение включено в свежий проект стандарта SQL. Вероятно многие производители СУБД будут поддерживать такой синтаксис. Постгрес уже в их числе.
Да.
Суть патча в том, что после ON DELETE SET NULL в скобках можно прописать список столбцов, которые нужно сбросить в NULL и не включать в него tenant_id.
Будем считать, что это затишье перед ...
А у вас pg_variables в postgres pro используется или вы собрали для его для PostgreSQL?
А на merge посмотрим, может в 15 попадет.
Лично мне еще нравится патч schema variables. Эта штука, помимо прочего, даст возможность реализовать что-то вроде ораклового контекста, без чего полноценно использовать RLS сложно.