Rails datetime类型在mysql数据库中的精度问题

在我的博客网站开发过程中,我在开发环境使用sqlite3,而在生产环境则使用性能更好的mysql。一直以来我也觉得没什么,因为rails会屏蔽所有的数据库差异。但这样也带来一些问题,其中一个就是schema.rb的版本控制问题。Rails建议对此文件进行版本控制,这样可以更清楚的看到数据库版本的变动。但由于开发和生产环境使用的数据库不一致,导致每次rake db:migrate后schema.rb都会变化,开发环境和生产环境的这个文件版本不一致(由于数据库的不同),将测试环境的schema.rb传到版本控制显然不好,但是不传的话总是提示有文件未提交感觉很不好。

经过权衡决定在测试环境也使用mysql,并且将测试数据也导入到新的mysql数据库。数据库不一致的另一个问题是我在迁移时发现的,不同的数据库还是有差异的,而且还影响到测试案例的成功与否,其中一个问题就是这次要重点介绍的datetime类型的精度问题。

datetime类型是rails在generate model时自动生成的,如果执行

$ bin/rails generate model Product name:string description:text 

会生成一个迁移文件如下:

#file name: db/migrate/20160501090706_create_products.rb
class CreateProducts < ActiveRecord::Migration
  def change
    create_table :products do |t|
      t.string :name
      t.text :description
 
      t.timestamps null: false
    end
  end
end 

其中 t.timestamps null: false会生成2个字段created_at,updated_at,其schema.rb中的

#file name : schema.rb
#...
    t.datetime "created_at"
    t.datetime "updated_at"
#...

这种datetime类型的字段是作为时间戳使用的,在sqlite3中为6位精度,如2016-05-01 11:28:38.860909,这没有问题。但是换为使用mysql就有问题了,datetime 类型在mysql中默认精度只到秒,这显然不能满足要求,秒级的时间戳是没有意义的。在网上找了很多资料,有很多比较陈旧,下面把我测试通过的解决方法记录一下,方便大家参考1 2

在Rails 4.2的Release Notes有下面一句话:

Added support for fractional seconds for MySQL 5.6 and above

可以看出Rails 4.2已经支持日期精度功能,关于mysql的fractional seconds功能可以参考 mysql reference.

我们在Rails 4.2+应用中应该如何使用此功能呢?可以通过添加migrate文件来解决,具体如下:

 bin/rails g migration ChangeDatetimeLimitForMysql 

在生成的文件中修改如下:(下面只以修改一个表为例,多个表修改方法类似)

# file name: db/migrate/20160501090706_change_datetime_limit_for_mysql.rb
# mysql 5.6.4以上的版本支持分数精度(fractional seconds),默认mysql精度只到秒
# limit 置为 6 精确到微妙;如将limit 修改为 3 则精确到毫秒

class ChangeDatetimeLimitForMysql < ActiveRecord::Migration
  def up
    change_column :comments, :created_at, :datetime, limit: 6
    change_column :comments, :updated_at, :datetime, limit: 6

     #...
  end
  
  def down
    change_column :comments, :created_at, :datetime
    change_column :comments, :updated_at, :datetime

    #...
  end
end 

然后执行rake db:migrate即可,这样之后新增修改的记录的时间戳都会精确到微秒。注意测试环境也需要执行rake db:test:prepare使其生效。

[注]:此方案适用于Rails 4.2+,mysql 5.6.4+.

参考资料:

文章未经特殊标明皆为本人原创,未经许可不得用于任何商业用途,转载请保持完整性并注明来源链接 《ITechLib》

文件字符编码概述

作为一个程序员,不可避免要经常与字符编码打交道。代码乱码、数据乱码、报文乱码也是时常遇到。近期生产上也出现了乱码、非法字符问题,趁着这个机会对字符编码进行下总结。

1.文件内容格式概述

首先大家应该理解:文件其实是一系列的二进制流,界面展示(或程序执行)相当于对这些二进制流的一种转义。理解这一点再考虑后面的问题就简单多了。

对于文件来说里面可以存放任何内容,例如文件可以存放文本、图片、音乐、视频甚至是程序二进制可执行文件。不同的文件需要不同的软件才能进行查看,例如一般的文本可以用记事本、UltraEdit、vim等来查看;图片可以用系统自带的看图工具打开,但编辑则需要Photoshop等专业工具;音乐和视频文件也类似。为了进一步标识哪些文件可以用什么软件打开或编辑,就有了文件拓展名(后缀),例如.txt、.pdf、.mp3等等,文件拓展名只是一个标识,并不影响具体的文件内容和展示。

下面再说下二进制与文本文件的区别:文本文件是指你能够直接用文本编辑器打开,能直接读得懂。二进制文件则需要特定的软件来读取。

2.文本文件编码问题

文本文件是我们最常遇到的文件格式,它们的后缀有很多种比如txt,log,rb,java,h,cpp等等,当然也不局限于这些后缀。刚也说过了后缀并不影响文件的内容和展示,一个没有后缀的文本文件也可以正常打开和查看。文本文件其实也是一系列的二进制流,但它是遵循某个编码标准的(另外还有BOM等特殊规定),常见的编码标准有ASCII、GBK、UTF-8等等。这些编码定义了二级制和字符之间的映射关系,例如汉字“中”的UTF-8编码为E4 B8 AD,它的GBK编码为D6 D0。编码可以参考这些博文1 2

假设文件是用GBK编码写的,但是当成UTF-8格式来读,得到的结果肯定是不对的,可能会有乱码或者天书文字。当然了,现在编辑器还是挺智能的,它们一般能识别出文件使用的编码,所以看上去没什么问题。遇到乱码问题需要做两件事:1.确定文件使用的正确编码格式;2.利用iconv等编码转换工具将文件转换为其他你指定的编码;或者指定让编辑器采用和文件一致的格式来读取。

大家可能还遇到过一种事情,我的编码选对了,大部分字符都能正确显示,但仍然有部分是?、[]或是其他乱码,这种一种可能是文件本身有问题,另一种是字符集的问题。比如Unicode定义了U+00000-U+10FFFF所有的字符集(其中U+D800-U+DFFF为UTF-16的代理对字段),但是一般的字体、软件仅支持U+0000-U+FFFF中的常用字符。对于这之外的即使是合法的字符,也不支持显示。

3.不同程序对于字符编码的支持

不同程序对于字符编码的支持各异,下面分别以java和ruby为例进行简单说明:

Java编码

在Java内部支持unicode编码,严格地说它采用的是UTF-16编码,对于BMP字符严格支持,但对于增补字符U+100000-U+10FFFF支持不佳,在调用函数时需小心。具体参见3 4

在java中常见的中文string,length方法和substring方法都能得到正确的结果,但是对于增补字符会有问题,可能出现半个字的问题。Java中的length对于U+0000-U+FFFF的字符会认为是长度为1,但对于增补字符会认为是2。

String str = "今天天气很好" ;
Assert. assertEquals(6, str.length());
Assert. assertEquals("天气", str.substring(2, 4));
Assert. assertEquals(2, "\uDD1E\uD834".length());//增补特殊字符
Assert. assertEquals("\uD834", "\uDD1E\uD834".substring(1, 2));

Ruby编码

在ruby中1.9+采用原始文件的编码,可以在文件、io等进行指定,字符串长度等方法对于字符集可以很好的支持,对于增补字符也能得到正确的结果。具体参见5

"今天天气很好".length   =>  6
"\u{1DD1E}".length  => 1

4.其他的编码规范

URL编码:

在URI规范中存在一些保留字符,如:/?&=@%等,这些字符如果要作为参数使用而不是作为特殊字符使用,必须在%字符后以十六进制数值表示,例如%3A表示:;另外由于URL只能使用ASCII编码,所以对于中文等需要按照网页的编码格式进行编码,例如“中”应表示为%E4%B8%AD;另外URL 不能包含空格,通常使用 + 来替换空格。

XML编码:

在xml中对于特殊字符如 &<>’" 也需要进行转义。例如使用&lt;来表示<。在报文或是文件中需要对于这些特殊字符进行处理,如转义或是加上<![CDATE[ ]]>。否则在读取或保存是会出错。

注:严格地讲,在 XML 中仅有字符 “<”和”&” 是非法的。省略号、引号和大于号是合法的,但是把它们替换为实体引用是个好的习惯。 在XML的头部可以指定文件的编码方式,包括gbk,utf-8等,这些字符并不需要特殊转义,只要和声明的编码一致即可。

HTML编码:

和xml一样,对于&<>’”也需要进行转义处理。另外html对于其他一些不易输入的特殊字符可以使用特殊编码6 7。对于文件中的字符只要和文件头中声明的字符集一致即可。

参考资料:

文章未经特殊标明皆为本人原创,未经许可不得用于任何商业用途,转载请保持完整性并注明来源链接 《ITechLib》

文件字符编码概述

作为一个程序员,不可避免要经常与字符编码打交道。代码乱码、数据乱码、报文乱码也是时常遇到。近期生产上也出现了乱码、非法字符问题,趁着这个机会对字符编码进行下总结。

1.文件内容格式概述

首先大家应该理解:文件其实是一系列的二进制流,界面展示(或程序执行)相当于对这些二进制流的一种转义。理解这一点再考虑后面的问题就简单多了。

对于文件来说里面可以存放任何内容,例如文件可以存放文本、图片、音乐、视频甚至是程序二进制可执行文件。不同的文件需要不同的软件才能进行查看,例如一般的文本可以用记事本、UltraEdit、vim等来查看;图片可以用系统自带的看图工具打开,但编辑则需要Photoshop等专业工具;音乐和视频文件也类似。为了进一步标识哪些文件可以用什么软件打开或编辑,就有了文件拓展名(后缀),例如.txt、.pdf、.mp3等等,文件拓展名只是一个标识,并不影响具体的文件内容和展示。

下面再说下二进制与文本文件的区别:文本文件是指你能够直接用文本编辑器打开,能直接读得懂。二进制文件则需要特定的软件来读取。

2.文本文件编码问题

文本文件是我们最常遇到的文件格式,它们的后缀有很多种比如txt,log,rb,java,h,cpp等等,当然也不局限于这些后缀。刚也说过了后缀并不影响文件的内容和展示,一个没有后缀的文本文件也可以正常打开和查看。文本文件其实也是一系列的二进制流,但它是遵循某个编码标准的(另外还有BOM等特殊规定),常见的编码标准有ASCII、GBK、UTF-8等等。这些编码定义了二级制和字符之间的映射关系,例如汉字“中”的UTF-8编码为E4 B8 AD,它的GBK编码为D6 D0。编码可以参考这些博文1 2

假设文件是用GBK编码写的,但是当成UTF-8格式来读,得到的结果肯定是不对的,可能会有乱码或者天书文字。当然了,现在编辑器还是挺智能的,它们一般能识别出文件使用的编码,所以看上去没什么问题。遇到乱码问题需要做两件事:1.确定文件使用的正确编码格式;2.利用iconv等编码转换工具将文件转换为其他你指定的编码;或者指定让编辑器采用和文件一致的格式来读取。

大家可能还遇到过一种事情,我的编码选对了,大部分字符都能正确显示,但仍然有部分是?、[]或是其他乱码,这种一种可能是文件本身有问题,另一种是字符集的问题。比如Unicode定义了U+00000-U+10FFFF所有的字符集(其中U+D800-U+DFFF为UTF-16的代理对字段),但是一般的字体、软件仅支持U+0000-U+FFFF中的常用字符。对于这之外的即使是合法的字符,也不支持显示。

3.不同程序对于字符编码的支持

不同程序对于字符编码的支持各异,下面分别以java和ruby为例进行简单说明:

Java编码

在Java内部支持unicode编码,严格地说它采用的是UTF-16编码,对于BMP字符严格支持,但对于增补字符U+100000-U+10FFFF支持不佳,在调用函数时需小心。具体参见3 4

在java中常见的中文string,length方法和substring方法都能得到正确的结果,但是对于增补字符会有问题,可能出现半个字的问题。Java中的length对于U+0000-U+FFFF的字符会认为是长度为1,但对于增补字符会认为是2。

String str = "今天天气很好" ;
Assert. assertEquals(6, str.length());
Assert. assertEquals("天气", str.substring(2, 4));
Assert. assertEquals(2, "\uDD1E\uD834".length());//增补特殊字符
Assert. assertEquals("\uD834", "\uDD1E\uD834".substring(1, 2));

Ruby编码

在ruby中1.9+采用原始文件的编码,可以在文件、io等进行指定,字符串长度等方法对于字符集可以很好的支持,对于增补字符也能得到正确的结果。具体参见5

"今天天气很好".length   =>  6
"\u{1DD1E}".length  => 1

4.其他的编码规范

URL编码:

在URI规范中存在一些保留字符,如:/?&=@%等,这些字符如果要作为参数使用而不是作为特殊字符使用,必须在%字符后以十六进制数值表示,例如%3A表示:;另外由于URL只能使用ASCII编码,所以对于中文等需要按照网页的编码格式进行编码,例如“中”应表示为%E4%B8%AD;另外URL 不能包含空格,通常使用 + 来替换空格。

XML编码:

在xml中对于特殊字符如 &<>’" 也需要进行转义。例如使用&lt;来表示<。在报文或是文件中需要对于这些特殊字符进行处理,如转义或是加上<![CDATE[ ]]>。否则在读取或保存是会出错。

注:严格地讲,在 XML 中仅有字符 “<”和”&” 是非法的。省略号、引号和大于号是合法的,但是把它们替换为实体引用是个好的习惯。 在XML的头部可以指定文件的编码方式,包括gbk,utf-8等,这些字符并不需要特殊转义,只要和声明的编码一致即可。

HTML编码:

和xml一样,对于&<>’”也需要进行转义处理。另外html对于其他一些不易输入的特殊字符可以使用特殊编码6 7。对于文件中的字符只要和文件头中声明的字符集一致即可。

参考资料:

文章未经特殊标明皆为本人原创,未经许可不得用于任何商业用途,转载请保持完整性并注明来源链接 《ITechLib》