Удаляются ли временные данные (transients) автоматически?
Этот вопрос возник после прочтения Временные RSS-ленты в wp_options не удаляются автоматически?
Предполагается, что временные данные (transients) должны истекать и удаляться. Однако единственный способ, которым это обрабатывается - когда срок действия временных данных истек и они запрашиваются, только тогда они удаляются во время запроса.
Что произойдет, если срок действия временных данных истек, но после этого они никогда не запрашивались? Из описания в Codex я думал, что подразумевается какая-то сборка мусора. Теперь я не уверен и не могу найти код, который выполняет такую очистку.
Значит ли это, что они навсегда останутся в базе данных?
Теперь это так
Начиная с WordPress 3.7, просроченные транзиенты удаляются при обновлении базы данных, см. #20316
Старый ответ
Если никто не сможет доказать обратное, то похоже, что транзиенты вообще не удаляются автоматически. Еще хуже то, что в отличие от опций, они не обязательно хранятся в базе данных. Поэтому нет надежного способа получить список всех транзиентов для проверки их срока действия.
Временный код для очистки устаревших транзиентов, если используется хранение в базе данных:
add_action( 'wp_scheduled_delete', 'delete_expired_db_transients' );
function delete_expired_db_transients() {
global $wpdb, $_wp_using_ext_object_cache;
if( $_wp_using_ext_object_cache )
return;
$time = isset ( $_SERVER['REQUEST_TIME'] ) ? (int)$_SERVER['REQUEST_TIME'] : time() ;
$expired = $wpdb->get_col( "SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE '_transient_timeout%' AND option_value < {$time};" );
foreach( $expired as $transient ) {
$key = str_replace('_transient_timeout_', '', $transient);
delete_transient($key);
}
}

$time = $_SERVER['REQUEST_TIME']; и затем использование $time в SQL-запросе - не делайте так. Будьте более осторожны с переменными/значениями $_SERVER, чтобы предотвратить SQL-инъекции.

@hakre хм... Я взял это из презентации по производительности PHP, где рекомендовали использовать это вместо time()
, которое может вызывать ошибки (выполнение не является мгновенным по своей природе). Время запроса устанавливается самим PHP, оно не поступает из каких-либо пользовательских данных. Почему это считается уязвимостью?

@Rarst: Я не сказал, что не следует использовать это, просто нужно убедиться, что оно безопасно экранировано для использования в SQL-запросе. Так следует поступать с каждой переменной из внешнего источника. Переменные $_SERVER могут быть установлены не так, как ожидается, а вместо этого их может задать сам запрашивающий пользователь. Я всего лишь хотел продвинуть хорошие практики программирования. Как всегда, чтобы узнать реальное состояние доступности, обратитесь к документации. Например, для PHP 4 такой переменной не существует, и она может быть перезаписана пользовательским заголовком или переменной окружения - http://php.net/manual/en/reserved.variables.server.php

@hakre исправлено (я думаю), кстати, спасибо за напоминание про PHP4 (я не могу дождаться, когда WordPress перестанет его поддерживать)

На мой взгляд, это выглядит намного лучше ;). Будем надеяться, что не возникнет проблем с time() и отрицательными числами, которые могут случайно удалить все или ни одного временного значения. Никогда не доверяй работающей системе :P

На случай, если ты не знал, _ — это wildcard-символ для одного символа в операторах LIKE, и его желательно экранировать. :)

@Denis да, я это знаю... Но в этом запросе нет практической разницы?.. Разве что кто-то умудрится назвать опцию XtransientXtimeoutX или что-то в этом роде.

Разве вы не должны использовать $wpdb->prepare(), чтобы должным образом защитить себя от некорректных данных, о которых говорил @hakre? Это также решит проблему экранирования символа '_'. Я бы рекомендовал использовать этот метод как лучшую практику.

@Tom кроме как "чтобы быть абсолютно уверенным", этот конкретный запрос не требует prepare, и я не стал его добавлять.

Теперь, когда я смотрю на это, ты прав. (int) вероятно, это вся защита, которая тебе нужна для этой серверной переменной.

Переношу некоторые комментарии из обсуждения в ответ, с перефразировкой и изменением форматирования.
По сути, всё сводится к тому, что если у вас нет сверхэкстремального случая, то "сборка мусора" для них не требуется. Если вы никогда их не запрашиваете, то неважно, существуют они или нет.
Дело в том, что по умолчанию транзиенты хранятся в таблице options. В базовой установке таблица options может содержать около 100 записей. Каждый транзиент добавляет ещё две записи, но даже если их тысячи, это не влияет на скорость сайта, так как они не загружаются автоматически.
При запуске WordPress загружает опции в память, но только те, у которых включён флаг autoload. У транзиентов его нет, поэтому они не загружаются в память. Только транзиенты, которые действительно используются позже, несут затраты.
С точки зрения базы данных, таблица options имеет индексы как по option Id, так и по option name. Транзиенты всегда загружаются по имени (ключу), поэтому их поиск — это простые SELECT-запросы по уникальному ключу. Таким образом, поиск выполняется за O(log(n)) и очень быстр. С Big-O log(n) вам потребуются миллионы строк, чтобы это стало заметно. Честно говоря, накладные расходы на подготовку и завершение запроса, а также передачу данных занимают гораздо больше времени. Сам запрос выполняется практически мгновенно. Поэтому просто наличие неиспользуемых строк не влияет ни на что, кроме занимаемого места на диске.
Индексирование в базах данных — одна из тех сложных концепций, которые непонятны тем, кто не разобрался в происходящем "за кулисами". Базы данных изначально созданы для быстрого поиска данных и легко справляются с такими вещами. Вот полезная статья: http://en.wikipedia.org/wiki/Index_(database) (на английском).
Теперь о чистке. Самый очевидный способ (вызов SQL DELETE) на самом деле не удаляет записи из базы. Он просто убирает их из индекса и помечает строку как "удалённую". Так работают базы данных. Чтобы освободить место на диске, нужно выполнить OPTIMIZE TABLE, и это небыстрая операция. Она занимает время, вероятно, большее, чем стоит. В целом, это вряд ли сэкономит вам CPU-время.
Если у вас есть случай, когда постоянно создаются новые неиспользуемые транзиенты, нужно искать коренную проблему. Что их создаёт? Используются ли изменяющиеся ключи? Если да, то плагин или код, вызывающий это, следует исправить, чтобы он так не делал. Это будет полезнее, потому что код, который неправильно их создаёт, скорее всего, также не извлекает их, выполняя лишнюю работу.
С другой стороны, может быть случай, когда транзиенты создаются, например, для каждой записи. Это может быть совершенно нормально. Я сам так делаю в SFC для хранения входящих комментариев из Facebook. Каждая запись может иметь связанный транзиент, то есть две дополнительные строки на запись. Если у вас 10 тыс. записей, в таблице options будет 20 тыс. строк (в конечном итоге). Это не плохо и не медленно, потому что, опять же, разница между 100 и 20 000 строками для базы данных несущественна. Всё проиндексировано. Всё молниеносно. Доли миллисекунд.
Когда строк становится миллионы, тогда стоит забеспокоиться. Когда размер таблицы options превышает сотни мегабайт, стоит присмотреться внимательнее. Но в целом это не проблема, за исключением экстремальных случаев. Это точно не проблема для чего-то меньшего, чем крупный новостной сайт с сотнями тысяч записей. А для любого достаточно большого сайта, где это может стать проблемой, следует использовать внешнее кеширование объектов, и в таком случае транзиенты автоматически сохраняются там, а не в базе данных.

Примечание: временные данные (transients) без срока действия действительно загружаются автоматически, и отсутствие срока действия является значением по умолчанию, поэтому если приложение/плагин создает много временных данных без установки срока действия, они будут занимать значительный объем памяти при каждой загрузке страницы/записи.

Нет никаких причин использовать "временные данные без срока действия", потому что они по сути идентичны обычным "настройкам" (options).

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

Также, этот вариант не идентичен настройке, так как он будет очищаться при использовании кэша объектов — подробности смотрите в недавней статье на WPEngine.

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

:) (потому что это не потому, что стандартное поведение WordPress ошибочно, да?)

Например, 7 дней. Если автор плагина/темы хочет большее или меньшее значение, он его укажет. Если они хотят автозагрузку, им не нужно указывать 0 для срока действия (= бесконечность), но именно это они сейчас получают, когда параметр срока действия выполняет двойную роль параметра автозагрузки да/нет. В любом случае, срок действия по умолчанию не должен также подразумевать автозагрузку=да по умолчанию; это просто напрашивается на проблемы.

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

Отто - Я совершенно с тобой не согласен. Проблема в том, что со временем, со всеми этими транзиентами, размер таблицы становится абсурдно большим. Не нужно миллионов строк, чтобы система начала тормозить. Я сейчас работаю с таблицей опций, в которой более 130 тысяч строк, и она регулярно зависает. Поскольку поле value имеет тип large text, даже поиск только строк с "autoload" превращается в кошмар с точки зрения производительности. Эти поля value хранятся отдельно от остальных данных строки. Даже логически это часть одной таблицы, для выборки нужных строк должны происходить соединения (joins). Соединения, которые теперь занимают вечность, потому что нужные данные разбросаны по всему диску. Профилирование (с использованием Jet Profiler для MySQL) подтвердило это.
Добавление auto-load в кластерный ключ может помочь решить эту проблему. Кластеризация по Autoload Desc, ID ASC, например, позволит всем строкам с autoload группироваться вместе на диске. Но даже в этом случае, с точки зрения базы данных, нагрузка будет огромной.
Лично я считаю, что дизайн этой системы не продуман. Таблица опций превратилась в общую свалку для множества вещей. Это нормально, если поле value достаточно мало, чтобы включаться на ту же страницу, что и остальные данные строки, и может быть эффективно проиндексировано. К сожалению, это не так. Тот, кто это проектировал, должен вернуться на курсы по основам баз данных.

верно, но учтите, что когда начиналась разработка WordPress, никто не предполагал, что у него будут тысячи плагинов, использующих таблицу options как хранилище данных :)
