【数据库】_由2000W多条开房数据引发的思考、实践----给在校生的一个真实【练耙场】,同学们,来开始一次伟大的尝试吧。

 
×
 

缘起---闲逛博客园

前几天的时候,在某一QQ群看到一条消息“XXX酒店开房XXXBTXX迅雷BT下载”,当时是一目十行的心态浏览,目光掠过时,

第一反应我想多了~以为是XX种子(你懂的~),但并不感兴趣。

直到又回到博客园逛时,看到一篇最多评论的文章:

看看多线程的效率有多差劲! - 张浩华 - 博客园

http://www.cnblogs.com/zhhh/p/3385751.html

于是点击进去了。这时,我才回想起来,当时是自己邪想多了......

原来是2000w条开房数据记录的数据库,浏览了文章后,我表示对这2000w感兴趣了!于是,就催生了此文。

结缘---搜索引擎来牵线

看了上面提到的那文章,我决定也要玩一玩,不过那园友有点儿“懒”了,没有提供数据库以及Demo下载。好吧,这时候我们要发挥搜索本领了,几经搜索,最后是Google帮上的忙(百度搜索到的链接很多都已经被和谐了~)。 (温馨提示:近来上级严查水表,各位传播东西需小心~)。

一个小时过去了,1.7GB的压缩文件终于下载完成:

C:\[fuliba.net]某酒店2000W数据(解压密码:sjisauisa是就数据8很舒适好sjjss).rar
文件大小:1834815332字节
创建日期:2013年10月24日 15:38:55
哈希值(MD5):091AAC2B45D76CE1CD4248E7FFF1C00E

解压后,约8GB多。

小巫见大巫

面对这千万条数据级别的数据库文件,先来看一看我的本机配置信息:

 操作系统名称    Microsoft Windows  Ultimate
 操作系统核心(Kernel)类型    Multiprocessor Free (-bit)

 Microsoft SQL Server 2008R2
 Microsoft SQL Server Management Studio                        10.50.1600.1
 Microsoft Analysis Services Client Tools                        10.50.1600.1
 Microsoft Data Access Components (MDAC)                        6.1.7601.17514
 Microsoft MSXML                        3.0 5.0 6.0
 Microsoft Internet Explorer                        9.10.9200.16686
 Microsoft .NET Framework                        2.0.50727.5472
 Operating System                        

 硬件概要

 CPU:        英特尔 Core i3-380M (双核)
 主板:        方正 R410CP (英特尔 HM55 (IbexPeak-M DH))
 内存:         GBytes
 显卡:        ATI (PARK PRO/XT GL)
 硬盘:        东芝 MK3265GSX

 处理器信息

 处理器:        Intel(R) Core(TM) i3 CPU       M   @ .53GHz
 运行速度:        2533.3 MHz
 核心/线程:        双核, 四线程
 核心代号:        Arrandale SV
 功耗:        25.0 W
 插座:        Socket G1 (rPGA988A)
 一级缓存:        指令:  x  KB, 数据:  x  KBytes
 二级缓存:        集成:  x  KB
 三级缓存:         MB
 特性:        MMX SSE SSE SSE SSSE SSE4. SSE4. EMT64 VT EIST TM1 TM2

 主板信息

 主板厂商:        方正
 主板型号:        方正 R410CP
 芯片组:        英特尔 HM55 (IbexPeak-M DH)
 主板插槽:        2xPCI Express x1, 6xPCI Express x2, 1xPCI Express x16
 USB支持:        v2.
 PCI
 BIOS版本:        V1.
 BIOS日期:        

 内存信息(总计: GBytes)

 内存条1:
 内存大小:         MB
 内存类型:        DDR3 SDRAM
 制造商:        三星
 制造日期:        2010年第39周

 内存条2:
 内存大小:         MB
 内存类型:        DDR3 SDRAM
 制造商:        三星
 制造日期:        2012年第1周

 显卡信息

 显卡芯片:        ATI (PARK PRO/XT GL)
 显存大小:         MB of DDR3 SDRAM
 显卡型号:        ATI (PARK PRO/XT GL) [宏碁]
 显卡BIOS版本:
 频率:        157.0 MHz

 存储信息

 存储器1:
 控制器:        Serial ATA 3Gb/s
 型号:        东芝 MK3265GSX
 容量:        , MB ( GB)
 转速:         RPM
 缓存:         KB
 NCQ功能:        支持,
 S.M.A.R.T.:        存在, 开启
 48bit LBA:        支持, 开启
 工作时间:        7088小时 

测试机配置信息

应该还算可以吧?有点别扭的是在32位系统上硬是要它使用(破解--映射方式)6GBytes的内存,呵呵,会不会有什么后患呢?

【数据库】_由2000W多条开房数据引发的思考、实践----给在校生的一个真实【练耙场】,同学们,来开始一次伟大的尝试吧。


之前学习使用的数据库文件多为MDF格式的,直接附加就能使用,但现在这个解压后是.bak格式的,是备份出来的,所以不能以附加的方式进行附加,

而是用【还原】的方式,如图:

【数据库】_由2000W多条开房数据引发的思考、实践----给在校生的一个真实【练耙场】,同学们,来开始一次伟大的尝试吧。

也可以尝试用命令的方式还原bak文件并建立到一个新的数据库,(仅供参考,没实践过)

/*
备份数据DB 到.bak文件。然后利用此bak文件恢复一个新的数据库DBTest。
*/
USE master
BACKUP DATABASE DB
  TO DISK = 'g:\DBBack0930.bak'
RESTORE FILELISTONLY
  FROM DISK = 'g:\DBBack0930.bak'
RESTORE DATABASE DBTest
  FROM DISK = 'g:\DBBack0930.bak'
  WITH MOVE 'DBTest' TO 'E:\Program Files\Microsoft SQL Server2005\Data\DBTest.mdf',
  MOVE 'DBTest_log' TO 'E:\Program Files\Microsoft SQL Server2005\Data\DBTest_log.ldf'
GO

OK!我们就开始还原吧,点击确定之后,我呆住了,机器也似乎呆住了,那还原的状态进度一直(2分钟内)是0,

鼠标光标变成了圆圈忙碌状态,系统瞬间变得很卡,(汗~~在校生,没见过什么大场面,被这还原进度惊呆了,各位不要见笑哦!),

又过了一分钟,终于有反应了,状态的百分比显示20%+,以步进6%左右递增,不会处于“卡死”状态了,

又过了几分钟,终于完成了。以前,课堂练习的附加,还原文件几乎都在秒级完成,几乎没有什么时间等待的概念,

这次可谓见识了什么叫海量(不过,但各位老鸟中,2000w,可能还只是沧海一粟(sù))数据。


还原成功!于是心急着马上查一查心中的她,是否榜上有名了。不加思索,就来了一条SQL语句:

select * from dbo.cdsgus where Name='女神'

噢,又一次惊呆了!在此先省略......字,

慢,别想多了,不是找到了女神的名单,而是电脑再次进入了呆住了的状态,只是查询还在在执行中,一开始,我还以为是内存太小,

CPU太差了,Ctrl+Alt+Del呼唤了任务管理器出来:

【数据库】_由2000W多条开房数据引发的思考、实践----给在校生的一个真实【练耙场】,同学们,来开始一次伟大的尝试吧。

上图告诉我,不是CPU,内存的错,也就在这时,一个闪闪的红星引起了我的注意~~

对,你猜对了!就是硬盘的读写状态提示灯,它在飞快的闪烁,平时,它只会偶尔闪闪。于是,我再次从任务管理中调出【资源监视器】来观察情况,

【数据库】_由2000W多条开房数据引发的思考、实践----给在校生的一个真实【练耙场】,同学们,来开始一次伟大的尝试吧。

“卡住”的原因找到了,跃然上图,不多说了。以前,很少会关注硬盘IO问题,毕竟经常性地Copy大文件的机会不多。而这次,我才真正关注起硬盘IO问题,在这给自己和各位对IO陌生的朋友补充一下:

读写IO(Read/Write IO)操作

磁盘是用来给我们存取数据用的,因此当说到IO操作的时候,就会存在两种相对应的操作,存数据时候对应的是写IO操作,取数据的时候对应的是是读IO操作。

单个IO操作 当控制磁盘的控制器接到操作系统的读IO操作指令的时候,控制器就会给磁盘发出一个读数据的指令,并同时将要读取的数据块的地址传递给磁盘,

然后磁盘会将读取到的数据传给控制器,并由控制器返回给操作系统,完成一个写IO的操作;

同样的,一个写IO的操作也类似, 控制器接到写的IO操作的指令和要写入的数据,并将其传递给磁盘,磁盘在数据写入完成之后将操作结果传递回控制器,

再由控制器返回给操作系统,完成一个写IO的操作。单个IO操作指的就是完成一个写IO或者是读IO的操作。

 对于经常做网络,服务器的来说,I/O不好反映到网站上就是网站页面加载慢、卡、读取数据库慢,

甚至导致网页打开超时显现。

关于IO瓶颈处理的推荐文章:
Understanding Disk I/O - when should you be worried?
http://blog.scoutapp.com/articles/2011/02/10/understanding-disk-i-o-when-should-you-be-worried

贝塔中的DBA » IO系统性能之一:衡量性能的几个指标
http://www.dbabeta.com/2009/io-performence-01_several-concepts.html

等待了200多s,终于有结果了:

【数据库】_由2000W多条开房数据引发的思考、实践----给在校生的一个真实【练耙场】,同学们,来开始一次伟大的尝试吧。

呵呵,终究没有出现女神的名字。

面对这2000w,一条select * from XXX就把我汗颜了,我在想着下一个要查谁的时候,内心一个念头把我拉住了。

不能这样折腾硬盘,伤不起.....


给在校生的练耙场---可以实践检验平时学习的理论知识

【背景】

平时课堂练习的项目,数量都是几十条为主,也不想无聊去自己建立上万条非真实数据去测试。

【实战】

查询一条数据就要等待200多s,你有这样的时间浪费吗?也就在这时,平时学习的理论知识要派上用场了-----【索引】

【四两拨千斤---索引来支招】

上面提到提到了,执行语句:  select * from dbo.cdsgus where Name='女神'  花了200多秒,

假如结果只有一条,各位猜一猜执行:

select top 1 * from dbo.cdsgus where Name='女神'  要花多长时间呢?  --留给各位实践一下。

好吧,为了快速地找到那个她,我们为字段名Name建立索引:

Create Index Index_ByName on dbo.cdsgus(Name)  --由于记录多,建立索引的时间很慢,耗时:03:49

上面注释提到建立索引就花了209秒,那它在背后究竟做了什么呢?建立索引后再次查询,为什么会让我再次惊呆呢?

这时,再去查询执行同样的查询,大家猜一下这一次所花的时间,与刚才的耗时对比,又一次让我惊呆了。

平时课堂练习的项目几乎体会不到索引对于检索速度的影响,这次可谓实践,领会了!

天下没有免费的午餐,索引能为我们的检索速度起到了四两拨千斤作用,那么,肯定也是要付出代价的。你知道有何代价么?

在课堂中,关于索引的缺点只有理论的提到几句:

第一,创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加。
第二,索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立聚簇索引,那么需要的空间就会更大。
第三,当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,这样就降低了数据的维护速度。 

很少试实践证明,即使实践了,由于是几十,百条数据的项目,感受不到。

这次可谓实践了。

针对以上缺点,

第一条,209s 耗费时间这个深有体会了。

第二条,需要占用物理空间,请看以下数据对比:

未为字段Birthday建立索引前的文件大小:
  :     ,,, shifenzheng.mdf
  :        ,, shifenzheng_1.LDF

建立索引后的文件大小:
  :     ,,, shifenzheng.mdf
  :        ,, shifenzheng_1.LDF

对于第三条,这里就不实践了,毕竟这个数据库是用来查数据的用的,从第一,二条缺点也可以推断出第三条了。

对于经常用来查询的字段,建立索引的利大于弊,复习一下:

第一,通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。
第二,可以大大加快数据的检索速度,这也是创建索引的最主要的原因。
第三,可以加速表和表之间的连接,特别是在实现数据的参考完整性方面特别有意义。
第四,在使用分组和排序 子句进行数据检索时,同样可以显著减少查询中分组和排序的时间。
第五,通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能。

有了索引后,我们查询就方便多了,大家想不想知道我们的DuDu是否入围呢? 大伙来围观:

select * from dbo.cdsgus where Name='杜勇'
--耗时 4s
--(393 row(s) affected)

【数据库】_由2000W多条开房数据引发的思考、实践----给在校生的一个真实【练耙场】,同学们,来开始一次伟大的尝试吧。

篇幅有限,也防和谐,就不在这公布具体数据,嘻嘻!大家想一想,同名的人在国内还是挺多的嘛!

由于我们在这里都是直接对数据库查询,也就不提供应用层的界面了,如Winform,WebFromt等,如果做那些,

要考虑到储存过程,高效分页查询等,所以下文继续只是用SQL语句演示。


疱丁解牛---分析表结构设计

上面从实用主义出发,走的是:下载---》还原---》查询结果,这样的路线,根本没有从底层,结构上的东西去想一想。

嗯,那现们静下心来,看一看这个表设计如何,

Column_name  Type Computed  Length    Prec    Scale   Nullable TrimTrailingBlanks    FixedLenNullInSource    Collation
Name                            yes    (n/a)    (n/a)    Chinese_PRC_CI_AS
CardNo                          yes    (n/a)    (n/a)    Chinese_PRC_CI_AS
Descriot                        yes    (n/a)    (n/a)    Chinese_PRC_CI_AS
CtfTp                          yes    (n/a)    (n/a)    Chinese_PRC_CI_AS
CtfId                          yes    (n/a)    (n/a)    Chinese_PRC_CI_AS
Gender                          yes    (n/a)    (n/a)    Chinese_PRC_CI_AS
Birthday                          yes    (n/a)    (n/a)    Chinese_PRC_CI_AS
Address                          yes    (n/a)    (n/a)    Chinese_PRC_CI_AS
Zip                          yes    (n/a)    (n/a)    Chinese_PRC_CI_AS
Dirty                          yes    (n/a)    (n/a)    Chinese_PRC_CI_AS
District1                          yes    (n/a)    (n/a)    Chinese_PRC_CI_AS
District2                          yes    (n/a)    (n/a)    Chinese_PRC_CI_AS
District3                          yes    (n/a)    (n/a)    Chinese_PRC_CI_AS
District4                          yes    (n/a)    (n/a)    Chinese_PRC_CI_AS
District5                          yes    (n/a)    (n/a)    Chinese_PRC_CI_AS
District6                          yes    (n/a)    (n/a)    Chinese_PRC_CI_AS
FirstNm                          yes    (n/a)    (n/a)    Chinese_PRC_CI_AS
LastNm                          yes    (n/a)    (n/a)    Chinese_PRC_CI_AS
Duty                          yes    (n/a)    (n/a)    Chinese_PRC_CI_AS
Mobile                          yes    (n/a)    (n/a)    Chinese_PRC_CI_AS
Tel                          yes    (n/a)    (n/a)    Chinese_PRC_CI_AS
Fax                          yes    (n/a)    (n/a)    Chinese_PRC_CI_AS
EMail                          yes    (n/a)    (n/a)    Chinese_PRC_CI_AS
Nation                          yes    (n/a)    (n/a)    Chinese_PRC_CI_AS
Taste                          yes    (n/a)    (n/a)    Chinese_PRC_CI_AS
Education                          yes    (n/a)    (n/a)    Chinese_PRC_CI_AS
Company                          yes    (n/a)    (n/a)    Chinese_PRC_CI_AS
CTel                          yes    (n/a)    (n/a)    Chinese_PRC_CI_AS
CAddress                          yes    (n/a)    (n/a)    Chinese_PRC_CI_AS
CZip                          yes    (n/a)    (n/a)    Chinese_PRC_CI_AS
Family                          yes    (n/a)    (n/a)    Chinese_PRC_CI_AS
Version                          yes    (n/a)    (n/a)    Chinese_PRC_CI_AS
id                no    (n/a)    (n/a)    NULL

可以看得出作者很懒,除了主键id,其它字段一律用 nvarchar,长度4000应付,其实看到这里的时候,我有点怀疑这些数据的真实性(是否随意生成)的。

 与 , 之间。字节的存储大小是所输入字符个数的两倍。所输入的数据字符长度可以为零

验证数据真实性(希望更多朋友提供准确的方式)

带着疑问出发,我用了这个语句来查询:

select  * from dbo.cdsgus where Address like 'XX省XXx市XX镇XX村%'   --X代表地点名称

呵呵,还真找到了几个熟悉的小伙伴,尚且当它是真的吧。又或者原数据库被人加工、整理过之后,随便建立了一个表保存起来的之后再备份出来放在网上的。

(我就纳闷了,整个备份文件还原之后仅只有一个表~)

说回正题,我们再看看:

Name    Owner     Type          Created_datetime
cdsgus  dbo       user table      

作者是夜猫子,看一看创建日期: ::21.120。

ID自动增涨列:

Identity    Seed    Increment    Not For Replication
id                     
index_name        index_description                  index_keys
Index_Birthday    nonclustered located on PRIMARY     Birthday
Index_ByAddress    nonclustered located on PRIMARY    Address
Index_ByName    nonclustered located on PRIMARY       Name
PK_cdsgus    clustered, unique, primary key located on PRIMARY    id

原本只有id默认主键索引,Name,Address,Birthday是我后来加上的。

constraint_type        constraint_name    delete_action    update_action    status_enabled
PRIMARY KEY (clustered)    PK_cdsgus    (n/a)    (n/a)    (n/a)    (n/a)    id

ok!表字段结构我们看完了,尽管这这个数据库只有一个表,表中只设置了一个id主键。但从结果显示中,

我们可以看出,其实有几个字段是作外键使用的,

如下图中的:District1--6,family,id等。

【数据库】_由2000W多条开房数据引发的思考、实践----给在校生的一个真实【练耙场】,同学们,来开始一次伟大的尝试吧。

既然了了外键,那么应该还有其它的数据表,希望哪位可以提供上来,呵呵。


天马行空---数据挖掘、分析、利用

数据有了,除了用来练习几个select 语句还能有什么用呢?

这个时候,该发挥我们的天马行空思想了,看一看,想一想里面都有什么了?

由于里面的数据有其真实性,且有了身份证号码,邮箱地址,手机号码......嘿嘿,想到了吧?不要用来做坏事哦!

  • 骗子们得到数据后,可能会用来群发敲诈短信;
  • 广告诉们可能会用来传广告;
  • 数据挖掘专家可能会用来了解各地酒店人口入住人数,年龄阶段,分布时间范围等。
  • ......

我们也来学着分析一下,(在园子里的应该都接触过sql语句,下面直接取样分析)

地区分布:(以北,上,广为例)

select  count(*) as '北京' from dbo.cdsgus where Address like '北京%'
select  count(*) as '上海' from dbo.cdsgus where Address like '上海%'
select  count(*) as '广东' from dbo.cdsgus where Address like '广东%'

【数据库】_由2000W多条开房数据引发的思考、实践----给在校生的一个真实【练耙场】,同学们,来开始一次伟大的尝试吧。

结论:留给大家思考......


年龄分布:

select  count(*) as '00后' from dbo.cdsgus where Birthday like '20%'
select  count(*) as '1990后' from dbo.cdsgus where Birthday like '199%'
select  count(*) as '1980后' from dbo.cdsgus where Birthday like '198%'
select  count(*) as '1970后' from dbo.cdsgus where Birthday like '197%'
select  count(*) as '1960后' from dbo.cdsgus where Birthday like '196%'
select  count(*) as '1950后' from dbo.cdsgus where Birthday like '195%'
select  count(*) as '1940后' from dbo.cdsgus where Birthday like '194%'
select  count(*) as '1930后' from dbo.cdsgus where Birthday like '193%'
select  count(*) as '1920后' from dbo.cdsgus where Birthday like '192%'
select  count(*) as '1910后' from dbo.cdsgus where Birthday like '191%'
select  count(*) as '1900后' from dbo.cdsgus where Birthday like '190%'
select  count(*) as '1800后' from dbo.cdsgus where Birthday like '180%'

结果:80后作为主力军,你是否在其中呢?

【数据库】_由2000W多条开房数据引发的思考、实践----给在校生的一个真实【练耙场】,同学们,来开始一次伟大的尝试吧。

类似的模糊查询,大家可以灵活变通,如查询客户是移动的用户还是联通的多(根据手机号码前N位),邮箱域名等。

更形象的建模数据挖掘,分析,推荐大家看:

对网上盛传的两千万泄漏数据的简单分析 - 深蓝 - 博客园
http://www.cnblogs.com/studyzy/p/3388887.html
在这,借用一些数据:

年龄阶段分布

【数据库】_由2000W多条开房数据引发的思考、实践----给在校生的一个真实【练耙场】,同学们,来开始一次伟大的尝试吧。

省份分布

省份 酒店排行 人口排行 上升名次
江苏 1 5 4
上海 2 24 22
北京 3 26 23
山东 4 2 -2
广东 5 1 -4
浙江 6 10 4
河南 7 3 -4
湖北 8 9 1
辽宁 9 14 5
陕西 10 16 6
河北 11 6 -5
福建 12 17 5
山西 13 18 5
安徽 14 8 -6
黑龙 15 15 0
天津 16 27 11
四川 17 4 -13
江西 18 13 -5
湖南 19 7 -12
吉林 20 21 1
内蒙 21 23 2
重庆 22 20 -2
广西 23 11 -12
甘肃 24 22 -2
贵州 25 19 -6
* 26 25 -1
云南 27 12 -15
海南 28 28 0
宁夏 29 29 0
青海 30 30 0
* 31 31 0

手机号段分布

139 1399857
138 1230530
135 782764
136 778188
137 683742
186 581451
159 456526
158 434760
133 356135
150 324798

前10大邮箱域名排名:

@qq.com 611842
@163.com 594392
@126.com 274512
@hotmail.com 203237
@sina.com 151798
@yahoo.com.cn 101692
@gmail.com 96346
@139.com 67565
@sohu.com 50179
@yahoo.cn 31274

建议

查询示例到此告一段落,现在网上也有不少人把这数据库作为数据源,可以通过Web页面进行查询了,

但我用了几个都很不理想,速度慢得可怜,有时候真怀疑他有没有建立相应的索引了,最大的局限性是:仅能通过身份证,姓名去查。

对于作为程序员的你,你接受这样的低权限吗?

所以,建议大家有空还是应该找到这文件,在本地测试,推荐给所有在校生,可以用来练习一下多数据查询,优化问题。

当然,如果哪位朋友有资源的话,建立一个Web页面,可以直接输入sql命令来查询的那就更好了。


最后来一张关于数据价值转化的图:

【数据库】_由2000W多条开房数据引发的思考、实践----给在校生的一个真实【练耙场】,同学们,来开始一次伟大的尝试吧。

我们现在得到数据库了,直接到达了加工车间这一层,至于怎样去发掘,那就要看你们的创意了。


总结

自从在学校交了210元的费用考了个所谓的的数据库工程师资格证(学校要求必须要考,但没什么技术含量),

之后比较少接触SqlServer数据库了,转眼,10月底了,再过一段时间就要准备实习了,目前还是在校生。

这次藉由这个2000w来温习了一下相关的基础知识,

磁盘IO性能问题,数据备份,恢复,索引,模糊查询,函数调用,大数据分析,挖掘,利用等。

抛砖引玉---期待亮点评论

此文,仅仅代表一个在校生初试2000w条记录的数据库的浅显实践,泛泛而谈,个中的结论,推断难免轻浮、果断。

不足之处,还望各位资深读者多多指导,斧正本文的错误论点。

上一篇:只是一个用EF写的一个简单的分页方法而已


下一篇:前端安全问题以及解决方案汇总