G на конце слов. König — это кёнихь или кёник?

Вот вам два слова, König и Zug. Знаете, как правильно из произносить?

Литературный немецкий язык развивается. И если 100 лет назад пражский диалект считался литературным, то ближе всех к норме говорят в Ганновере и окрестностях, то есть в северных землях.

На севере окончание -ig произносят, как «ихь», а на юге, например, в Баварии, как «ик». Стандартной, литературной формой сейчас признано произношение «ихь». Так что König произносится ближе всего к «кёнихь».

Отдельно для -g на конце слов особых правил нет. Например, слово Zug произносится как «Цук». Как «Цух» оно читается только в некоторых диалектах.

Разные способы определения функций в JavaScript (это сумасшествие!)

Это перевод с английского оригинальной статьи  Different Ways of Defining Functions in JavaScript (This Is Madness!).

Это сумасшествие! Это… JavaScript!

Чтобы добиться какого-то эффекта в JavaScript, всегда есть много способов. Это одновременно и хорошо, и плохо. Безусловно, это плохо для новичков, потому что означает не только то, что придется больше учиться, но и то, что присутствует больше подводных камней и возможных ошибок. И определение функций — не исключение.

Цель этой статьи — доступный обзорный тур, просто чтоб вы знали, что почëм и каковы главные отличия. Обратите внимание на раздел «Дополнительно по теме»! Многое в этой статье основано на посте Юрия Зайцева, в котором все расписано подробнее. Однако, я не нашел ни одного материала, где были бы показаны все виды определения функций.

Что насчет выполнения функций? Это поднимает ещё целый ряд проблем и открывает возможность для новой будущей статьи на эту тему. :)

Обзор: различные способы объявления функций

function A(){}; // объявление функции
var B = function(){}; // функциональное выражение
var C = (function(){}); // функциональное выражение с группирующими операторами
var D = function foo(){}; // именованное функциональное выражение
var E = (function(){ // немедленно исполняемое функциональное выражение (IIFE), которое возвращает функцию
    return function(){}
})();
var F = new Function(); // конструктор Function 
var G = new function(){}; // особый случай: конструктор объекта

Объявления функций: function A(){};

Объявление функций — это, пожалуй, лучше всего знакомый нам способ сделать чего-нибудь в мире JavaScript. Так мы создаем переменную A, которая доступна в текущей области видимости. Область видимости — это отдельная тема, так что во всех последующих примерах все происходит в глобальной области видимости (то, чего обычно следует избегать).

1. Hoisting (поднятие переменной)

Интересно, что функции «поднимаются» (hoist) в самый верх их области видимости, а это означает, что данный код:

A();
function A(){
    console.log('foo');
};

исполняется, как этот код:

function A(){
    console.log('foo');
};
A();

а это на практике значит, что вы можете вызывать функции перед тем, как они написаны в коде. Это не имеет значения, потому что вся функция «поднимается» в самый верх области видимости. (Это контрастирует с переменными, у которых «поднимается» только их объявление, но не их содержимое, как мы увидим в следующем разделе).

2. Никаких объявлений функций в блоках if или циклах и т.д.)

Таким способом нельзя определять функции в выражениях, например, в блоках if, что достаточно часто нужно, если мы хотим определить разные версии функции для разных обстоятельств, обычно, чтобы подстроиться под разные браузеры. Конечно, в некоторых случаях вы можете это сделать, но получившийся код будет неконсистентным (kangax задокументировал неконсистентности здесь). Если хотите использовать этот паттерн, лучше пользуйтесь функциональными выражениями.

3. Объявления функций должны иметь имена

Данный метод не позволяет вам создавать анонимные функции, а это означает, что вы всегда должны присваивать какой-нибудь идентификатор (в данном случае мы использовали «A»).

Функциональные выражения: var B = function(){};

Функциональное выражение выглядит похоже на объявление функции за тем исключением, что функция присваивается переменной. Несмотря на то, что функции не являются примитивными значениями в JavaScript, это тот способ, при котором они могут быть использованы по полной в этом функциональном языке. Функции относятся к «первому классу«:

[JavaScript] поддерживает передачу функций другим функциям в качестве аргументов, возвращая их как значения из других функций и присваивая их переменным или храня их в структурах данных.

1. Анонимные функции (им не нужны имена)

В функциональном выражении имя функции не обязательно, и мы называем их анонимными. Здесь мы делаем переменную B равной анонимной функции:

var B = function(){};

2. Поднятие (hoisting) объявления переменной

Объявления переменных «поднимаются» (hoist) в самый верх их области видимости, это напоминает поднятие функций, но при этом значение переменной не поднимается. Это происходит со всеми переменными, что означает, что то же самое происходит с нашими функциями, которые мы присваиваем переменным.

Этот код:

var A = function(){};
var B = function(){};
var C = function(){};

будет выполнен вот так:

var A, B, C; // объявления переменных поднимаются
A = function(){};
B = function(){};
C = function(){};

Следовательно, важен порядок, в котором функции создаются и вызываются

// Это работает
var B = function(){};
B();

// Это не работает
B2(); // TypeError (B2 не определена)
var B2 = function(){};

Второй пример выдает нам ошибку, потому что у переменной B2 поднимается только ее объявление, но не ее определение, вызывая таким образом ошибку «undefined».

Функциональные выражения с группирующими операторами: var C = (function(){});

Они на самом деле не очень отличаются от старых добрых функциональных выражений, и их не так уж часто встретишь в реальном мире (так что, может, они хороши лишь для задачек по JavaScript?). Недавно этот тип определения функций всплыл в этой статье и озадачил некоторых, включая меня самого.

Вот хороший способ посмотреть, что происходит:

function(){}; // синтаксическая ошибка SyntaxError
(function(){});

Почему одно работает, а другое нет? Первый пример — это определение функции, а мы уже узнали выше, что мы не можем объявлять их анонимно, то есть, они должны иметь имя. Вот почему мы получаем синтаксическую ошибку.

Второй пример использует скобки — операторы группировки — и таким образом вычисляется по-другому, как функциональное выражение. Операторы группировки мы применяем, чтобы помочь показать, что должно быть вычислено вперёд, как в математических задачах. Мы как бы говорим: «Вычисли сначала вот это, потом возьми результат и сделай с ним что-нибудь»:

(1 + 2) * 3; // 9
1 + (2 * 3); // 7

В первом примере мы говорим: «сначала сложи 1 и 2, затем возьми результат и умножь на 3», тогда как во втором примере мы говорим: «сначала перемножь 2 и 3, затем возьми результат и прибавь к нему 1».

Так как функции относятся к первому классу, то мы можем использовать похожие операторы группировки. Вот несерьезный пример, который, однако, показывает, как мы, в принципе, можем вставить функцию в той же манере:

(function(){} + 1); // function(){}1

Результат — строка (потому что для функции вызывается метод toString(), а затем прибавляется/присоединяется 1), но я надеюсь, вы уловили идею.

Когда движок JavaScript встречает здесь открывающую скобку, мы по сути говорим: «ОК, начни это группировать с чем-то еще». Используя наши технические термины, мы говорим движку, что мы не производим объявление функции, а вместо этого создаем функциональное выражение. И тогда его результат мы можем присвоить переменной:

(function(){}); // результирующая функция не присвоена
var foo = (function(){}); // результирующая функция присвоена foo
var bar = function(){}; // результирующая функция присвоена bar

Здесь мы можем увидеть, что foo и bar — по сути одно и то же, потому что в foo мы группируем функцию ни с чем иным, как с самой собой.

Именованное функциональное выражение: var D = function foo(){};

Здесь мы имеем дело все с тем же нашим старым другом, функциональным выражением. Однако, вместо присваивания переменной анонимной функции, мы присваиваем ее именованной функции (с именем foo).

1. Имя функции доступно только внутри самой функции

Мы не сделали имя функции (foo) видимым в содержащей ее области видимости (в этом случае глобальной области видимости):

var D = function foo(){
    console.log(typeof foo);
};
D(); // function
console.log(typeof foo); // undefined

2. Полезно для рекурсии

Так как имя функции доступно в самой функции, то получается, что это полезно для рекурсивных функций, гораздо более полезно, чем обычные старые анонимные функции.

Для иллюстрации вот тривиальная рекурсивная функция, вызывающая саму себя из именованного функционального выражения:

var countdown = function a(count){
    if(count > 0) {
        count--;
        return a(count); // еще мы можем сделать так: a(--count), но это не так очевидно
    }
    console.log('конец рекурсивной функции');
}
countdown(5);

3. Полезно для отладки

Как указывали некоторые, присваивание прежде анонимным функциям имен помогает при отладке, так как имя функции появляется в стеке вызовов.

4. Особенности: плохая реализация в JScript

kangax указывает на то, что именованные функциональные выражения являются по сути ядом для JScript, реализации JavaScript в Internet Explorer.

Именованная функция становится глобальной переменной, она «поднимается» (hoist) так же, как объявление функции и в конце концов создает несколько экземпляров той же самой функции.

Немедленно исполняемые функторы (Immediately-invoked function expressions, IIFE): var E = (function(){return function(){}})();

“Выполни эту функцию, возвращаемое значение которой является другой функцией, и присвой это переменной E”. Это может выглядеть волшебством, но на самом деле это очень просто, а паттерн эффективен  имеет полезные приложения, наиболее известное из которых — паттерн «модуль».

Сначала мы напишем пример, который не выглядит, как волшебство:

var foo = function(){
    return 'bar';
};
var output = foo();
console.log(output); // 'bar'

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

var foo = function(){
    return 'bar';
};
var output = (foo)(); // обратите внимание на дополнительные операторы группировки
console.log(output); // 'bar'

Поскольку foo указывает на на наше функциональное выражение, мы знаем, что можем просто не использовать переменную «foo» и вставить всю функцию как анонимную (ведь функции являются объектами первого класса!):

var output = (function(){
    return 'bar';
})();
console.log(output); // 'bar'

Но подождите, мы только что пришли к волшебной результирующей функции! Оказалось, что она не такая уж и волшебная, как только мы разобрали ее по частям. Это просто сокращение для кода, который мы написали изначально, где мы определили функцию, выполнили ее и определили вывод как ее возвращенное значение.

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

var E = (function(){
    return function(){}
})();

Приложения

Для этого метода есть хорошие варианты использования, включая скрытие информации в паттерне «модуль», (частичное применение (partial application), например) и другие умные штуки, использующие его. Это однозначно не тривиальный паттерн.

Конструктор Function: var F = new Function();

Этот метод очень старый, и его не рекомендуется использовать. Вы передаете неограниченное количество аргументов в начале, затем действительное тело функции представлено как строка в последнем аргументе (так как это строка, то, по сути, это эквивалент eval(), и не рекомендовано к использованию).

1. Определение функции

Вы можете создавать функцию так:

var F = new Function('arg1', 'arg2', 'console.log(arg1 + ", " + arg2)');
F('foo', 'bar'); // 'foo, bar'

2. Вам не нужен оператор new

Вы можете просто написать var F = Function(); чтобы получить тот же результат.

3. Особенности

В документации MDN приведены хорошие примеры особенностей применения (quirks), включая тот факт, что функции, объявленные при помощи конструктора Function, не наследуют нормально их текущую область видимости (то есть замыкание не формируется).

Это значит, что они не имеют доступа к переменным из включающей их области видимости, а это не очень полезно:

function foo(){
    var bar = 'blah';

    var first = new Function('console.log(typeof bar)');
    first(); // undefined

    var second = function(){
        console.log(typeof bar);
    }
    second(); // string
}
foo();

В функции «first» мы используем конструктор Function, так что он не имеет доступа к переменной bar. Однако, если мы используем являющуюся функциональным выражением функцию «second», то она фактически имеет доступ к переменным, определенным в содержащей ее области видимости (через замыкание).

Другими словами, не пользуйтесь конструктором Function .

Особый случай — конструктор объекта: var G = new function foo(){};

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

new function(){}; создает новый объект и вызывает анонимную функцию в качестве конструктора. Если из функции возвращается объект, то он становится результирующим объектом, иначе создается с нуля новый объект, а функция выполняется в контектсе этой новой функции (давайте оставим подробности для другого поста!).

Немного необычно видеть это в такой форме. Давайте сделаем это должным образом:

var Person = function(){
    console.log(this); // Person
}
var joe = new Person();

Так что на самом деле оператором new мы даем функции новый контекст «this» и затем выполняем данную функцию с этим новым контекстом. Это сильно отличается от определений функции, с которыми мы имели дело выше! Это ведет к совершенно новой теме, и мы прибережем ее на потом!

Дополнительно по теме (англ.)

Named function expressions demystified (kangax)

Immediately-Invoked Function Expression (IIFE) (Ben Alman)

Functions and function scope (Mozilla Developer Network — MDN)

How does an anonymous function in JavaScript work? (StackOverflow)

Function Declarations vs. Function Expressions (JavaScript, JavaScript by Angus Croll)

JavaScript: The Definitive Guide (классическая книга Дэвида Фленагана)

Наследование таблиц в PostgreSQL

Если у вас стоит задача создать таблицу на основе другой таблицы, так, чтобы она включала в себя все столбцы родительской, то делается это при помощи наследования.
Допустим, у нас есть таблица insects и мы хотим сделать таблицу bees на ее основе.

CREATE TABLE insects (
id serial
name text,
size float
);

CREATE TABLE bees (
can_collect_honey smallint
) INHERITS (insects);

В этом случае столбцы name и size будут присутствовать и в bees. Кроме того, в bees будет присутствовать дополнительный столбец can_collect_honey.
Если мы сделаем Insert в одну из этих таблиц, то добавляемая строка появится только в той таблицу, в которую мы ее добавляем.
Если сделать Select из таблицы Insects, то в выборку попадут удовлетворяющие критериям записи из ОБОИХ таблиц.
Если мы хотим выбирать из строк только родительской таблицы, нужно использовать ключевое слово ONLY:

SELECT *
FROM ONLY insects

Любые связки с другими таблицами (Reference), первичные ключи (Primary Key), ограничения на уникальность (Unique) и другие ограничения (Constraints), существующие для родительской таблицы, не наследуются потомком. Для него нужно создавать свои ключи и ограничения. Отсюда следует, что, например, если мы хотим видеть поле id уникальным, то оно будет таковым только в пределах родительской таблицы.

Если мы хотим, чтобы при добавлении в родительскую таблицу при определенных критериях данные попадали вместо этого в дочернюю таблицу, то это можно сделать либо с помощью триггеров (Trigger), либо с помощью правил (Rule).

Одна таблица может наследовать сразу нескольким, также и у одного родителя может быть много потомков (с помощью пустой родительской таблицы и потомков можно создавать т.н. партиции (Partition), например, чтобы разнести большие массивы данных по какому-то параметру по разным таблицам для более быстрого доступа и т.п. — в этом случае в каждой партиционной таблицу надо поставить проверку (Check) на те данные, которые туда пишутся).

Если внешняя таблица ссылается (Reference) на родительскую таблицу, то строки, входящие в дочерние таблицы, игнорируются.

Особое внимание надо уделить также полям типа Serial и последовательностям (Sequence) для них. Если, как в нашем примере, дочерняя таблица просто наследует Serial поле родительской (id в нашем случае), то они делят последовательность. И, скажем, добавив строку в родительскую таблицу, а потом в дочернюю, и снова в родительскую, имеем три записи, две из которых — с id= 1 и 3 — относятся к родительской, а с id=2 — к дочерней. Если мы просто определим поле id в дочерней таблицу тоже, то для нее автоматически создастся новая последовательность, так, что записи в родительской и дочерней таблицах станут иметь дубликаты по id. Однако, вы можете вручную указать другую последовательность для столбца id (скорее всего, с другими, не пересекающимися с родительской последовательностью, стартовым и конечным номерами). В этом случае при записи в каждую из таблиц номера будут браться из соответствующей «своей» последовательности.

Подробнее по теме:

«Пропаганда» Жака Эллюля. Глава 5. Тезисы.

В последней, 5-й главе, Эллюль пишет о социо-политических эффектах пропаганды. Приведу основные тезисы, которые запомнил:

  1. В прошлом идеологии нужна была пропаганда, чтобы проводить экспансию, ведь идеология является экспансионистской по своей природе.
  2. Новые отношения идеологии с пропагандой: теперь, наоборот, идеология стоит на службе у пропаганды. Пропагандист может либо стимулировать, либо мифологизировать идеологию в целях пропаганды.
  3. Идеология строит себя вокруг действий, стимулируемых пропагандой, а не мотивирует на эти действия.
  4. Пропаганда разрушительно влияет, выхолащивает любую доктрину, если использует ее.
  5. Пропаганда упрощает общественное мнение, сводит его к полярному состоянию «белое-черное», нюансы исчезают.
  6. Под воздействием пропаганды очень сильно различаются общественное и личное мнение.
  7. Пропаганда замещает собой лидера группы. Лидер лишается своей роли и сводится к символу, знамени.
  8. Пропаганда разделяет группы людей на отдельные сегменты. Как пример, в СССР газеты и журналы общего назначения не могли всерьез критиковать просчеты на местах, зато специальные, медицинские, технические журналы, могли критиковать нещадно огрехи в собственной отрасли. То есть по получаемой информации общество сильно сегментировалось в профессиональном плане.
  9. Автор считает, что в демократических обществах применение пропаганды ведет к двухпартийной системе. Маленькие партии просто не имеют ресурсов, чтобы вести пропаганду. Две партии дополняют друг друга — одна ведет пропаганду за себя, вторая — против правящей партии.
  10. Использование пропаганды профсоюзами парализует их, так как на пропаганду нужны деньги, а деньги создают зависимость от того, кто их дает.
  11. Рабочие под воздействием позитивной пропаганды могут работать эффективнее, даже если их жизненные условия не улучшаются.
  12. Использование пропаганды церковью лишает ее религиозности — она превращается в обычную государственную бюрократию.
  13. Пропаганда, направленная вовне страны, влияет также и на жизнь в самой стране. Нельзя изолировать такую пропаганду от жизни в стране и внутренней пропаганды.
  14. Пропаганда нужна демократии, чтобы бороться с враждебными пропагандами, но в тот момент, когда демократия к ней прибегает, она лишается демократичности и становится просто «еще одной идеологией». Сам демократический образ мысли, ментальность при этом страдают.

Предыдущая часть тезисов.

Рецензия на JavaScript Allongé

Книга «JavaScript Allongé» автора Reginald Braithwaite заслуживает внимания. Она посвящена функциям в JavaScript. Если быть точнее, то книга — это сборник готовых к применению рецептов для использования некоторых «фич» функционального программирования.

Первые главы книги шикарны — в них объясняется устройство функций в JavaScript, функции высшего порядка, области видимости, замыкания и т.п., при том делается это в необычной манере. Постепенное усложнение материала постоянно на протяжении всей книги, и, если с первых страниц создаётся обманчивое впечатление о простоте материала, то к концу книги от него не остаётся и следа. Скажу честно, где-то с середины смысл текста стал от меня ускользать.

В книге много уникальных авторских идей и «доработок» уже существующих концепций, и, несомненно, что текст написан для профессионалов, уже «собаку съевших» на JavaScript. Советую как минимум прочитать первые, вводные, главы. Они расширяют кругозор программиста, дают возможность посмотреть на работу функций с другой стороны. Изложение материала последовательное, если читать вдумчиво, то не остаётся «темных пятен».

Disabled, hidden, readonly в HTML

Три атрибута HTML disabled, hidden и readOnly часто путают. Все они влияют на видимость элемента на странице и его наличие при отправке формы на сервер. Все эти атрибуты делают значение элемента недоступным для редактирования пользователем.

Предположим, что у нас есть некий input в форме.

<form>

<!-- ................. -->

    <input type = "text" id="input_id" name="input_name">

</form>

Чтобы установить атрибут readOnly или disabled из JavaScript, поступаем так:

var el = document.getElementById("input_id");

el.readonly = true;

Чтобы установить тип элемента hidden, делаем так:

var el = document.getElementById("input_id");

el.setAttribute("type", "hidden");

Вот как влияют все эти свойства-атрибуты на поведение элемента:

 Параметр  hidden  disabled  readOnly
 Видимость на странице  невидим  видим, затемнён  видим
 Передаётся на сервер при отправке формы  да  нет  да
 Пользователь может редактировать значение input  нет  нет  нет

 

Анализ запросов в PostgreSQL. Часть 3. Операции объединения, группировки, сортировки и ограничения количества записей в PostgreSQL

В данной (последней) части статьи об анализе запросов в PostgreSQL я расскажу об операциях объединения таблиц (JOIN), группировки и сортировки данных.

Предыдущие части: часть 1, часть 2.

Операции объединения таблиц

Операции объединения обычно производятся только над двумя таблицами. Если у вас есть подзапросы или несколько Join-ов, то они будут выполняться в несколько последовательных операций объединения. Промежуточные результаты, например, подзапроса, также можно считать в данном случае за таблицу.

Nested Loops

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

Является самой медленной формой Join-а, но быстро выбирает первую строку. Если вторая подоперация медленна, то выполняется особенно медленно.

Hash Join/Hash

Объединение хешированием подразумевает загрузку строк-кандидатов из одной таблицы в хеш-таблицу (Hash в плане выполнения запроса), после чего они сравниваются с каждой строкой другой таблицы. Таким образом, устраняется недостаток объединения с использованием вложенных циклов (Nested loops), потому что внутренний цикл (тестирование выборки из первой таблицы с каждой строкой второй таблицы) теперь состоит из быстрых операций, благодаря хеш-таблице.

Может использоваться только для хешируемых типов данных и только для условия объединения через равенство значений столбцов.

Обычно идеально для объединения большой (первой) и маленькой таблиц (второй).

Медленно стартует из-за того, что нужно захешировать вторую (маленькую) таблицу.

Может быть слишком медленным, если оценка размеров таблиц была ошибочной.

Merge Join

Merge join объединяет два отсортированных списка. Надо помнить, что обе таблицы должны быть предварительно отсортированы.

Может применяться только для объединения с условием равенства значений столбцов.

Самый быстрый join, особенно для больших объемов данных.

Поскольку для выполнения требуются отсортированные таблицы, то может потребоваться предварительная медленная операция сортировки или индексные сканирования.

Append

Операция Append применяется там, где нужно сделать объединение через Union. Результаты одной операции/таблицы просто присоединяются к другой. Затрат на операцию нет почти никаких, и общие затраты по её итогам будут примерно равны сумме затрат всех подопераций.

Materialize

Операция материализации может применяться для некоторых подзапросов. В отдельных случаях планировщик может посчитать, что выполнить один раз операцию материализации для подзапроса, запомнив результат, выгоднее, чем делать это для каждого вызова этого подзапроса. Тогда мы и видим такую операцию в анализаторе запроса.

Setop (Intersect, Intersect All, Except, Except All)

Существует четыре Setop-операции: Setop Intersect, Setop Intersect All, Setop Except и Setop Except All. Они выпоняются, когда планировщик обнаруживает команды INTERSECT, INTERSECT ALL, EXCEPT или EXCEPT ALL, соответственно.

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

Группировка и сортировка

Sort / Sort Key

Сортировка по ключу, указанному в Sort Key. Для выполнения требует больших объемов памяти. Неконвейеризованная операция.

Обычно применяется для ORDER BY, DISTINCT, GROUP BY, UNION и merge join-ов.

Время, затрачиваемое на начало операции, обычно очень велико.

Если памяти (work_mem) хватает для выполнения операции, то будет отрабатывать быстрее, иначе используется внешний диск, и операция проходит медленно.

GroupAggregate

Агрегация предварительно отсортированного набора данных. Конвейеризованная операция.

HashAggregate

Агрегация с использованием временной хеш-таблицы. Не нуждается в предварительной сортировке данных, но зато использует большие объёмы памяти. Результат операции не упорядочен. Неконвейеризованная операция.

Ограничение по количеству строк в результате

Limit

Ограничение по количеству строк, накладываемое на нижестоящие операции. Эффективность зависит от типа этих операций. Для неконвейеризованных (как сортировка, например) эффективность страдает очень сильно.

Используется для LIMIT и OFFSET, а также для min() и max(), если в запросе нет WHERE.

Записи, пропускаемые по OFFSET, тем не менее, выбираются из таблицы, над ними проделываются все операции, и лишь затем они отбрасываются.

Если вместе с Limit применяется сортировка, то она может быть внутренне оптимизирована.

WindowAgg

Применение оконных функций.

«Пропаганда» Жака Эллюля. Глава 4. Тезисы.

В 4-й главе речь идёт о психологических эффектах, производимых пропагандой.

  1. Пропаганда лишает человека его персональной воли и способности мыслить, заменяя суррогатом общественной идеологии.
  2. Пропаганда может создавать виртуальные проблемы и решать на словах, а не на деле, реальные.
  3. Пропаганда приводит человека к отчужденности, подтачиваяая его традиционные связи и идеи, лишая его эго и растворяя в массе.
  4. Пропаганда порождает тревожность, но она же способна чувство тревожности снимать, поэтому человек, подпавший под ее влияние, похож на наркомана — у него начинается «ломка», если его лишить источника пропаганды. При этом он легко подпадёт под влияние любой другой пропаганды, так как уже к этому готов.
  5. Пресыщенность пропагандой создаёт иммунитет к её интеллектуальной составляющей, но не к эмоциональной. Человек больше не вслушивается в передачу, не читает внимательно газету, не рассматривает плакат, но уже одного только мимолётного взгляда на этот плакат или пробегания глазами по статье достаточно, чтобы «освежить» в нём всё то, что заложено предыдущими усилиями пропагандиста.
  6. Пропаганда, в зависимости от обстоятельств, может давать оправдание неблаговидным поступкам (что привязывает индивида к ней ещё больше) или, наоборот, обвинять.
  7. Два противоположных пропагандистских влияния не нейтрализуют друг друга, а повергают пропагандируемого в шок, и далее либо вызывают в нём отчуждение, равнодушие, эскапизм и нигилизм, либо делают его наиболее ярым и слепым приверженцем одной из этих пропаганд, так, что вторую он будет игнорировать и считать враждебной в любых проявлениях.

Предыдущая часть                                       Следующая часть

Анализ запросов в PostgreSQL. Часть 2. Операции индексного и табличного доступа в PostgreSQL

Это продолжение первой части статьи.

Здесь мы рассмотрим, что означают термины, в которых PostgreSQL выводит информацию в EXPLAIN запросах. Возьмем пример из официальной документации по PostgreSQL:

SET enable_nestloop = off;
EXPLAIN SELECT *
FROM tenk1 t1, tenk2 t2
WHERE t1.unique1 < 100 AND t1.unique2 = t2.unique2;

                                        QUERY PLAN
------------------------------------------------------------------------------------------
 Hash Join  (cost=232.61..741.67 rows=106 width=488)
   Hash Cond: (t2.unique2 = t1.unique2)
   ->  Seq Scan on tenk2 t2  (cost=0.00..458.00 rows=10000 width=244)
   ->  Hash  (cost=232.35..232.35 rows=106 width=244)
         ->  Bitmap Heap Scan on tenk1 t1  (cost=2.37..232.35 rows=106 width=244)
               Recheck Cond: (unique1 < 100)
               ->  Bitmap Index Scan on tenk1_unique1  (cost=0.00..2.37 rows=106 width=0)
                     Index Cond: (unique1 < 100)

Из части 1 нам уже понятно, что здесь означают цифры, но может быть не до конца ясно, что за операции такие Hash Join и Seq Scan, Bitmap Heap Scan и Bitmap Index Scan, а также зачем нужен Recheck Cond и вообще, как понимать не только то, сколько ресурсов потребляет каждая операция, но и то, что она реально делает и зачем. Материал не претендует на всеохватность, но надеюсь его пополнять по мере возможности.

Как читать план выполнения запроса. Общие сведения

План выполнения разбивается на отдельные операции. Можно видеть, что для удобства каждый элемент плана выделяется стрелкой ->. Операции, входящие в состав более крупной операции, выделяются табуляцией. Численные показатели для операции верхнего уровня — это сумма всех численных показателей операций, входящих в ее состав плюс дополнительные издержки.

Cond

Ко многим операциям указываются условия их выполнения. В примере выше, Index Cond — это условие, по которому производится индексное сканирование. В данном случае система пробегает по индексу столбца unique1 и сравнивает каждое из значение с 100.

Filter

После отработки определённой операции к получившимся в ее результате данным может применяться фильтр с заданными параметрами, который «просеивает» эти результаты, если этого требует структура запроса. Это обозначается словом Filter и указанием условий фильтрации. Важно понимать, что, в отличие от Index Cond, этот фильтр применяется не во время поиска/выборки данных, а уже после того, как данные выбраны, то есть она достаточно медленна.

Index Scan

Индексное сканирование используется тогда, когда выгодно и возможно использовать индекс. Операция обычно очень быстрая, особенно быстро она отрабатывает в начале, однако, произвольный доступ к данным (особенно большого объёма) гораздо быстрее выполняется при операции последовательного поиска. Индекс хорош для поиска по нему, но не для операций чтения для больших объёмов данных.

Индекс служит для упорядочивания данных, чтобы искать среди них нужные было проще. Индексы используют две математические идеи: двусвязный список и сбалансированное дерево поиска, объединенные в поисковом алгоритме.

Двусвязные списки (списки, каждый элемент которых имеет ссылки на предыдущий и последующий элементы) используются для соединения так называемых узлов, каждый из которых — это блок или страница данных в БД. Данные группируются по несколько строк в такие блоки для оптимизации поиска. В свою очередь, индекс — это пары упорядоченных по возрастанию или убыванию значений определенного столбца и внутренний идентификатор соответствующей строки с данными в БД. Эти пары объединены в блоки с одинаковым количеством таких пар в каждом. Эти блоки упорядочены в виде двусвязного списка.

Для того, чтобы при поиске значения не приходилось последовательно проходить по всем блокам индекса, в нём используется алгоритм сбалансированного дерева поиска (B-Tree). Для его построения делается следующее. У каждого из блоков индекса берется максимальное по величине значение, далее берется определенное количество таких соседних блоков, и создается новый блок на следующем уровне иерархии, в котором перечислены все максимальные значения изначальных блоков с ссылками на эти блоки, далее этот уровень также является основой для создания следующего по тем же правилам, и так, пока количество значений не уместится в единственном, т.н. «корневом» узле. Поиск по дереву происходит в обратном порядке. Когда ищется какое-то значение, то обход дерева начинается с минимального значения корневого блока. Как только находится такое, которое больше искомого, осуществляется переход на соответствующий узел следующего уровня иерархии, и так, пока не будет достигнут последний уровень и не отыщется ссылка на строку БД, содержащую искомое значение.

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

Обычно на выходе получается упорядоченный набор данных, выстроенных по индексу.

Index Only Scan

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

Seq Scan

Последовательное сканирование — это последовательный перебор всех строк базы в поисках интересующего значения. Обычно операция выполняется в десятки раз медленнее, чем индексное сканирование, но то, какая из операций будет применяться в конкретных обстоятельствах, зависит от ограничивающих условий запроса. Если индекса для столбца, в котором нужно найти определенное значение, вообще не создано, то будет выполняться последовательное сканирование. В некоторых случаях, например, при большом объеме данных для одного значения индексного поля, последовательное сканирование может быть эффективнее индексного, так как обычно оно работает с большими блоками данных, поэтому за одну операцию доступа потенциально может выбрать большее количество данных, чем индексное сканирование, соответственно, нужно меньше операций доступа и выше скорость. Вообще, операции ввода/вывода выполняются намного быстрее, чем при произвольном доступе, использующемся при индексном сканировании.

На выходе получается неупорядоченный набор данных.

Bitmap Index Scan / Bitmap Heap Scan / Recheck Cond

Сканирование с использованием битовых карт используется, когда при индексном сканировании нужно выбрать много записей за раз. В отличие от индексного сканирования, строки базы выбираются не по одной для каждого найденного соответствия в индексе, а сразу все. То есть сначала идёт поиск по индексу, а уже потом массовое чтение нужных данных из таблицы по найденным в индексе указателям.  При этом сначала указатели выстраиваются в порядке, в котором запрашиваемые данные физически расположены в базе, чтобы ускорить их чтение. Для этого используется битовая карта, которая требует для своего поддержания дополнительных ресурсов, но это компенсируется возросшей скоростью чтения данных. Дополнительный недостаток — выбираемые данные располагаются не в индексном порядке, что не имеет значения, если мы не используем оператор order by.

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

В общем, такое сканирование — это по сути комбинация методов, описанных выше — последовательные операции ввода/вывода и выборка по индексу.

Начало операции требует много времени на чтение всех строк по индексу и их сортировку.

Может комбинировать несколько индексов.

Часто используется для IN или =ANY(массив).

Результирующий набор данных не упорядочен.

Другие операции доступа к данным

Также могут встретиться ключевые слова Function Scan (для условий поиска с применением функций), Values Scan (для операции values) и Result (для простого вывода  результата).

Пример с Result:

tpcc=> explain select 1;
           QUERY PLAN
------------------------------------------
Result (cost=0.00..0.01 rows=1 width=0)

Вывод

Обычное индексное сканирование оптимальнее всего для выборки небольшого числа строк, сканирование с битовой картой — для большего числа выбираемых строк, а последовательное сканирование — когда выбирается существенная часть таблицы. Конечно, операции с использованием индекса могут применяться, только если нужный индекс был создан.

В следующей части читайте об операциях объединения, сортировки, группировки и ограничения числа строк в планировщике PostgreSQL.

Как PHP выводит «Hello World»? — Взгляд за кулисы.

Это перевод с английского замечательной статьи Abhinav Singh.

Вам когда-нибудь было любопытно, как же PHP выводит для вас в браузере фразу “Hello World” с помощью echo? Например, у меня не было интереса, пока я не прочёл о внутренностях PHP и его расширениях. Мне подумалось, что, может, некоторым из вас будет занимательно открыть для себя другую сторону PHP, так что приступим. Здесь я коротко освещу тему «Как PHP штампует контент, запрошенный на веб-странице».

Общее описание

Вот что происходит по шагам:

  1. Мы никогда сами не запускаем PHP-демонов или что-то в этом роде. Когда мы запускаем Apache, то он также запускает PHP-интерпретатор (автор рассматривает среду с веб-сервером Apache, но многие вещи справедливы и для других конфигураций — прим. пер.).
  2. PHP связан с Apache (SAPI, серверным API) посредством модуля mod_php5.so.
  3. PHP в целом состоит из трех модулей (ядро PHP, движок Zend и уровень расширений).
  4. Ядро PHP — это модуль, который обрабатывает запросы, файловые потоки, имеет дело с ошибками и т.п.
  5. Движок Zend (ZE) преобразует человекочитаемый код в понятные машине лексемы/коды операций (op-codes). Затем он выполняет этот сгенерированный код в виртуальной машине.
  6. Расширения — это наборы функций, классов, потоков, доступных PHP-скриптам, которые можно использовать в конкретных задачах. Например, расширение mysql для подключения к базе данных MySQL при помощи PHP.
  7. Когда движок Zend (ZE) выполняет сгенерированный код, скрипт может затребовать доступ к некоторым расширениям. Тогда ZE передаёт управление модулю/слою расширения, который после выполнения задач передаёт управление обратно ZE.
  8. Наконец, движок Zend возвращает результат обратно ядру PHP, которое передаёт его уровню серверного API, которое в конце концов отображает его в вашем браузере.

На шаг глубже

Но подождите! Это ещё не всё. Выше была приведена лишь высокоуровневая блок-схема. Давайте копнём чуть глубже и посмотрим, что ещё происходит за кулисами:

  1. Когда мы запускаем Apache, тот также запускает интерпретатор PHP.
  2. Запуск PHP осуществляется за 2 шага.
  3. Первый шаг — выполнение начальной установки структур и значений, которые сохраняются всё время жизни SAPI.
  4. Второй шаг — для временных настроек, которые существуют только для запроса одной страницы.

Шаг 1 запуска PHP

Запутались в шагах 1 и 2? Нет причин для беспокойства, далее мы обсудим то же самое в деталях. Давайте сначала посмотрим на шаг 1, который обычно является основным. Помните, что шаг 1 происходит даже перед тем, как сделан какой-нибудь запрос страницы.

  1. Когда мы запускаем Apache, он запускает PHP интерпретатор
  2. PHP вызывает метод MINIT каждого подключённого расширения. Посмотрите на свой файл php.ini, чтобы увидеть модули, подключённые по умолчанию.
  3.  MINIT — это модульная инициализация (Module Initialization). Каждый метод модульной инициализации инициализирует и определяет набор функций, классов, которые будут использованы в будущих запросах страниц.

Типичный MINIT-метод выглядит так:

PHP_MINIT_FUNCTION(extension_name) {

/* Инициализация функций, классов и т.д. */

}

Шаг 2 запуска PHP

  1. Когда сделан запрос страницы, уровень SAPI передаёт управление уровню PHP. PHP затем настраивает среду для выполнения запрошенной PHP страницы. Он создаёт символьную таблицу, в которой будут храниться переменные, использующиеся при выполнении этой страницы.
  2. Затем PHP вызывает RINIT-метод каждого модуля. RINIT-модуль — это модуль инициализации запроса (Request Initialization Module). Классический пример реализации RINIT-модуля — модуль сессий. RINIT-метод модуля сессий, если подключен в php.ini, предзаполнит переменную $_SESSION и сохранит её в символьной таблице.
  3. О модуле RINIT можно думать, как о директиве auto_prepend_file, которая добавляется к началу каждого PHP-скрипта перед его выполнением.

Типичный RINIT-метод выглядит так:

PHP_RINIT_FUNCTION(extension_name) {

/* Инициализация переменных сессии, предзаполнение переменных, переопределение глобальных переменных и т.д. */

}

Шаг 1 завершения работы PHP

Так же, как и запуск PHP, его остановка осуществляется за 2 шага.

  1. После завершения выполнения страницы по достижении конца скрипта или из-за вызова функции exit() или die() PHP начинает процесс очистки. Он вызывает метод RSHUTDOWN каждого расширения. RSHUTDOWN можно представить как директиву auto_append_file каждому PHP-скрипту, который будет выполнен при любых обстоятельствах.
  2. Метод RSHUTDOWN удаляет символьную таблицу (управление памятью) посредством вызова unset() для всех переменных в символьной таблице.

Типичный метод RSHUTDOWN выглядит так:

PHP_RSHUTDOWN_FUNCTION(extension_name) {

/* Управление памятью, unset всех переменных, использованных в последнем вызове PHP и т.д. */

}

Шаг 2 завершения работы PHP

Наконец, когда все запросы сделаны, и SAPI готово к завершению работы, PHP приступает к второму шагу завершения работы.

  1. PHP вызывает метод MSHUTDOWN каждого расширения, что является последним шансом для каждого расширения отменить регистрацию обработчиков и высвободить всю память, выделенную во время MINIT-цикла.

Типичный метод MSHUTDOWN выглядит так:

PHP_MSHUTDOWN_FUNCTION(extension_name) {

/* Free handlers and persistent memory etc */

}

И это приводит нас к концу того, что мы называем жизненным циклом PHP. Важно заметить, что шаг 1 запуска и шаг 2 завершения работы происходят, когда не осуществляется никаких запросов к веб-серверам.