Про формат дампов mysql

Смеркалось. Прилетела задача, залить в mysql таблицу, в которой есть несколько миллиардов записей. Консольная утилита отказывалась ее заливать, с очень непонятным сообщением об ошибке:

mysql -u root -p -D dbname <dump.sql
ERROR 2006 (HY000) at line 2024: MySQL server has gone away

Выяснилось, что за количество вносимых записей за один оператор отвечает параметр настройки max_allowed_packet, и его значение рекомендуется увеличить. Однако, выставить его в число, превышающее 4194304, не получается; и 4 млн записей за раз оказалось недостаточно для загрузки в базу такого дампа.

Выход был найден, пришлось дамп снять со следующими параметрами:

mysqldump -u root -p --opt --skip-extended-insert --skip-quick  dbname tablename >dump.sql

Дамп очень сильно разросся, зато теперь он имел отдельный insert на каждую строчку таблицы. Загружался он очень долго, но все-таки загрузился.

Апгрейд MySQL и переписывание запросов

Официальный логотип Mysql

Технически апгрейд базы MySQL несложен, но могут возникнуть не очевидные подводные камни. Например, если MySQL был 4-й версии, а после upgrade версия стала выше 5.0.11, то могут возникнуть проблемы с работой запросов, использующих LEFT JOIN.

Начиная с версии MySQL 5.0.12 запросы с JOIN обрабатываются по стандарту SQL:2003, поэтому такие запросы к базе придется переписать. Вот пример:

CREATE TABLE IF NOT EXISTS `t1` (
`id` int(10) unsigned NOT NULL auto_increment,
`type` enum('a','b') default NULL,
PRIMARY KEY (`id`),
KEY `type` (`type`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
INSERT INTO `t1` VALUES (1, 'a'), (2, 'a'), (3, 'b'), (4, 'a'), (5, 'b');

CREATE TABLE IF NOT EXISTS `t2` (
`t1_id` int(10) unsigned default NULL,
KEY `t1_id` (`t1_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
INSERT INTO `t2` VALUES (1), (1), (1), (2), (2);

CREATE TABLE IF NOT EXISTS `t3` (
`t1_id` int(10) unsigned default NULL,
KEY `t1_id` (`t1_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
INSERT INTO `t3` VALUES (1), (1), (2), (2), (3);

#Test 1
select t1.id
from t1, t2
left join t3 on t1.id = t3.t1_id and t1.type = 'a'
where t1.id = t2.t1_id
group by t1.id
#1054 - Unknown column 't1.id' in 'on clause'

#Test 2, swap the order of the conditions in "on"
select t1.id
from t1, t2
left join t3 on t1.type = 'a' and t1.id = t3.t1_id
where t1.id = t2.t1_id
group by t1.id
#1054 - Unknown column 't1.type' in 'on clause'

В MySQL 4.x запрос работает без ошибок, начиная с версии 5.0.12 запрос ругается на синтаксис. Если в эти запросы добавить скобок, все заработает:

select t1.id
from (t1, t2)
left join (t3) on (t1.id = t3.t1_id and t1.type = 'a')
where t1.id = t2.t1_id
group by t1.id

select t1.id
from (t1, t2)
left join (t3) on (t1.type = 'a' and t1.id = t3.t1_id)
where t1.id = t2.t1_id
group by t1.id

CentOS и свежий MySQL

Официальный логотип Mysql

Начиная с версии 5.1 в MySQL поддерживается партиционирование таблиц, а это как раз то, что мне надо. Решил я проапгрейдить свой сервер, но оказалось, что в стандартных репозиториях CentOS mysql довольно старый.

Проблема решается подключением репозитория remi.

Для 32-битной версии:
wget http://download.fedora.redhat.com/pub/epel/5/i386/epel-release-5-3.noarch.rpm
wget http://rpms.famillecollet.com/enterprise/remi-release-5.rpm
rpm -Uvh remi-release-5*.rpm epel-release-5*.rpm

Для 64-битной версии:
wget http://download.fedora.redhat.com/pub/epel/5/x86_64/epel-release-5-3.noarch.rpm
wget http://rpms.famillecollet.com/enterprise/remi-release-5.rpm
rpm -Uvh remi-release-5*.rpm epel-release-5*.rpm

Далее следует отредактировать /etc/yum.repos.d/remi.repo, установить enabled = 1, и запустить:

yum upgrade mysql-server

Интересный баг mysql

Официальный логотип Mysql

По долгу службы пришлось мне отправиться в длительную поездку. Компьютер с собой не возьмешь, и поэтому приспичило меня залить дамп базы Mysql на ноутбук с OpenSuSe 11.0. Dump отказался заливаться, ругнулся вот так:

ERROR 1221 (HY000) at line 3: Incorrect usage of UNION and INTO

Код хранимой функции был чистый, давным-давно вылизанный и ошибок в нем не было. Выяснилось, что на старых версиях mysql может не работать вот такая конструкция:

select 1 into @`avar` from (select 1 union select 1) `a`;

хотя сам запрос:

select 1 from (select 1 union select 1) `a`;

синтаксически правильный и имеет право на жизнь. Баг в конце концов поправили (подробности здесь), но в стандартных репозиториях OpenSuSe 11.0 (OSS, Non-OSS, Debug и Updates) версия mysql осталась старая, с багом.

Проблема лечится подключением репозитория MySQL и обновлением до последней версии сервера mysql.

MySQL и защита от удаления записей в таблицах

Однажды мне понадобилась защита от удаления записей в таблице. Естественно, на триггере BEFORE DELETE, чтобы уж наверняка все работало. В привычном мне Oracle такая задача решается очень просто, с вызовом raise_application_error внутри триггера. Но как оказалось, в MySQL такая задача решается через жоп использование несуществующего поля в какой-нибудь таблице. Вот пример:

delimiter |

CREATE TRIGGER my_table_bd
BEFORE DELETE ON my_table
FOR EACH ROW
begin
declare v int;
if ifnull(old.status,0) > 0 then
select antarktida
into v
from logs u
where africa = 'asia';
end if;
end|

delimiter ;

Полей antarktida, africa в таблице logs нет, поэтому при срабатывании триггера происходит ошибка. Причем ошибка выскочит, если ifnull(old.status,0) > 0. Такая конструкция выглядит ужасно (костыль он и есть костыль), но работает 100%. И похоже, это единственный способ запретить удаление из таблицы.

Экспорт баз MySQL

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

mysqldump -u username -p db_name –routines –extended-insert >dump.sql

Как получить список дат в mysql

Однажды мне потребовалось получить список дат на каждый день 2009 года. Как оказалось, в mysql такой список получить очень просто.

Для этого нужна всего лишь любая таблица, в которой есть не меньше 365 строк. У меня такая таблица была, и называется она games. Скрипт выглядит так:


SET @rownum:=0;
select adddate('2008-12-31',interval @rownum:=@rownum+1 DAY) next_date from games limit 0,365;

Как сбросить пароль в mysql

Иногда бывают ситуации, когда рутовый пароль на базу MySQL проебали где-то потерялся, и его надо срочно перезадать. Если есть доступ к shell на сервере, то сделать это достаточно просто:

1. Останавливаем mysqld:

/etc/init.d/mysqld stop

2. Запускаем mysqld без проверки прав доступа:

mysqld_safe –skip-grant-tables &

3. Заходим под рутом и перебиваем пароль:

mysql -u root -D mysql
mysql> update user set password=password(‘new_password’) where user=’root’;

4. Обновляем права доступа:

flush privileges;

5. Выходим, убиваем mysqld и запускаем его снова:

mysql> exit;
/etc/init.d/mysqld stop
/etc/init.d/mysqld start