MySQL, el extraño caso de un campo timestamp

Hace un tiempo descubrí una característica (tal vez sea un bug) sobre los campos timestamp de MySQL. Es probable que este documentado en alguna parte que todavía no he leído:

Cuando se añade un campo timestamp a una tabla, MySQL agrega mágicamente algunas características al nuevo campo creado como un «trigger» y la fecha actual como valor por defecto.

Aquí esta el script donde se produce el caso:

-- CREANDO UNA TABLA CUALQUIERA E INSERTANDO DATOS 
mysql> create table t(
    -> id int not null primary key auto_increment,
    -> val varchar(50)
    -> );
Query OK, 0 rows affected (0.15 sec)

mysql> insert into t (val) values ("foo") ,("var");
Query OK, 2 rows affected (0.08 sec)
Records: 2  Duplicates: 0  Warnings: 0

mysql> select * from t;
+----+------+
| id | val  |
+----+------+
|  1 | foo  |
|  2 | var  |
+----+------+
2 rows in set (0.00 sec)


-- AGREGANDO UN CAMPO TIMESTAMP Y MAS DATOS
mysql> alter table t add ts_field timestamp;
Query OK, 2 rows affected (0.35 sec)
Records: 2  Duplicates: 0  Warnings: 0

mysql> insert into t (val) values ("foo 2") ,("var 2");
Query OK, 2 rows affected (0.06 sec)
Records: 2  Duplicates: 0  Warnings: 0

-- HE AQUI LA MAGIA:
mysql> select * from t;
+----+-------+---------------------+
| id | val   | ts_field            |
+----+-------+---------------------+
|  1 | foo   | 0000-00-00 00:00:00 |
|  2 | var   | 0000-00-00 00:00:00 |
|  3 | foo 2 | 2013-01-09 23:20:01 |    <---
|  4 | var 2 | 2013-01-09 23:20:01 |    <---
+----+-------+---------------------+
4 rows in set (0.00 sec)

¿¡Pero que acaba de pasar!?
no lo sé.

La nueva estructura de la tabla es:

CREATE TABLE `t` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `val` varchar(50) DEFAULT NULL,
  `ts_field` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=latin1

Esto sólo pasa cuando en la tabla no existe otro campo timestamp aún.

Ahora, es útil? tal vez. Es un BUG? tal vez.

El "extraño caso" se repite en MySQL 5.1 y 5.5.

Update: Es una característica documentada en https://dev.mysql.com/doc/refman/5.5/en/timestamp-initialization.html , me disculpo por el post que finalmente resultó ser una chorrada, un lapsus, un horror.

MySQL Vista global de las bases de datos

Cansado de tener que averiguar manualmente  cuanto espacio ocupan mis bases de datos, acabo de crear un procedimiento almacenado para tener una vista global de las bases de datos que tenemos en nuestro servidor MySQL.

En MySQL no hay disponible  un comando que nos permita tener una resumen global de las bases de datos, algo que se parezca a SHOW TABLE STATUS pero para todas las bases de datos. El comando SHOW DATABASES sólo lista las bases actuales pero no nos dice cuanto espacio ocupan o cuantas tablas hay, etc.

El procedimiento que escribí esta basado en la base de datos INFORMATION_SCHEMA, que contiene mucha información de todas las bases de datos existentes.

Ya antes en un post anterior mencioné que podemos tener todas estas «herramientas» en una base de datos llamada tools, es por eso que recomiendo que éste procedimiento este en la misma base de datos tools (pero de todos modos puedes elegir otro lugar)

Al llamar al procedimiento si tenemos una vista global con información que nos será de utilidad para tas tareas de mantenimiento.

 

mysql> call tools.sp_overview();
+------------------------------+---------+--------+----------+---------+
| Database                     | Charset | Tables | Routines | Size Mb |
+------------------------------+---------+--------+----------+---------+
| database1                    | utf8    |     43 |       28 |     7.0 |
| database2                    | latin1  |     43 |       28 |   205.0 |
| database3                    | utf8    |    116 |        0 |   126.2 |
| database4                    | utf8    |     99 |        0 |     0.3 |
| database5                    | utf8    |    165 |        0 |    77.4 |
| database6                    | utf8    |    121 |        2 |   719.4 |
| database7                    | utf8    |    122 |        0 |    91.3 |
| database8                    | utf8    |    116 |        0 |    89.7 |
| database9                    | utf8    |    124 |        0 |     4.5 |
| database10                   | utf8    |    113 |        0 |   147.7 |
| database11                   | latin1  |    119 |        3 |   436.4 |
| database12                   | latin1  |    122 |        0 |   439.1 |
| database13                   | latin1  |    122 |        4 |   452.7 |
| database14                   | utf8    |    115 |        0 |   273.0 |
| database15                   | utf8    |    122 |        0 |   265.5 |
| database16                   | utf8    |      0 |        0 |    NULL |
| database17                   | latin1  |     26 |        0 |     0.9 |
| database18                   | latin1  |      9 |        0 |     0.0 |
| database19                   | latin1  |     22 |        0 |     0.3 |
+------------------------------+---------+--------+----------+---------+
19 rows in set (3.01 sec)

 

Advierto que la primera vez el procedimiento es muy lento, y puede demorar varios segundos.

Aquí tienen El código fuente del procedimiento:

 

DELIMITER $$

DROP PROCEDURE IF EXISTS tools.sp_overview$$
CREATE PROCEDURE tools.sp_overview()
BEGIN

    SELECT s.SCHEMA_NAME as `Database`, s.DEFAULT_CHARACTER_SET_NAME as `Charset`,
        count(t.TABLE_NAME) as `Tables`,

        (SELECT count(*) from information_schema.ROUTINES as r
            WHERE r.routine_schema = s.SCHEMA_NAME) as `Routines`,

         round(sum(t.DATA_LENGTH + t.INDEX_LENGTH) / 1048576 ,1) as `Size Mb`

        FROM information_schema.SCHEMATA AS s
            LEFT JOIN information_schema.TABLES t on s.schema_name = t.table_schema
        WHERE s.SCHEMA_NAME not in ('information_schema', 'performance_schema')

    GROUP BY s.SCHEMA_NAME;
END$$
DELIMITER ;

¿Estaremos condenados a tener hijas?

Es la pregunta que me hice cuando caí en la cuenta de que casi todos mis «colegas» han tenido hijas, no es tan alarmante como parece, al parecer sólo 1 de cada 10 colegas han tenido hijos… un momento… O_o

…Antes que me lapiden, personalmente no creo que sea malo ser mujer o tener hijas, he dicho «condenados» en buena onda… así es que guárdense sus comentarios, ahora sigamos:

Mi muestra se limita a los pocos amigos que tengo, pero cuando me moví hacia otros círculos he notado una proporción similar y cuando lo comento con mis amigos también notan que algo raro está pasando ¿cuál será la causa?

No me atrevo a señalar una causa específica, pero podemos intuir sobre los presuntos culpables:

  1. El estrés, probablemente la principal causa, nosotros sabemos lo que significa tener encima la presión de terminar un proyecto en la fecha programada, de hecho el estrés afecta a todo nuestro organismo.
  2. Los equipos electrónicos con los que trabajamos día a día, sé que no hay un estudio definitivo sobre cuanto afecta a “nuestros muchachos” los celulares, laptops, puntos Wi-Fi, etc., pero estamos con ellos todo el tiempo e incluso muy cerca de la zona… el lugar donde… bueno el órgano este… los huevos pues no?
  3. Mala praxis, consultando con un ginecólogo, a éste le resulta muy fácil explicar la razón (combinado con los factores anteriores) y es el no conocer bien como funciona nuestro organismo.
  4. Tal ves sea que estemos “condenados” a ello, punto y se acabó.

Encontré unos manuales (ok no son manuales pero sirven como referencia) que resumen la larga conversación que tuve:

http://www.todopapas.com/fertilidad/salud-concebir/nino-o-nina-ahora-puedes-elegir-361

http://www.bbc.co.uk/mundo/noticias/2010/09/100929_fertilizacion_embarazo_men.shtml

http://es.answers.yahoo.com/question/index?qid=20070515130958AAUX60w

Lo sé hay, uno de Yahoo! Answers, que esperaban, me daba un poco de pereza escribir tanto, siempre consulten un especialista antes de hacer cualquier cosa con sus cuerpos.

Bueno, a practicar se ha dicho! Eso sí hay que tener mucha paciencia.

Un abrazo.

La App del Látigo

Hace una semana que estoy fascinado con trollear  a mis amigos con la App del Látigo, lo vi por primera vez en un capítulo de The Big Bang Theory, es una App muy sencilla pero poderosa (si la sabes usar con sabiduría).

Es gratuita y esta disponible para Android (creo que hay versiones en la iStore):

 

The Whip en Google Play

 

Sólo tienen que mover el móvil como si fuera el látigo para que la App haga su trabajo: emitir el sonido del látigo junto a un mensaje motivador!

El autor de la App lo describe como:

The ultimate motivational tool.

The Whip: This app turns your Android-powered phone into the ultimate motivational tool. Useful whether asking your employees to work the weekend, or remarking on a friend’s relationship status…

 

Recomiendo que sólo lo usen en momentos que lo ameriten, por que pierde la gracia si lo usan constantemente.

Aquí un extracto del capítulo:

 

 

 

Migrar de MS Access a MySQL con relaciones

He usado herramientas como MySQL Migration Toolkit (*) y Bullzip’s Access To MySQL, ambos hacen un excelente trabajo pero no exporta las relaciones entre tablas. Hacer esta tarea nos puede tomar varias horas (hasta ahora):

Escribí un código VBA para identificar las relaciones generando código MySQL con las sentencias de creación, esto puede ser muy útil después de la migración usando cualquier herramienta libre.

Option Explicit
'Copiar y pegar esta función en un módulo nuevo/existente de MS Access.

Public Sub printRelations()
    Dim sql, fk As String
    Dim i, j As Integer
    For i = 0 To CurrentDb.Relations.Count - 1
        sql = "ALTER TABLE `" & CurrentDb.Relations(i).ForeignTable & _
            "` ADD CONSTRAINT `" & CurrentDb.Relations(i).Name & "` FOREIGN KEY ("
        fk = "("
        For j = 0 To CurrentDb.Relations(i).Fields.Count - 1
            sql = sql & "`" & CurrentDb.Relations(i).Fields(j).ForeignName & "` ,"
            fk = fk & "`" & CurrentDb.Relations(i).Fields(j).Name & "` ,"
        Next j

        sql = Left(sql, Len(sql) - 1)
        fk = Left(fk, Len(fk) - 1)
        fk = fk & ")"
        sql = sql & ") REFERENCES `" & CurrentDb.Relations(i).Table & "`" & fk & ";"

        Debug.Print sql
    Next i
End Sub

Para ejecutar el código anterior, debemos invocarlo desde la ventana de Inmediato (Ctrl+G), luego solo tendremos que copiar el SQL generado para llevarlo a MySQL.

Enjoy!

(*)MySQL Migration Toolkit esta descontinuado pero aun sigue disponible desde los servidores Mirror de MysQL como:
http://mirrors.dotsrc.org/mysql/Downloads/MySQLGUITools/

MySQL necesita mejorar sus mensajes de error

Acabo de modificar una base de datos, no debió demorar las dos horas que demoré en realizar la tarea si no fuera por que MySQL no ha mejorado aun sus mensajes de error.

La tarea fue agregar una nueva clave foránea a una tabla existente, veamos como reproducir lo que me ha pasado:

-- Crear las tablas foo y bar
create table foo (
	id integer not null primary key,
	bar_id int not null
);
create table bar (
	id integer not null primary key
);
-- Intentar crear una clave foránea
alter table foo
	add foreign key(bar_id) references bar(ANY_FIELD) ;

Al ejecutar la ultima sentencia MySQL devuelve un error genérico que puede significar muchas cosas:

Error Code: 1005. Can't create table 'temp.#sql-4bd7_11' (errno: 150)

Todo hubiera sido mas fácil si me hubiera dado cuenta que escribí mal el campo de la tabla de deferencia bar(ANY_FIELD) (algunas veces pasa), si el mensaje de horror hubiera dicho: "field bar.ANY_FIELD don't exists" no estaría despierto hasta las 2am buscando una solución,

Estoy usando MySQL  5.5.21 community edition.

Valores auto_increment personalizados

En el Perú los formatos numéricos de las facturas es 001-000033 , la serie (001) cambia eventualmente pero el valor auto-incrementado es el mismo:

001-000034
001-000035
001-000036
...

Para manejar esto en MySQL he preparado una solución basada en otro articulo mio:

  1. Crear la tabla para guardar los números actuales:
    create table _sequence
    (
        seq_name varchar(50) not null primary key,
        seq_group varchar(10) not null,
        seq_val int unsigned not null
    );
  2. Crear una función para obtener el valor siguiente e incrementarlo:
    delimiter //
    drop function if exists getNextCustomSeq//
    create function getNextCustomSeq
    (
        sSeqName varchar(50),
        sSeqGroup varchar(10)
    ) returns varchar(20)
    begin
        declare nLast_val int; 
    
        set nLast_val =  (select seq_val
                              from _sequence
                              where seq_name = sSeqName
                                    and seq_group = sSeqGroup);
        if nLast_val is null then
            set nLast_val = 1;
            insert into _sequence (seq_name,seq_group,seq_val)
            values (sSeqName,sSeqGroup,nLast_Val);
        else
            set nLast_val = nLast_val + 1;
            update _sequence set seq_val = nLast_val
            where seq_name = sSeqName and seq_group = sSeqGroup;
        end if; 
    
        set @ret = (select concat(sSeqGroup,'-',lpad(nLast_val,6,'0')));
        return @ret;
    end// 
    
    delimiter ;
  3. Crear un procedimiento almacenado para modificar el valor actual:
    delimiter //
    drop procedure if exists sp_setSeqCustomVal//
    create procedure sp_setCustomVal(sSeqName varchar(50),  
                  sSeqGroup varchar(10), nVal int unsigned)
    begin
        if (select count(*) from _sequence  
                where seq_name = sSeqName  
                    and seq_group = sSeqGroup) = 0 then
            insert into _sequence (seq_name,seq_group,seq_val)
            values (sSeqName,sSeqGroup,nVal);
        else
            update _sequence set seq_val = nVal
            where seq_name = sSeqName and seq_group = sSeqGroup;
        end if;
    end//
    delimiter ;

Haciendo algunas pruebas:

  1. Crear una tabla:
    create table custom_autonums
    (
       id int not null primary key auto_increment,
       seq_1 varchar(20), -- custom sequence 1
       seq_2 varchar(20), -- custom sequence 2
       unique(seq_1),
       unique(seq_2)
    );
  2. Crear un trigger para la tabla:
    delimiter //
    drop trigger if exists custom_autonums_bi//
    
    create trigger custom_autonums_bi before insert on custom_autonums
    for each row
    begin
       set new.seq_1 = getNextCustomSeq("seq_1","001");
       set new.seq_2 = getNextCustomSeq("seq_2","DBA");
    end//
    
    delimiter ;
  3. Insertando algunos valores:
    insert into custom_autonums (id) values (null),(null),(null);
    select * from custom_autonums;
    +----+------------+------------+
    | id | seq_1      | seq_2      |
    +----+------------+------------+
    |  4 | 001-000001 | DBA-000001 |
    |  5 | 001-000002 | DBA-000002 |
    |  6 | 001-000003 | DBA-000003 |
    +----+------------+------------+
    3 rows in set (0.00 sec)
  4. Alterando los valores actuales:
    call sp_setCustomVal('seq_1','001',675);
    
    insert into custom_autonums (id) values (null),(null),(null);
    select * from custom_autonums;
    +----+------------+------------+
    | id | seq_1      | seq_2      |
    +----+------------+------------+
    |  4 | 001-000001 | DBA-000001 |
    |  5 | 001-000002 | DBA-000002 |
    |  6 | 001-000003 | DBA-000003 |
    |  7 | 001-000676 | DBA-000004 |
    |  8 | 001-000677 | DBA-000005 |
    |  9 | 001-000678 | DBA-000006 |
    +----+------------+------------+
    6 rows in set (0.00 sec)

Enjoy!

Instalar bmon como servicio

bmon es una herramienta para monitorear el estado de red actual, es muy útil para saber que cantidad del ancho de banda esta siendo utilizado en ese momento.

Lo mas útil es que guarda (en memoria solamente) información histórica de los últimos 60 segundos, minutos,horas y dias; el único problema es que es necesario que este corriendo todo el tiempo.

 

En un escenario en el que un servidor Linux es usado como proxy para una red (basicamente) Windows nos será de utilidad acceder a esa información en tiempo real desde cualquier parte de la red.

Para ello podemos dejar ejecutando bmon en una sesión cargada con screen al mismo tiempo configuramos bmon para que genere salida HTML cada segundo la cual leeremos desde Apache.

Pasos para instalar y ejecutar:

  1. Instalar screen y bmonsudo apt-get install screen bmon
  2. Crear el directorio /var/www/bmon-html
  3. Crear el archivo /etc/init/bmon.conf  con el siguiente contenido
    description "bmon bandwidth monitoring process"
    
    start on (local-filesystems and net-device-up and runlevel [2345])
    stop on runlevel [016]
    
    exec screen -d -m -S bmon bmon -O html:path=/var/www/bmon-html
  4. Iniciar el servicio:sudo service bmon start
  5. Luego estará disponible la pagina  http://localhost/bmon-html/ , y estará actualizada constantemente.

Enjoy!

 

Lo que no me gusta de Linux Mint 12

Linux Mint se esta afianzando como la distribución, basada en Ubuntu, mas interesante en lo que a interfaz gráfica de usuario se refiere, me imagino que la a la mayoría no le agradó la idea de usar Unity en primer momento.

Con Linux Mint 12 inicialmente tenemos una interfaz con Gnome3 que da la sensación de «menta fresca», que es muy amigable pero algunas de sus características no me han gustado tanto:

  1. El administrador de conexiones de red no permite mas de un perfil. Es cierto, cuando uno lleva su maquina a muchos lugares con conexiones diferentes, es muy útil tener perfiles de conexión individuales de tal modo que no tengamos que ir cambiando los números de IP, DNS, etc., cada vez! al igual que Windows, llega a ser fastidioso.  Puedes utilizar algunos aditamentos/scripts que te ayuden pero no hay la misma comodidad.
  2. Las conexiones de red adicionales no conectan automáticamente. No hay como disfrutar de conectar y usar (plug and play) con las conexiones nuevas (como 3G), Linux Mint 12 necesita que uno active manualmente la red para poder utilizarla, igualmente hay que tomar un tiempo configurando para que sea automático.
  3. El indicador de fecha/hora disminuye su tamaño cuando hay muchos iconos en la barra superior. Mas de una vez eso me causó problemas, mas aun cuando eres conocido como «el rey de la puntualidad».
  4. La tecla Super (Windows) no esta muy bien controlada cuando uno tiene instalado Windows en VirtualBox. Muchos accesos directos de Windows combinan la tecla [Windows] evitando que tengamos que estirar la mano hasta el Mouse, ya se imaginarán usar Windows sin la funcionalidad de la tecla con su nombre.
  5. Las notificaciones visuales cumplen su objetivo pero llegan a ser un fastidio, por que es necesario acercar el Mouse y cerrar la notificación, a menos que nos guste ver el mensaje durante varios minutos.
  6. No es posible situar ventanas en la parte superior sin que éstas se maximicen. Es una característica genial que con solo acercar una ventana a la barra superior ésta se maximice como por arte de magia, pero en Linux Mint 12 necesitan ajustar los parámetros para que no ocurra siempre. Por ejemplo para mover a la parte superior la ventana principal de Skype o Pigdin tenemos que hacer malabares para evitar que se maximicen automáticamente.
  7. Ya no puedo usar Negative de Compiz. Hay otros efectos que se ven geniales, pero Negative me ayuda a bajar la intensidad de brillo de mi laptop cuando estoy en ambientes con poca luminosidad.
  8. Ya no esta disponible las divertidas Fortunes. Con Linux Mint 11 teníamos configurado por defecto Fortunes, era divertido abrir una terminal, no es difícil de configurar pero debieron mantener esa característica.
  9. Hay mas, pero no creo que sea necesario mencionar el resto de los detalles incómodos que encontré.

A pesar de las cuestiones mencionadas, Linux Mint 12 sirve para hacer lo que necesites (como cualquier distribución) pero todas muchas de esas características son configurables y podemos hacer que trabajen como nosotros queremos, pero toma un tiempo y la verdad no me interesa hacer eso cuando hay tareas mas importantes y sobre todo si eres un usuario gruñón.

Creo que la balanza se inclina mas al «No me gusta», regresaré a Ubuntu y veré como madura Linux Mint con mucha atención.

Enlaces: