MySQL конвертируем cp1251 внутри latin1 в utf8 или "charset hell"

Имеем такую ситуацию: есть бд mysql, все таблицы этой бд в кодировке latin1, текст внутри этих таблиц засунут в кодировке cp1251. Цель: конвертировать таблицы и данные в них в utf8.

КОРОТКО И ВНЯТНО
mysqldump -u user -p --default-character-set=latin1 --set-charset db_name > dump_db.sql
iconv -f cp1251 -t utf8 dump_db.sql > dump_db_utf.sql
Затем заменить в конвертированном дампе кодировку latin1 на utf8 вручную.

ДЛИННО И ВДУМЧИВО
Понадобится: консоль, screen, iconv, vim(другой ваш любимый редактор) и mysql c mysqldump конечно.

Делаем дамп исходной базы обычным образом (чтобы не корячить боевую)
mysqldump -u user -p db_name > dump.sql
Создаем в mysql базу для того чтобы над ней измываться и заливаем туда наш дамп
mysql -u user -p
# ввели пароль, зашли
CREATE DATABASE db1;
# выходим из mysql ^D
mysql -u user -p db1 < dump.sql
# ввели пароль
Итого имеем тоже самое в нашей тестовой базе. Теперь чтобы понять суть проблемы сделаем так: переведем кодировку в screen в cp1251 и заглянем в бд чтобы увидеть русский.
# переводим кодировку консоли в screen (или вашим способом)
^a:encoding cp1251
# идем в mysql
mysql -u user -p
# переключаемся на кодировку нашей бд
set names latin1;
use db1;
# выбираем поле в котором ожидаем русский язык
select field from table_name limit 1;
Если русский увидели то все ок. Дальше все понятно. mysqldump умеет выгружать данные с установленным чарсетом, делается это вот так
mysqldump -u user -p --default-character-set=latin1 --set-charset db1 > dump_db1.sql
То есть выводим содержимое в файл так же как мы смотрели выше через mysql, установив кодировку исходной БД. Теперь открываем наш дамп в vim. Учитывая что кодировка консоли у нас сейчас тоже выставлена в cp1251 - должны увидеть в дампе русский текст. Выходим из редактора и конвертируем дамп
iconv -f cp1251 -t utf8 dump_db1.sql > dump_db1_utf.sql
Теперь меняем кодировку консоли обратно в utf8
# для screen
^a:encoding utf8
Открываем наш дамп в utf8 и правим кодировки latin1 на utf8
vim dump_db1_utf.sql
# замена с подтверждением для vim
:%s/CHARSET=latin1/CHARSET=utf8/gc
Ещё в начале дампа надо будет изменить строку вида
/*!40101 SET NAMES latin1 */;
На вот такую
/*!40101 SET NAMES utf8 */;
Все, имеем дамп в utf8. По аналогии немного повращав мозгами можно справляться с другими подобными заморочками.

Node.js + MySQL FOUND_ROWS()

Столкнулся с багой оригинального характера. Когда написал свою первую прокладку под mysql сильно радовался тому, что все работает и довольно привычно-удобно. Но оказывается я страшный лошара. В этой самой прокладке есть метод foundRows который должен возвращать (и это иногда работает!) количество рядов в запросе без учета LIMIT (см. FOUND_ROWS()). Загвоздка в том что FOUND_ROWS() сработает только для того запроса который был перед ним в пределах одного коннекта. То есть сначала запрос на select с лимитом и следующим обязательно должен быть select found_rows() примерно так
SELECT SQL_CALC_FOUND_ROWS * 
FROM tbl_name
WHERE id > 100 
LIMIT 10;

SELECT FOUND_ROWS();
В ситуации с асинхронными запросами, когда используется только один коннект, при приличной паралельной загрузке между этими двумя запросами вполне может проскочить другой запрос и FOUND_ROWS() начнет "врать". По сути очередь будет выглядеть так
SELECT SQL_CALC_FOUND_ROWS * 
FROM tbl_name
WHERE id > 100 
LIMIT 10;

# пример "проскочившего" запроса
SELECT *
FROM tbl_name_2
WHERE id=12;

SELECT FOUND_ROWS();
И вот уже FOUND_ROWS() возвращает совершенно не то что нужно. Для создания тестовой загрузки проекта можно пользоваться Apache ab например вот так
# 900 раз запросить страницу с 10 конкурентными запросами
ab -c 10 -n 900 http://cocainum.info/post/301/
В моей ситуации во время такого тестирования мелкие запросы как раз влезали между запросами с лимитом и запросами подсчета рядов. В результате пейджинг думал что страниц у него ровно одна штука и исчезал со всех своих страниц как класс, пока шла нагрузка с конкурентными запросами.
А теперь внимание вопрос для третьего класса средней школлы.. Как поделить три яблока на четверых чтобы каждый получил по яблоку и никто не получил по лицу. Как же это решить? Я не нашел удобного толкового решения. Callback hell забарывает и на код становится жутко глядеть. Решение есть, смотрим тут Pooling connections. То есть если необходимо по каким-то причинам строго последовательно выполнять запросы, то из пула специально берем отдельный коннект и пользуемся им. Выглядеть это будет примерно так:
pool.getConnection(function(err, conn) {
  conn.query( 'SELECT SQL_CALC_FOUND_ROWS * FROM tab1 LIMIT 100,10', function(err, rows) {
    conn.query('SELECT FOUND_ROWS() as cnt', function(err, rows){
      // получаем общее кол-во строк
      // ...
      // освобождаем соединение
      conn.release();
    });
  });
});
В прокладке я это реализовал, но пока что выглядит все это жутко, надо либо свыкнуться с этой мыслью, либо придумать что-то лучше. Пока переделанную версию выкладывать смысла нет по-моему.
Стоит обратить внимание на секцию настроек "Pool options" хотя бы чтобы научиться регулировать количество соединений connectionLimit.
Вообще доделать конечно надо ради интереса, но по-хорошему надо переезжать уже на Sequelize или что-то такое.

MySql: восстановить дамп бд из .gz файла

Бывает удобно распаковывать .gz файл прямо в бой вот так
gunzip -c db_dump_file.sql.gz | mysql -u root -p db_name

Mysql: как удалить таблицы по префиксу

Как вариант генерим строку запросов
SELECT GROUP_CONCAT('DROP TABLE ', table_name, ';' SEPARATOR '') AS statement 
FROM information_schema.tables 
WHERE table_name LIKE 'prefix\_%';
В результате получим строку из кучи запросов на дроп таблиц с заданным префиксом. Результат проверяем (на соответствие таблиц целевым) и полученный запрос применяем к базе.
В случае, если целевых таблиц очень много — нужно помнить про вот этот нюанс (group_concat_max_len).

Декоратор для mysql - node.js + express.js

dbConfig.js
module.exports = {
  user: 'root',
  password: 'root',
  host: '127.0.0.1',
  database: 'db'
};
db.js
/**
 * decorator for mysql
 */
var mysql = require('mysql'),
    dbConfig = require('./dbConfig'),
    conn = mysql.createConnection(dbConfig);

// standard query
exports.q = function(sql, callee, next){

  if (typeof(callee) != 'object' || !(callee instanceof Array)) {
    calleeSend = [];
  } else {
    calleeSend = callee;
  }

  conn.query(sql, calleeSend, function(err, qres){
    if (err){
      if (typeof(callee) == 'function') {
        return callee(err);
      } else if (typeof(next) == 'function') {
        return next(err);
      } else {
        console.log('Uncaught DB error: ', err);
        console.log('Uncaught error in query: ', sql);
      }
    }

    try {
      if (typeof(callee) == 'function') {
        callee(err, qres);
      } else if (typeof(next) == 'function') {
        next(err, qres);
      }
    } catch(e) {
      next(e);
    }

  });
};

// get row
exports.getRow = function(sql, callee, next){

  if (typeof(callee) != 'object' || !(callee instanceof Array)) {
    calleeSend = [];
  } else {
    calleeSend = callee;
  }

  conn.query(sql, calleeSend, function(err, qres){
    if (err){
      if (typeof(callee) == 'function') {
        return callee(err);
      } else if (typeof(next) == 'function') {
        return next(err);
      } else {
        console.log('Uncaught DB error: ', err);
        console.log('Uncaught error in query: ', sql);
      }
    }

    row = (qres[0]) ? qres[0]: false ;

    try {
      if (typeof(callee) == 'function') {
        callee(err, row);
      } else if (typeof(next) == 'function') {
        next(err, row);
      }
    } catch(e) {
      next(e);
    }

  });

};

// last insert id
exports.lastId = function(next){

  conn.query("SELECT LAST_INSERT_ID() as id", function sres(err, qres){
    var id = (qres[0].id) ? qres[0].id: false ;

    try {
      next(err, id);
    } catch (e) {
      return next(e);
    }

  });
};

// calc found rows
exports.foundRows = function(next){
  conn.query("select found_rows() as cnt", function(err, qres){
    var cnt = (qres[0].cnt) ? qres[0].cnt : false ;

    try {
      next(err, cnt);
    } catch (e) {
      return next(e);
    }
  });
};
Сам mysql — вот этот модуль npm install mysql

Я вообще не уверен что это правильно полностью, надо бы узнать мнение корифеев (где б найти ещё их), но то что оно работает и это вообще возможно и не слишком замороченно - очень радует.

UPD: и правильно думал, ошибка здесь есть - см. Node.js + MySQL FOUND_ROWS()