mysql-explained
Table of Contents
Chapter 1: Databases - In Theory and Everyday Life
What is a Database?
- database这个名字的意义其实是一系列data的良好组合,以便于需要的时候取用
A database is simply any collection of data that's organized so that it can be retrieved and used as needed.
- 每当computer需要以各种形式(比如商店的客户信息,医生的病人信息)展现数据的时候, 总是需要database的参与
Everyday Database Examples
- 现代人的生活被web page, email等占据,根据EMC的估计,截止到2011年,世界上的数据 高达1.8 zettabyte,而且还以翻倍的速度每年增长
- 这些数据很大一部分都存储在单独的文件里面,比如微软的word文档,或者是image 文件. 但是这种老旧的存储方式不利于你取用
- 除此以外,所有的数据都是存储在数据库里面的,比如:
- 电子邮件存储在数据库里
- 你经常收到垃圾邮件,说明你的电子邮件也在某些广告商那里
- 发邮件的时候,你打入首字母,就会弹出你所有以此字母开头的好友的名字,所以好友 们的名字也是存储在数据库里面的
- 你信用卡的信息存储在银行的数据库里面
Types of Databases
- 前面说了,你的address book虽然也算是一种database,但是显然不是一种"精密"的数 据库
- 数据库可以像address book那样在本地计算机以文本的形式简单的存储.也可以像银行 数据库一样,运行在高端的server里面,等待着来自世界各地的访问
Text files - Data Exchange and Basic Storage
- 最简单的数据库就是以txt形式存储的啦,虽然功能有限(比如存储和索引功能没有),但
是文本确可以在不同操作系统间兼容,并且很容易被人读取.常见的文件数据库有:
- CSV
- XML
- JSON
- CSV代表Comma Sepearated Values.这是最简单的database foramt,所有的数据使用
逗号进行分割(space或者tab其实也可以),一个CSV的例子如下
"Stormy Weather", "Carl Hiassen",1995,335 "Dune","Frank Herbert",1965,528 "The Stand","Stephen King",1978,1200
- CSV诞生于个人电脑普及之前,虽然非常的简单,但是现在还是有其存在的价值:它可以 用来在相互不兼容的程序直接传递数据:程序A的格式和程序B不兼容,但是程序A可以把 自己的数据导出成CSV格式,让程序B进行读取
- XML代表Extensible Markup Language,发明于1990年代.而且是早期互联网上数据传输 的主要格式
- XML会比CSV有进步,因为它的field都会有名字了,例子如下
<?xml version="1.0" encoding="UTF-8"?> <Books> <ID>1</ID> <Title>Stormy Weather</Title> <Author>Carl Hiassen</Author> <Year>1995</Year> <Pages>335</Pages> </Books>
- 和html不同的是,XML里面所使用的tag是没有关键字的,用户想写什么,就写什么.而不 是只有固定的<HTML>,<Body>等
- 相比于CSV,XML有了巨大的进步,XML可以有category和sub-category的概念了
- 同时又没有丢失CSV简单易读,而且明文(非binary)的优点.可以让XML在互联网时代大 显身手
- JSON是最新一代的txt format,它是JavaScript Object Notation的简称,发明于 2001年
- 和XML不同的是,JSON能够区别出简单的数据类型(而不总是string类型),比如:
- number
- string
- true/false
- JSON还能够区分单独的value和array of value.同时能够保证让复杂结构的数据能以
人类易读的方式写出来.例子如下
{ { "Title": "Stormy Weather", "Author": "Carl Hiassen", "Year": "1995", "Pages": "335" }, { "Title": "Dune", "Author": "Frank Herbert", "Year": "1965", "Pages": "528" } }
- 三种text format的格式都只适合于小规模数据的存储,因为程序想做什么事情,比如把 它们全部都读入内存,数据太大就不合适使用tex format的数据库啦
- 而且由于每个人都可以读取,安全性不太好.如果被误删除,也是没有办法恢复.所以数 据量稍微大一些的话,我们就会推荐大家使用其他类型的数据库
Mobile Databases - Smartphones, Tablets and the Web
- 大部分稍微精密一点的计算机程序(包括你的只能手机和平板)都需要比text file更 加精密的方法来存储数据.
- 因为在一个特定的时间:
- data需要满足某些'约束',拥有某些'结构'
- 不同类型数据之间的关系要'确定'
- 查找某些数据的方法应该便捷
- 应该支持random访问某一项数据,而不是把所有的数据都导入到内存
- 上面的这些要求,就足可以做成一个单独的提供数据的软件:数据库管理系统.常见的
数据库管理系统(数据库软件)有:
- SQLite: 特点是体积小,速度快.但是不支持某些高端特性,比如索引
- Microsoft SQL Server Compact and Express Edition: 微软出品,有两个版本: Compact版本主打mobile app,而Express版本为桌面和website准备
- Oracle Express Edition: 一种受限制的Oracle版本(限制存储的内容多少和内存 使用大小),但是在单机上面性能足够了
- MySQL:最著名的开源数据库软件,功能强大,代码开源,而且没有Microsoft和Oracle 等产品对于数据存储大小的限制.是最广泛引用的数据库软件
Desktop Database Software - Local Analysis
- 单机上使用的,非text file的数据库还有一种.它和Mysql, Oracle Express版本不同 的地方在于:它是为非计算机专业人士准备的!代表是微软的Access
Server Databases - Organizational Data and Enterprise Applications
- 运行在server端的数据库,才是当今数据库的主流,它们能处理最多1百万GB的数据
- 在server层面,database software就不简单的是一个program或者在一个文件了,而很 可能变成一系列的service
- 在这个领域最著名的就是Oracle, IBM和微软
Cloud Databases - Outsourced Data Storage
- 最近有爆发趋势的数据库就是Cloud Database啦
- cloud database是云计算提供商提供的多种服务的其中一种.
Chapter 2: Choosing and Installing MySQL
Why Choose MySQL?
- MySQL有全平台支持
- MySQL是开源软件
- MySQL提供了多个storage engine
Notable Feature of MySQL
- 作为一个RDBMS(relational database management system). MySQL拥有RDBMS常见的
所有特性:
- Stored Procedures: MySQL可以来存储和运行一种叫做"存储过程"的东西(存储过 程可以非常精细的处理数据),这些存储过程直接存储在server端,运行的时候就会很 有优势(不需要和remote client进行过多通信)
- Views: 视图其实就是"虚拟表",和真实的表一样,拥有一系列带有名称的列和行数 据,但是视图并不在数据库中以存储数据值集形式存在,视图可以看做是"存储在server 端的搜索定义",它们可以有效减少table的column数目.
- User Authentication and Privileges: 用户需要提供用户名,密码等信息才能连接 数据库成功.不同用户在数据库中,拥有不同的权利
- ACID-compiance: MySQL满足ACID规范:
- Atomicity: 数据要么完全成功,要么完全失败. Atomicity的存在让事务(transaction) 要么完成,要么回滚
- Consistency: 事务必须始终报纸系统处于一致的状态,不管在任何的给定时间内 的并发事务有多少
- Isolation: 隔离性的高低,决定了'事务做出的改动'在'事务完成之前'是否能被 其他用看到
- Durability: 持久性意味着一旦事务被成功执行,那么这个改动是permanent的即 便server断电
GNU General Public License and Open Source Software
- Mysql是一个遵守GNU General Public License(version 2)的软件
- GNU GPL是一个free softwar license,它允许使用者使用,分享,更改开源代码,只是 使用者所有的更改都要继续遵守GNU GPL
- GPL并没有要求软件一定"免费分发",但是实际上大部分遵守GPL的软件都是免费的
- MySQL是双licence 软件,你一来可以使用GPL,如果想付费也可以找Oracle买license
Installing MySQL and Creating an AMP Environment
- 如果你对命令行非常熟悉的话,MySQL本身并不需要多少安装的过程.如果你需要图形化
的帮助的话,你就需要AMP了.所谓AMP是一个sotware stack的总称,包括:
- Apache
sudo apt-get install apache2
- MySQL
sudo apt-get install mysql-server
- PHP(or Python)
sudo apt-get install php5 libapach2-mod-php5
- Apache
Chapter 3: Database Design - The First Steps
Creating a Model
- 数据库设计阶段"独立于"任何software的介入
- 数据库设计包含了很多的principle.理解这些principle对于学习任何数据库都至关重 要
- 第一步是将数据组织(organize)到不同的subjects,并且创建存储在数据库里面的
structure.这个过程叫做data modeling,这个过程通常都是在纸上完成的,你需要决定:
- 哪些数据需要存储
- 如何把数据分到不同的table里面
- table如何符合最终的database structure
Formal vs. Informal Data Modeling
- data modeling的formal practice通常比较繁琐包括使用语言和标记完成三个单独的步骤:
- conceptual
- logical
- physical
- 在实际的工作当中,data modeling由于documentation并不受重视的原因,很少采用formal 的流程,而一般会选择informal的流程.设计者一般都是直接从第三个步骤,也就是 physical desing直接开始的.
- 无论是否使用正式的流程, data modeling都是一个重要的步骤,因为:
- 通常情况下,初学者在设计数据库的时候都很匆忙,而没有想明白需要的data type类 型.这就会导致遇到问题的时候,会回过头来重新设计
- 业务逻辑是不断变化的,所以数据库也会不断变化.一个清晰的data model的过程会帮 助设计者为未来的改动预留好准备
- 通常来说,数据库是为一类人来设计的,再设计阶段认真分析,最好再落实到文档上会 保证设计能够"囊括"了所有人的需求
Example Database - Job Search Plus
- Job Search Plus是一个程序,旨在帮助人们管理job search
- 使用这个例子是因为它恰到好处的size,细节足够大家学习,又不至于过多而让人迷失
- 在本章后面的内容里面,我们可以看到这个例子告诉我们:真实世界的需求变动是如何影 响数据库的结构以及application的设计的
- 这些需求(requirements)在反应了业务本身的要求的情况下,会被叫做业务逻辑(business
rules).比如在我们的Job Search Plus里面下面的情景都是业务逻辑:
- 一个company里面可能有多个Job lead.注意job lead是一个新的英语词汇,你可以把 它理解为"工作机会"(lead的意思是你认为你最符合的工作)
- 一个公司里面也可能有多个contact person,但是其中一个是默认的contact person
Data Concepts and Relationships
- 创建data model的第一步是找出数据库中的concept,并且算出这些concept之间的 relation,这可以说是非常重要的一步,特别是是你对系统还不太熟悉的情况下
- 首先定义数据库里面的"核心角色(central theme)"可以帮助你更快的了解系统清晰 的结构
- 我们下面就来分析下谁能成为我们Job Search Plus的核心角色.我们的JSP的一个重要 步骤就是employment search:所以我们database的central concept就是job lead 所谓job lead,就是工作机会(这个工作机会里面会有"任职要求":一个用户需要达到怎 样的要求,雇主才会考虑他)
- 好,确定了核心概念,我们可以开始围绕着这个概念继续数据库的设计了.
- job lead的存在,意味着要存在另外一个和job lead密切相关的概念,lead source:就
是说,某个job lead肯定是有其"消息来源"的.这个消息来源可能是:
- 在线工作发布平台,比如校园BBS
- 工作中介机构
- 其他能够首先通知job lead的消息来源
- 我们再来看看job lead本身,每个lead都会有一个相关的company,并且有一个contact
person(或者多个contact person),这里又会涉及到contact management
+-------------+ Multiple contacts in each company +--------------1| Contact |8----------------------+ | +-------------+ | 8 1 +-------------+ +-------------+ +-------------+ | Resource |1---------8| Job Lead | 8--------------------------------------8 | Company | +-------------+ +-------------+ Multiple lead sper company +-------------+ Each resource might supply many leads
- 我们打印不出"无穷大",所以使用8来代替(无穷大就是平躺的8):
- job lead和resource保持着"一对多"的关系:一个消息来源可能提供多个job lead
- job lead和contact也保持着"一对多"的关系:一个contact person肯定要维护不止一个job lead
- job lead和company的关系是"多对多", 公司发布多个工作机会这个很容易理解,稍 微有点难以理解的是一个工作属于多个公司,其实就是一个工作可能直接受雇于某个 公司,或者使用了外包公司
- 从contact object 角度理解,一个contact object肯定是要负责多个job lead,而 一个company肯定也会雇佣多个contact people的
- 再往前走一步,每个job lead一旦发布,就存在着被job seeker关注的可能.一旦一个
job seeker决定关注这个job lead,那他就不可避免的需要记录关于这个job lead的
很多activity,比如申请时间,面试时间等等.我希望用户可以为这些activity增加
reminder,所以我又增加了一个新的concept: Activities
+-------------+ Multiple contacts in each company +--------------1| Contact |8----------------------+ | +-------------+ | 8 1 +-------------+ +-------------+ +-------------+ | Resource |1---------8| Job Lead | 8--------------------------------------8 | Company | +-------------+ +-------------+ Multiple lead sper company +-------------+ 1 | | | +-------------+ +--------------8| Activities | +-------------+
Filling in the Details
- data modeling的下一个步骤,是增加一些细节来描述哪些数据需要存储
- 我们还是以Job Search Plus为例,如果你要记录一个job lead,那么你很大可能希望
了解job lead如下的信息:
- Date Received/Recorded
- Job Title
- Description
- Full or Part Time
- Job Location
- Company Name
- Contact Name
- Phone Number
- 每一个这样的信息都是job lead这个entity的attribute
- 然后我们再来看看Activity entity的attribute:
- Activity Date
- Activity Type(Sent Resume, Scheduled Interview, Follow-up call)
- Detail / Comments
- Complete (or Not)
- 加入这些attribute之后的图标如下
+-----------+ +-------------+ |Job Lead | |Activities | |---------- | |---------- | |Date | |Date | |Job Title | |Job Lead ID | |Description|1-----------------8|Activity Type| |FT/PT | |Detail | |Location | |Completed | |Company | +-------------+ |Contact | |Phone | |Source | +-----------+
- 细心的人可能会发现,在Activities里面有一个attribute叫做Job Lead ID, 用来refer back到activity相关的Job Lead entity!这是relation database特有的"相互联系" 的方式!
- 为了data model的完整,我们还需要加入company, contact, resource entities.
+----------+ |Contact | +----------+ +---------------1|Name | | |Company ID| 8 |Title | +-----------+ |Address | |Job Lead | |City State| +-----------+ |Postal Cod| +-----------+ |Date | |Phone/Fax | |Resource | |Job Title | |Email | +-----------+ |Description| |Notes | |Name | |FT/PT | |Archive | |Type |1--------8|Location | +----+-----+ |Link | |Company ID | 8 |Description| |Contact ID |8----+ | +-----------+ |Phone | | | |Scource ID | | | +-----------+ | | 1 | | | | 1 | | +-----------+ | | |Company | | | +-----------+ 8 +---8|Name | +-------------+ |Address | |Activities | |City, State| +-------------+ |Postal Code| |Date | |Phone/Fax | |Job Lead ID | |Email | |Activity Type| |Website | |Detail | +-----------+ |Completed | +-------------+
- 下一步就是把这个简单的图标转换成EER diagram(Enhanced Entity-Releationship diagram),EER的优点是其有更加detail的类型信息,比如INT(a numeric integer)还是 VARCHAR(for text and other alphanumeric data)
Defining the Tables
- data modeling的最后一个阶段是转换成physical data model(也叫schema),具体讲
起来就是从当前的data model里面把数据写入到table和table的field里面(使用SQL):
- 每个entity变成一个table
- 每个attribute都变成了一个field
- 在这最后一个过程中,会加入更多的设计细节,比如那些field会加上索引以期提高搜索的速度
Chapter 4: Database Normalization
From Design to Reality
- 本章会介绍data modeling的最后一个步骤:形成physical database
- 我们这里的数据库都是关系型数据库,主要的成员是table和field
- 这些table和其他table会在database management software的帮助下,联系在一起.
- 这种联系的方式(也就是关系型数据库)最早是1970年被Edgar F. Codd发明的. Codd博 士创造了一系列的rules,在这些rules的管理下,存储数据有了很多优点:data的存储可 以防止duplication,从而保证正确性的同时,还能简化维护
- 这些rules以"范式"的形式存在,从更高的理论高度,指引我们的数据存储
Looking Back
- 我们前面已经把Job Search Plus简化到了一张易懂的表上,如下
+----------+ |Contact | +----------+ +---------------1|Name | | |Company ID| 8 |Title | +-----------+ |Address | |Job Lead | |City State| +-----------+ |Postal Cod| +-----------+ |Date | |Phone/Fax | |Resource | |Job Title | |Email | +-----------+ |Description| |Notes | |Name | |FT/PT | |Archive | |Type |1--------8|Location | +----+-----+ |Link | |Company ID | 8 |Description| |Contact ID |8----+ | +-----------+ |Phone | | | |Scource ID | | | +-----------+ | | 1 | | | | 1 | | +-----------+ | | |Company | | | +-----------+ 8 +---8|Name | +-------------+ |Address | |Activities | |City, State| +-------------+ |Postal Code| |Date | |Phone/Fax | |Job Lead ID | |Email | |Activity Type| |Website | |Detail | +-----------+ |Completed | +-------------+
The Normalization Process
- 理论界存在很多数据库的范式,但是我们只需要了解其中最重要的一些就可以了.如果 能完美的实现这些范式,那么数据库就会非常的stable而且well-organized,但是由于 客观业务需求的不同,我们可能不会完整的follow 这些范式,只能选择性的实现其中一 部分
- 最重要的范式有:
- 第一范式(1NF)
- 第二范式(2NF)
- 第三范式(3NF)
- 等等
- 第N+1范式满足的前提是第N范式已经得到满足
First Normal Form(1NF)
- 第一范式有如下的要求:
- 一个table里面所有的field必须是最小的信息片段(smallest useful piece of information), 最小也就意味着信息不可再分.这个要求可以简化为atomicity.一个 field不能拥有两个value.这single的value常被称作scalar value
- table不能有重复的field.两个filed名字不一样,内容重复也不行
- Atomicity把数据减小到尽可能小(只要还有实际的意义),但是根据具体的业务逻辑的 不同,我们对于"有实际意义"的定义却不同
- 在Job Search Plus里面,Company和Contact table里面都有地址这一项,我完全可以 把地址存成一个字符串,比如'123 Lark Drive, Ocala, FL 34470'
- 但是当我们需要把公司按照city, state,或者ZIP code排序的时候,就显得不太适用了.
这个时候为了能够可以排序(数字数据库对于某个域的排序非常的快),我们要把地址拆
成多个field
Address City State Zip ----------------- ----- ----- ------- 123 Lark Drive Ocala FL 34470
- 把数据分成较小的part的话,显而易见的优点就有:
- 按照不同City或者ZIP分钟,或者排序的话,速度更快
- 能够显著的减少数据冗余
- 既然分成较小的part很好,我们为什不把上面的Address也分成多个field?如果这样做 的话,就是一个典型的over-normalization.判断是否over-normalization的原则很简 单:看看这么做是否"值得".这里显然不值得,因为我们很少安装地址排序或者分组
- 1NF的另外一个重要特点是不能有重复的field或是groups of field.其中这个group of field是新手容易忽视的地方:考虑下和activity的one to many关系,如果我们允许group of field的话,我们Job Lead可能会有这样的一个multiple field: 'Activity1', 'Activity2', 'Activity3'.换句话说就是"一个field里面存储多个entity!"
- 这种做法有很多的缺点:
- 它限制了能够存储的activities的数目,无论你的field是什么格式,比如text,其长 度总是有限的,不能无限制的存储activity
- 在one一端,把many一端的详细信息都列出来会非常的麻烦:你还要在循环里面处理每 个activity,然后把结果汇总起来
- 我们relation database的解决方案就是one to many数据库:把数据分成两个表存储,
一个job lead table,一个activity table.这两个数据库会被联系起来以达到如下目
的:
- 每个activity table里面的一个record会和某个特定的job lead联系起来
- 每个job lead会和一个或多个activity联系起来
- 这种one to many的设计会让"获取"如下信息的操作非常简便(company和contact也是
one-to-many的关系,company是one)
- 某个lead对应的所有的activities
- 某个job lead在某一段date range里面的特定activity
- 某个company所有的contact
- 某个contact所有的company按照字母顺序排序
- 在欧美的数据库设计中,通常会把地址设计成两行,比如
+-----------+ |Company | +-----------+ |Name | |Address1 | |Address2 | |City | |State | |Postal Code| |Phone | +-----------+
- 这种设计看起来是明显的违反了"不能repeating field"的规则,但是问题的根源在于 前面说到的exception.在欧美的地址设计中,总是会设计成两行,而且保证不会有第三 行.在面对范式和real business logic的情况下,要服从real business logic
Second Normal Form(2NF)
- 第二范式首先要求要满足第一范式,然后再满足如下要求:
- 所有不是table key的一部分的field,也 必须depend on 整个key(entire key)
All fields that are not part of a table's key must be functionally dependent on the entire key.
- 所有不是table key的一部分的field,也 必须depend on 整个key(entire key)
- 在relational database的table里面,每个record都要和其他record相互区别开来,使 用的办法就是每个record的"某个field,或者几个field联合起来的值,唯一"
- 这些field(field or combination of fields that serves to uniquely identify each record in the table)叫做table的primary key
- primary key是一个特殊的index field,所谓index field是指能够帮助数据库查找和
排序的field
Index fields assist the database in searching or sorting information on that field and the primary key does this while also uniquely identifying each record
- 某个table还可以存储其他table的primary key,从而uniquely的reference那个table 里面的record.这种field被称之为foreign key. 前面的例子中,Contact entity都会 有一个field 叫做"CompanyID",这就是一个外键(foreign key),对应Company table 的主键(primary key)
- 前面说过primary key是一个index field,但是其实某个table可以在primary key之 外再加上另外的的field作为index field.但是并不是所有的key都可以作为index key: 只有全局唯一的field(primary key必然唯一)才能作为index key,常见的例子有驾驶 员的驾驶执照号,或者是产品的序列号.
- 这里还要注意的是,虽然有很多field是全局唯一的,但是primary key只有一个.虽然 primary key可能是一个或者多个field的combination(所以不能叫做primary field), 但是它确实是只有一个!
- 前面说到index要求全局唯一,除了primary key以外的其他field很难做到全局唯一(因
为只要有一对相同就不行),这个时候,我们可以要求index field是"一系列field的
combination",然后保证这个combination唯一就可以了.这个和primary key的定义是
一样的
因为全局唯一性很难,所以我们允许多个field 组合起来,视同为一个field 来作为index field或者是primary key
- 这种cobinate多个field的方法叫做composite key, 可以用于index field或者primary key,实际上绝少用于primary key,因为:key里面的多个field必须在其他table里面的 duplicated,如果他们想reference你的主键作为外键的话!
- 有些情况下,table里面会有一个field的数据是'天然unique的',比如在一个小的图书 馆系统里面,ISBN field就肯定是unique的,所以也是可以作为primary key的,但是这 种选择经不起时间的考验,一旦图书馆变大,一本书可能有两个copy的话,ISBN就不再 unique了.这种情况下,我们可以引入另外一个field copy(来表示这是某个特定book 的第几本),那么copy和ISBN两个域组合起来就肯定是unique的了.也就可以作为primary key或者index field了.这也就是典型的一个composite key的例子
- 虽然"绝对unique的field"肯定可以作为primary field,但是只是有潜力而已,并不是 一定可以拿来用,比如:在我们的例子中有employee表,里面的每个employee都肯定有 身份证号,这个号码可以保证是唯一的,但是却不是一个好的primary key的选择:因为 这是"隐私信息",而primary key需要很精彩的到处传递,使用隐私信息作为主键显然 不合理.
- 虽然身份证号不适合做primary key,但是却非常适合做index field
- 好了我们现在总结一下,对于primary key:
- 很多情况下很难找到某一个field绝对的unique,需要配合至少另外一个field组成 unique的composite key.但是composite key作为主键的话,其他table来引用主键 的时候,要同时引用多于一个field.这非常的麻烦!
- 好不容易找到某个field是unique,但是又面临这个field有可能会改变,或者虽然永 远不改变,但是是隐私信息,没法暴露给其他table或者外界
- 最终的解决方案是surrogate key(代理键),也就是数据库生成的在field内绝对unique
的值(最常见的保证unique的方法是auto increment,或者是创建GUID字符串,但是不
常见)
The database management software can generate an automatically incrementing number or unique string of characters as each record is created
- surrogate key的产生(通常是在数据创建的时候产生surrogate key)是数据库管理软 件内部的细节,如何产生用户是无法知晓的.
- 多说一句surrogate key大多数情况下和primary key是一回事.为什么说是大多数情况 下?因为少数情况下,数据库是以temporal database的状态存在的.这种情况下surrogate key需要另外的域来对应primary key.
- 其实在绝大多数的情况下,数据库都是使用surrogate key作为主键的,我们这里的例 子也不例外.某个table引用其他table的时候,也只需要引用一个field就可以了.这样 外键就控制在一个field了,减小了空间存储
- 而且,使用主键外键的最大好处就是绝大部分数据只用存在一个地方就可以了,需要的 时候使用外键引用一下.
- 鉴于使用代理键来作为primary key其实已经是事实上的标准,我可以从另外一个角度
来理解下Second Normal Form: 所有不是table主键的域,必须直接的描述table的主
体,并且这个域的值,必须直接的依赖于这个记录,即便这个值不一定是unique的
Every field outside the table's primary key must directly describe the subject of that table and the value of the field must directly depend on which item is being represented by the record, even if it's not unique for each record.
- 我们来举个例子:
- 对于一个job lead来说,发布这个job lead的company的email和这个job lead没有 直接的关系,所以不应该把某个公司的email一遍又一遍的存储在job lead的表中.
- 对于一个activity来说,某个job lead的job desscription并不是和自己直接相关, 所以我们没必要一遍一遍的存储在activity里面,而且应该存储在job lead表里面, 然后通过一个外键LeadID来引用
- 我们来比较下和原定义的不同: 在第二范式创建的时候,还没有代理键的概念,所以有
些field就天然是做primary key的(没有专职的和数据没关系的代理键的存在)
All fields that are not part of a table's key must be functionally dependent on the entire key.
Third Normal Form (3NF)
- 第三范式是在满足第一第二范式的情况下,还要满足
- 所有不是key一部分的field,必须相互独立
All fields that ar not part of the key must be functionally independent of each other
- 所有不是key一部分的field,必须相互独立
- 这一点说起来拗口,但是其实是一个非常实际是要求,满足第三范式就意味着:更改一个 non-key的field不会要求另外一个non-key的field进行改变!
- 做过excel的同学会有这样的体验,你有几个field存放着某些报销的金额,最后还有一 个总的field,帮你记着你的总额!这样每当你输入一个金额的时候,总额就会跟着改变
- 如果你理解第三范式,就会差距到这种"calculated field"的方式,就是一种典型的 anti-pattern
- 我们对比下第二范式和第三范式,发现这两个范式说的是一枚硬币的两面:
- 第二范式要求所有不是key的filed必须depend on key
- 第三范式要求所有不同key的field必须相互之间independent
- 有人总结了第二和第三范式,得到了如下如下警示
Every non-key field must provide a fact about the key, the whole key, and nothing but the key.
- 我们来看一个具体的anti-3nf的例子:含有calculated field的table
OrderLineID OrderID ProductID ProductName Price Quanlity Total 003 4324 A8791 3x3 table $285.00 2 $570.00 - 这个例子其实两处违法了3NF:
- 我们不需要既存储ProductID,又存储ProductName.这两者必然是dependent的,一旦 ProductID改动的话,ProductName必然改动.反过来说,ProductName依赖ProductID 那么正好创建一个Product table,安装2NF的要求,ProductName肯定是这个表里面 的主力成员:因为它紧密的依赖于这个表的primary key
- Total field肯定是不行的,因为它依赖于另外两个non-key field: Price, Quantity 一旦我们更改了其中一个filed,total field会需要跟着进行更改.注意这个更改要 程序的开发者来进行维护,数据库是不会帮我们维护的!这看起来好像没什么大不了 的,但是随着时间的流逝,"更改fieldA同时还要更改fieldB"这件事情很容易忘记,最 后就会出现大家忘记了这件事情,数据出现了不一致
- 另外的容易'犯错'情况是inventory,inventory会列出商家有的所有产品.这个table
如果存在的话,可以含有product name, package type, model number等等,但是注意
我们不要含有如下的信息:
- 当前商品的库存
- 当前商品最后一次售出的时间
- 这里的问题还是"这些信息不够dependent on key".这些信息即便放在上面的表里面 也不会出现特别严重的问题,比如产品名字更改了,但是我们的库存并不会更改(这个跟 calculated field不一样,那个是压根不行.这个是不建议).
- 但是这些信息确实和key的关系不大(这个商品id可以在不同的商场卖出,都是一个商品, 但是其库存每个商场不一样,库存不能说是商品相关信息).同时,库存的频率过高,我们 另外起一个表来存储比较好.
- 我们的例子符合3NF(也就同时符合1NF,2NF),所以我们不需要进行什么改动
Boyce-Codd Normal Form (BCNF or 3.5NF)
- 绝大部分情况下3NF已经是我们所需要的最高标准了.
Most of the time, the achievement of 3NF will have resolved any potential issues in the tables.
- BCNF(3.5NF)的主要目的,在于:
- 去寻找是否有field可以拆成自己的单独的一个table,从而减少数据的重复
- 某几个field展现出了共同点是否可以合并成一个filed
Data or Database?
- 前面我们总结了data model的过程,它主要是展现了特定的数据是如何组织的
Data model describes how a specific collection of data is organized
- database model则有些许不同,其描述的是某款特定的database software使用何种具 体技术来组织并存储数据
- 我们这里介绍的database model肯定是mysql使用relational model来组织数据啦!
Chapter 5: Servers and Databases
Building the Database
- 前面你学习了如何model data,并且把某些数据存放在哪些表里面,然后这些表通过一些 "外键"联系起来.
- 这些都是虚拟的model,啊我没现在来创建physical data model(也叫作shema)
- physical data model(shema)是把data描述给DBMS(这里是mysql)的一个过程
Accessing the Server
- 虽然MySQL是支持server和local都在本地的,但那只不过是为了方便使用.MySQL设计 的时候就是安装CS结构设计的.也就是Server和Client是在不同的host.所以很多时候 你要确认remote的server是不是在running,还有知道如何远程连接上server
- MySQL可以看做是一类软件的合称,具体来说有:
- mysql-server:常驻内存的应用damon形式运行,一般在remote.
- mysql-client:包括:
- bash里面的mysql命令:用来在命令行连接mysql-server
> mysql -h localhost -u root -p password: (enter password here) Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 52 Server version: 5.5.43-0ubuntu0.14.04.1 (Ubuntu)
- bash里面的mysqladmin命令:主要用来检查server的运行情况
mysqladmin -uroot status Uptime: 82418 Threads: 1 Questions: 3 Slow queries: 0 Opens: 105 Flush tables: 1 Open tables: 98 Queries per second avg: 0.000
- 还有其他第三方的GUI访问软件,比如MySQL Workbench
- bash里面的mysql命令:用来在命令行连接mysql-server
The MySQL Command Prompt
- 默认的client就是mysql啦,登录sever以后,它会显示出一个不太友好的prompt
Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 5 Server version: 5.7.17 Homebrew Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. mysql>
- 这个prompt主要是让你使用SQL语言来和mysql-server进行通信的,一个例子如下
mysql> SELECT CURRENT_DATE() as "Current Date"; SELECT CURRENT_DATE() as "Current Date"; +--------------+ | Current Date | +--------------+ | 2017-03-20 | +--------------+ 1 row in set (0.01 sec) mysql> quit quit Bye
- 这里我们使用了MySQL里面内置的一个函数CURRENT_DATE,在Mysql里面有很多这样和 date或者text manipulation打交道的函数.他们可以直接写到SQL里面
- 我们这里使用了SELECT,这个是SQL里面我们最常用的命令了.SELECT的作用,是让sever 返回特定的as set of results.
- 还要注意,SQL是以"分号"结尾的,这个分号必须有,让程序直接我们这个查询结束了.
- 我们再来看一个例子,让CURRENT_DATE有个更加友好的名字
mysql> SELECT CURRENT_DATE() AS 'Today\'s Date'; SELECT CURRENT_DATE() AS 'Today\'s Date'; +--------------+ | Today's Date | +--------------+ | 2017-03-20 | +--------------+ 1 row in set (0.00 sec)
- 这个例子演示了AS的用法,AS可以作为column的别名(alias)
- alias里面因为有单引号,所以使用了"\"(backslash)来作为Escape character
- Mysql除了支持单引号,还支持双引号和重音符(``),有了另外两种字符串形式,其实Escape
character并不太重要.但是其他数据库可能不支持这么多字符串形式
mysql> SELECT CURRENT_DATE() AS "Today's Date"; SELECT CURRENT_DATE() AS "Today's Date"; +--------------+ | Today's Date | +--------------+ | 2017-03-20 | +--------------+ 1 row in set (0.00 sec) mysql> SELECT CURRENT_DATE() AS `Today's Date`; SELECT CURRENT_DATE() AS `Today's Date`; +--------------+ | Today's Date | +--------------+ | 2017-03-20 | +--------------+ 1 row in set (0.00 sec)
- 很有意思的是,如果你换行的时候,是在某种字符串之间,那么下一个prompt会提示你
需要某种字符串结尾,比如下面的>左边有个重音符.结果出现了排版问题是自然的,一
因为在Date前面有个回车符
SELECT CURRENT_DATE() AS `Today's `> Date`; +---------------+ | Today's Date | +---------------+ | 2017-03-20 | +---------------+ 1 row in set (0.00 sec)
- 当然你也可以一行多个"分号"来运行多个语句
mysql> SELECT NOW() AS "The current date and time is - "; SELECT CONCAT(USER(), ' is currently logged in.') as "Current User"; +---------------------------------+ | The current date and time is - | +---------------------------------+ | 2017-03-20 17:14:57 | +---------------------------------+ 1 row in set (0.00 sec) +----------------------------------------+ | Current User | +----------------------------------------+ | root@localhost is currently logged in. | +----------------------------------------+ 1 row in set (0.00 sec)
- 上面的例子用了两个新的函数,第一个就是CONCAT(),这是个连接字符串的函数,属于 高频操作了.数据库的1NF让数据库的field已经达到了atomic的极致,所以一段说明性 的文字很容易是多个field的concat!
- 第二个是USER()函数,会显示当前登录的用户
- 上面主要讲了Mysql的字符串函数,同时mysql还有很多处理日期的函数:
- CURRENT_DATE():返回当前日期
- ADDDATE():增加n天到某个日期
- 返回两个日期之间的差距
- 日期函数例子如下
mysql> SELECT CURRENT_DATE() AS "Today's Date", ADDDATE(CURRENT_DATE(), 30) AS "30 Days", ADDDATE(CURRENT_DATE(), 60) AS "60 Days"; SELECT CURRENT_DATE() AS "Today's Date", ADDDATE(CURRENT_DATE(), 30) AS "30 Days", ADDDATE(CURRENT_DATE(), 60) AS "60 Days"; +--------------+------------+------------+ | Today's Date | 30 Days | 60 Days | +--------------+------------+------------+ | 2017-03-20 | 2017-04-19 | 2017-05-19 | +--------------+------------+------------+ 1 row in set (0.00 sec) mysql> SELECT CURRENT_DATE() AS "Today's Date", DATEDIFF('2017-08-30', CURRENT_DATE()) AS "Days Remaining"; SELECT CURRENT_DATE() AS "Today's Date", DATEDIFF('2017-08-30', CURRENT_DATE()) AS "Days Remaining"; +--------------+----------------+ | Today's Date | Days Remaining | +--------------+----------------+ | 2017-03-20 | 163 | +--------------+----------------+ 1 row in set (0.00 sec)
- MySQL也支持四则运算
mysql> SELECT 25 + 3 / 4 * (3 - 1) + (( 10 + 15 + 20) / 3) as Result; SELECT 25 + 3 / 4 * (3 - 1) + (( 10 + 15 + 20) / 3) as Result; +---------+ | Result | +---------+ | 41.5000 | +---------+ 1 row in set (0.00 sec)
Creating Databases
- 首先是来查看已经存在的数据库
mysql> SHOW DATABASES; SHOW DATABASES; +--------------------+ | Database | +--------------------+ | information_schema | | mysql | | performance_schema | | sys | +--------------------+ 4 rows in set (0.00 sec)
- 如果想查看某个database的内容,需要首先use这个数据库.我们以information_schema
为例,这个数据库存储了这个server上所有数据库的information,所以table有点多(使
用show tables来查看所有tables)
mysql> USE information_schema; USE information_schema; Reading table information for completion of table and column names You can turn off this feature to get a quicker startup with -A Database changed mysql> SHOW TABLES; SHOW TABLES; +---------------------------------------+ | Tables_in_information_schema | +---------------------------------------+ | CHARACTER_SETS | | COLLATIONS | | .... |
- 使用USE以后,就可以直接查询这个数据库里面的某个表了,当然有个更精确的方法是把
数据库名和表名写在一起,使用"."分割
mysql> SELECT COUNT(*) FROM COLUMNS; SELECT COUNT(*) FROM COLUMNS; +----------+ | COUNT(*) | +----------+ | 3104 | +----------+ 1 row in set (0.04 sec) mysql> SELECT COUNT(*) FROM information_schema.COLUMNS; SELECT COUNT(*) FROM information_schema.COLUMNS; +----------+ | COUNT(*) | +----------+ | 3104 | +----------+ 1 row in set (0.03 sec)
- 定位了表以后,下面我们是来看某个表下面有哪些列(column)了,方法类似.但是我们
需要使用IN或者FROM来确定table
mysql> SHOW COLUMNS IN CHARACTER_SETS; SHOW COLUMNS IN CHARACTER_SETS; +----------------------+-------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +----------------------+-------------+------+-----+---------+-------+ | CHARACTER_SET_NAME | varchar(32) | NO | | | | | DEFAULT_COLLATE_NAME | varchar(32) | NO | | | | | DESCRIPTION | varchar(60) | NO | | | | | MAXLEN | bigint(3) | NO | | 0 | | +----------------------+-------------+------+-----+---------+-------+ 4 rows in set (0.00 sec)
- 这种显示方式有时候会导致宽带过长,我们可以使用\G来替代分号,从而让每个field
的结果单独占一行
mysql> SHOW COLUMNS IN CHARACTER_SETS\G SHOW COLUMNS IN CHARACTER_SETS\G *************************** 1. row *************************** Field: CHARACTER_SET_NAME Type: varchar(32) Null: NO Key: Default: Extra: *************************** 2. row *************************** Field: DEFAULT_COLLATE_NAME Type: varchar(32) Null: NO Key: Default: Extra: *************************** 3. row *************************** Field: DESCRIPTION Type: varchar(60) Null: NO Key: Default: Extra: *************************** 4. row *************************** Field: MAXLEN Type: bigint(3) Null: NO Key: Default: 0 Extra: 4 rows in set (0.00 sec)
Creating the Database
- 创建数据库的命令非常简单
mysql> CREATE DATABASE JobSearchPlus; CREATE DATABASE JobSearchPlus; Query OK, 1 row affected (0.00 sec) mysql> SHOW DATABASES; SHOW DATABASES; +--------------------+ | Database | +--------------------+ | information_schema | | JobSearchPlus | | mysql | | performance_schema | | sys | | toy_development | | toy_test | | vwops_development | | vwops_test | +--------------------+ 9 rows in set (0.00 sec)
- 大多数情况下,我们是运行脚本,而不是手动来创建数据库的,所以为了确保我们创建
的数据库之前没有重名的数据库,我们可以使用如下的代码来判断
mysql> CREATE DATABASE IF NOT EXISTS JobSearchPlus; CREATE DATABASE IF NOT EXISTS JobSearchPlus; Query OK, 1 row affected, 1 warning (0.00 sec)
- 创建数据库的时候,在原来,需要认真考虑的一个数据库的特性是数据库的character
set(以及内部的collation).前面我们创建了数据库,但是没指定,在没指定的情况下,
会有默认值.我们先来看看如何读取默认值
mysql> SELECT * FROM information_schema.SCHEMATA WHERE schema_name = "JobSearchPlus"; SELECT * FROM information_schema.SCHEMATA WHERE schema_name = "JobSearchPlus"; +--------------+---------------+----------------------------+------------------------+----------+ | CATALOG_NAME | SCHEMA_NAME | DEFAULT_CHARACTER_SET_NAME | DEFAULT_COLLATION_NAME | SQL_PATH | +--------------+---------------+----------------------------+------------------------+----------+ | def | jobsearchplus | utf8 | utf8_general_ci | NULL | +--------------+---------------+----------------------------+------------------------+----------+ 1 row in set (0.00 sec)
- 所谓character set是指数据库能够存储的字符串的种类,所有的character set如下.
注意Maxlen,这说明某个字符串最多使用几个bytes来存储.
mysql> SHOW CHARACTER SET; SHOW CHARACTER SET; +----------+---------------------------------+---------------------+--------+ | Charset | Description | Default collation | Maxlen | +----------+---------------------------------+---------------------+--------+ | big5 | Big5 Traditional Chinese | big5_chinese_ci | 2 | | dec8 | DEC West European | dec8_swedish_ci | 1 | | cp850 | DOS West European | cp850_general_ci | 1 | | hp8 | HP West European | hp8_english_ci | 1 | | koi8r | KOI8-R Relcom Russian | koi8r_general_ci | 1 | | latin1 | cp1252 West European | latin1_swedish_ci | 1 | | latin2 | ISO 8859-2 Central European | latin2_general_ci | 1 | | swe7 | 7bit Swedish | swe7_swedish_ci | 1 | | ascii | US ASCII | ascii_general_ci | 1 | | ujis | EUC-JP Japanese | ujis_japanese_ci | 3 | | sjis | Shift-JIS Japanese | sjis_japanese_ci | 2 | | hebrew | ISO 8859-8 Hebrew | hebrew_general_ci | 1 | | tis620 | TIS620 Thai | tis620_thai_ci | 1 | | euckr | EUC-KR Korean | euckr_korean_ci | 2 | | koi8u | KOI8-U Ukrainian | koi8u_general_ci | 1 | | gb2312 | GB2312 Simplified Chinese | gb2312_chinese_ci | 2 | | greek | ISO 8859-7 Greek | greek_general_ci | 1 | | cp1250 | Windows Central European | cp1250_general_ci | 1 | | gbk | GBK Simplified Chinese | gbk_chinese_ci | 2 | | latin5 | ISO 8859-9 Turkish | latin5_turkish_ci | 1 | | armscii8 | ARMSCII-8 Armenian | armscii8_general_ci | 1 | | utf8 | UTF-8 Unicode | utf8_general_ci | 3 | | ucs2 | UCS-2 Unicode | ucs2_general_ci | 2 | | cp866 | DOS Russian | cp866_general_ci | 1 | | keybcs2 | DOS Kamenicky Czech-Slovak | keybcs2_general_ci | 1 | | macce | Mac Central European | macce_general_ci | 1 | | macroman | Mac West European | macroman_general_ci | 1 | | cp852 | DOS Central European | cp852_general_ci | 1 | | latin7 | ISO 8859-13 Baltic | latin7_general_ci | 1 | | utf8mb4 | UTF-8 Unicode | utf8mb4_general_ci | 4 | | cp1251 | Windows Cyrillic | cp1251_general_ci | 1 | | utf16 | UTF-16 Unicode | utf16_general_ci | 4 | | utf16le | UTF-16LE Unicode | utf16le_general_ci | 4 | | cp1256 | Windows Arabic | cp1256_general_ci | 1 | | cp1257 | Windows Baltic | cp1257_general_ci | 1 | | utf32 | UTF-32 Unicode | utf32_general_ci | 4 | | binary | Binary pseudo charset | binary | 1 | | geostd8 | GEOSTD8 Georgian | geostd8_general_ci | 1 | | cp932 | SJIS for Windows Japanese | cp932_japanese_ci | 2 | | eucjpms | UJIS for Windows Japanese | eucjpms_japanese_ci | 3 | | gb18030 | China National Standard GB18030 | gb18030_chinese_ci | 4 | +----------+---------------------------------+---------------------+--------+ 41 rows in set (0.00 sec)
- 我们知道Unicode最多的情况下会使用4个bytes来存储的(虽然使用4个的情况不多),
但是我们会看到我们默认的utf8 character set的MaxLen竟然只有3!
+----------+---------------------------------+--------+ | Charset | Description | Maxlen | +----------+---------------------------------+--------+ | utf8 | UTF-8 Unicode | 3 | +----------+---------------------------------+--------+
- 这是早期mysql的不佳实践,为了减少存储而使用3个bytes来存储utf8.对于使用4个
bytes的字符串干脆就不支持.所以,新的数据库我们应该默认使用utf8mb4!
+----------+---------------------------------+--------+ | Charset | Description | Maxlen | +----------+---------------------------------+--------+ | utf8mb4 | UTF-8 Unicode | 4 | +----------+---------------------------------+--------+
- 再来看看另外一个概念collations:所谓collation是指在character set确认的情况 下,如何来比较(比较了也就能排序)两个字符.
- 说起来collation这个概念有些搞笑的意思,因为两个字符如何比较,那不是有规范来
定义么.按照规范来不就可以了.但是真正的情况是,mysql一开始真的不是安装规范来
的!为的,也只是一点cpu速度的提升,比如utf8默认的collation
utf8mb4_general_ci就真的没有满足规范,所以在比较法语德语的时候可能会出现问题!
mysql> SHOW COLLATION WHERE Charset = 'utf8mb4'; SHOW COLLATION WHERE Charset = 'utf8mb4'; +------------------------+---------+-----+---------+----------+---------+ | Collation | Charset | Id | Default | Compiled | Sortlen | +------------------------+---------+-----+---------+----------+---------+ | utf8mb4_general_ci | utf8mb4 | 45 | Yes | Yes | 1 | | utf8mb4_bin | utf8mb4 | 46 | | Yes | 1 | | utf8mb4_unicode_ci | utf8mb4 | 224 | | Yes | 8 | | utf8mb4_icelandic_ci | utf8mb4 | 225 | | Yes | 8 | | utf8mb4_latvian_ci | utf8mb4 | 226 | | Yes | 8 | | utf8mb4_romanian_ci | utf8mb4 | 227 | | Yes | 8 | | utf8mb4_slovenian_ci | utf8mb4 | 228 | | Yes | 8 | | utf8mb4_polish_ci | utf8mb4 | 229 | | Yes | 8 | | utf8mb4_estonian_ci | utf8mb4 | 230 | | Yes | 8 | | utf8mb4_spanish_ci | utf8mb4 | 231 | | Yes | 8 | | utf8mb4_swedish_ci | utf8mb4 | 232 | | Yes | 8 | | utf8mb4_turkish_ci | utf8mb4 | 233 | | Yes | 8 | | utf8mb4_czech_ci | utf8mb4 | 234 | | Yes | 8 | | utf8mb4_danish_ci | utf8mb4 | 235 | | Yes | 8 | | utf8mb4_lithuanian_ci | utf8mb4 | 236 | | Yes | 8 | | utf8mb4_slovak_ci | utf8mb4 | 237 | | Yes | 8 | | utf8mb4_spanish2_ci | utf8mb4 | 238 | | Yes | 8 | | utf8mb4_roman_ci | utf8mb4 | 239 | | Yes | 8 | | utf8mb4_persian_ci | utf8mb4 | 240 | | Yes | 8 | | utf8mb4_esperanto_ci | utf8mb4 | 241 | | Yes | 8 | | utf8mb4_hungarian_ci | utf8mb4 | 242 | | Yes | 8 | | utf8mb4_sinhala_ci | utf8mb4 | 243 | | Yes | 8 | | utf8mb4_german2_ci | utf8mb4 | 244 | | Yes | 8 | | utf8mb4_croatian_ci | utf8mb4 | 245 | | Yes | 8 | | utf8mb4_unicode_520_ci | utf8mb4 | 246 | | Yes | 8 | | utf8mb4_vietnamese_ci | utf8mb4 | 247 | | Yes | 8 | +------------------------+---------+-----+---------+----------+---------+ 26 rows in set (0.00 sec)
- 按照规范实现的collation是utf8_unicode_ci
- 前面说character set和collation是以前数据库创建需要考虑的问题,那么今天我们就
不需要考虑了么?答案是真的不需要,我们只需要一股脑使用utf8mb4 + utf8_unicode_ci
就可以了
mysql> CREATE DATABASE JobSearchPlus CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci; CREATE DATABASE JobSearchPlus CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci; Query OK, 1 row affected (0.00 sec) mysql> SELECT * FROM information_schema.SCHEMATA WHERE schema_name = "JobSearchPlus"; SELECT * FROM information_schema.SCHEMATA WHERE schema_name = "JobSearchPlus"; +--------------+---------------+----------------------------+------------------------+----------+ | CATALOG_NAME | SCHEMA_NAME | DEFAULT_CHARACTER_SET_NAME | DEFAULT_COLLATION_NAME | SQL_PATH | +--------------+---------------+----------------------------+------------------------+----------+ | def | jobsearchplus | utf8mb4 | utf8mb4_unicode_ci | NULL | +--------------+---------------+----------------------------+------------------------+----------+ 1 row in set (0.00 sec)
Database Users and Security
- 如果你是本地修改数据库的话,那么使用root用户是最好的,永远不会有权限的问题. 但是真正的数据库在使用的时候都是要创建不同的用户,拥有不同的权限的.
- 需要注意的是,MySQL的用户和密码是和操作系统的用户名密码完全独立的(这点和SQLServer
不一样).所有的用户都存储在`mysql` database的`user`table里面.如果我们使用最
常见的[database].[table].[column]形式来表示的话,就是mysql.user.user
mysql> SELECT user, host from mysql.user; SELECT user, host from mysql.user; +-----------+-----------+ | user | host | +-----------+-----------+ | mysql.sys | localhost | | root | localhost | +-----------+-----------+ 2 rows in set (0.00 sec)
- 为什么我们要打印两个field呢?因为mysql的权限其实是这两个field的结合体,也就 是说只有从`host`登录的`user`才有相应的权限.
- 换句话说,我们的user和host两个column联合起来起到了composite primary key的作 用,而且其他的table也会把这两个primary key作为用户table的外键.
- 通过列出数据库中所有的列,发现mysql.user表确实没有常规的那种单一的primary key
mysql> SHOW COLUMNS FROM mysql.user; SHOW COLUMNS FROM mysql.user; +------------------------+-----------------------------------+------+-----+-----------------------+-------+ | Field | Type | Null | Key | Default | Extra | +------------------------+-----------------------------------+------+-----+-----------------------+-------+ | Host | char(60) | NO | PRI | | | | User | char(32) | NO | PRI | | | | Select_priv | enum('N','Y') | NO | | N | | | Insert_priv | enum('N','Y') | NO | | N | | | Update_priv | enum('N','Y') | NO | | N | | | Delete_priv | enum('N','Y') | NO | | N | | | Create_priv | enum('N','Y') | NO | | N | | | Drop_priv | enum('N','Y') | NO | | N | | | Reload_priv | enum('N','Y') | NO | | N | | | Shutdown_priv | enum('N','Y') | NO | | N | | | Process_priv | enum('N','Y') | NO | | N | | | File_priv | enum('N','Y') | NO | | N | | | Grant_priv | enum('N','Y') | NO | | N | | | References_priv | enum('N','Y') | NO | | N | | | Index_priv | enum('N','Y') | NO | | N | | | Alter_priv | enum('N','Y') | NO | | N | | | Show_db_priv | enum('N','Y') | NO | | N | | | Super_priv | enum('N','Y') | NO | | N | | | Create_tmp_table_priv | enum('N','Y') | NO | | N | | | Lock_tables_priv | enum('N','Y') | NO | | N | | | Execute_priv | enum('N','Y') | NO | | N | | | Repl_slave_priv | enum('N','Y') | NO | | N | | | Repl_client_priv | enum('N','Y') | NO | | N | | | Create_view_priv | enum('N','Y') | NO | | N | | | Show_view_priv | enum('N','Y') | NO | | N | | | Create_routine_priv | enum('N','Y') | NO | | N | | | Alter_routine_priv | enum('N','Y') | NO | | N | | | Create_user_priv | enum('N','Y') | NO | | N | | | Event_priv | enum('N','Y') | NO | | N | | | Trigger_priv | enum('N','Y') | NO | | N | | | Create_tablespace_priv | enum('N','Y') | NO | | N | | | ssl_type | enum('','ANY','X509','SPECIFIED') | NO | | | | | ssl_cipher | blob | NO | | NULL | | | x509_issuer | blob | NO | | NULL | | | x509_subject | blob | NO | | NULL | | | max_questions | int(11) unsigned | NO | | 0 | | | max_updates | int(11) unsigned | NO | | 0 | | | max_connections | int(11) unsigned | NO | | 0 | | | max_user_connections | int(11) unsigned | NO | | 0 | | | plugin | char(64) | NO | | mysql_native_password | | | authentication_string | text | YES | | NULL | | | password_expired | enum('N','Y') | NO | | N | | | password_last_changed | timestamp | YES | | NULL | | | password_lifetime | smallint(5) unsigned | YES | | NULL | | | account_locked | enum('N','Y') | NO | | N | | +------------------------+-----------------------------------+------+-----+-----------------------+-------+ 45 rows in set (0.00 sec)
- 既然是两个field作为key,那么可以说单一一个field不能"单独"定义一个用户.比如
下面的例子中,即便我们都使用joe来登录数据库,但是你从localhost登录,还是从
otherdomain.com登录会对应不同的'用户',权限也就不一样
user host joe localhost joe otherdomain.com guest % - 第三个用户也很特别,来自于% host,也就是说可以从任何IP进行登录.需要注意的是 user table里面的host是用来在登录的时候,对用户做一次判断的,所以可以是这种wildcard 值,而不一定是确定的值
- mysql.user table是mysql认证的第一步,面对所有试图的登录,mysql都会用这个表来 进行检查
- mysql认证的第二个部分,是当已经登录的client进行SQL或者mysql内置等命令的时候, 要去user表里面查找某个用户是否有相应的权限.后面我们会看到如何设置这个权限
Changing the Root Password
- mysql在某些发行版上的root密码为空,这是一个非常大的安全隐患,所以安装数据库 后马上确认root密码是否为空是非常重要的.
- 使用如下命令能够直接进入,说明密码真的是空
mysql -uroot
- 可以使用如下命令就能为root@localhost设置一个密码
mysql> SET PASSWORD FOR 'root'@'localhost' = PASSWORD('newpassword'); SET PASSWORD FOR 'root'@'localhost' = PASSWORD('newpassword'); Query OK, 0 rows affected, 1 warning (0.00 sec) mysql> exit; exit; Bye hfeng@ mysql (master) $ mysql -uroot mysql -uroot ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: NO) hfeng@ mysql (master) $ mysql -uroot -pnewpassword mysql -uroot -pnewpassword mysql: [Warning] Using a password on the command line interface can be insecure. Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 19 Server version: 5.7.17 Homebrew Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. mysql>
- 各种GUI软件在root登录的时候更改root密码总是会报错,所以更改root密码这件事, 还是命令行更靠谱
- 还有一种方法是把超级管理员改名,改成root以外的名字,但是我觉得这个虽然会一定
程度迷惑hacker,但是会更迷惑队友.
mysql> RENAME USER 'root'@'localhost' to 'primary'@'localhost';
Adding New Accounts and Assigning Privileges
- 当你创建database的时候,你肯定不希望以后都使用root账户来控制每个database,而
是希望最好每个database都有自己的账号系统.要做到这样需要两步:
- 创建账号
mysql> CREATE USER 'martin'@'localhost' IDENTIFIED BY 'password'; CREATE USER 'martin'@'localhost' IDENTIFIED BY 'password'; Query OK, 0 rows affected (0.01 sec) mysql> SELECT user, host FROM mysql.user ; SELECT user, host FROM mysql.user ; +-----------+-----------+ | user | host | +-----------+-----------+ | martin | localhost | | mysql.sys | localhost | | root | localhost | +-----------+-----------+ 3 rows in set (0.00 sec)
- 为账号GRANT权限
mysql> GRANT SELECT, INSERT, UPDATE, DELETE ON JobSearchPlus.* TO 'martin'@'localhost'; GRANT SELECT, INSERT, UPDATE, DELETE ON JobSearchPlus.* TO 'martin'@'localhost'; Query OK, 0 rows affected (0.00 sec) mysql> SELECT Host, Db, User, Select_priv, Insert_priv , Update_priv , Delete_priv , Create_priv FROM mysql.db ; SELECT Host, Db, User, Select_priv, Insert_priv , Update_priv , Delete_priv , Create_priv FROM mysql.db ; +-----------+---------------+-----------+-------------+-------------+-------------+-------------+-------------+ | Host | Db | User | Select_priv | Insert_priv | Update_priv | Delete_priv | Create_priv | +-----------+---------------+-----------+-------------+-------------+-------------+-------------+-------------+ | localhost | sys | mysql.sys | N | N | N | N | N | | localhost | jobsearchplus | martin | Y | Y | Y | Y | N | +-----------+---------------+-----------+-------------+-------------+-------------+-------------+-------------+ 2 rows in set (0.00 sec)
- 创建账号
- GRANT是Mysql的内置命令,其原理是修改mysql.user的相关域(并且马上自动FLUSH PRIVILEGES).我们当然可以使用SQL来直接更改mysql.db table,但是不推荐使用SQL 因为GRANT更不容易出错,而且自动回调用FLUSH PRIVILEGES
- 这个时候,去查看mysql.db表,就不如GUI的界面清楚了.一般GUI程序都会有Schema Privileges来显示某个用户的权限
- 有"添加权限"必然有"取消权限"
mysql> SELECT Host, Db, User, Delete_priv FROM mysql.db ; SELECT Host, Db, User, Delete_priv FROM mysql.db ; +-----------+---------------+-----------+-------------+ | Host | Db | User | Delete_priv | +-----------+---------------+-----------+-------------+ | localhost | sys | mysql.sys | N | | localhost | jobsearchplus | martin | Y | +-----------+---------------+-----------+-------------+ 2 rows in set (0.00 sec) mysql> REVOKE DELETE ON JobSearchPlus.* FROM 'martin'@'localhost'; REVOKE DELETE ON jobsearchplus.* FROM 'martin'@'localhost'; Query OK, 0 rows affected (0.00 sec) mysql> SELECT Host, Db, User, Delete_priv FROM mysql.db ; SELECT Host, Db, User, Delete_priv FROM mysql.db ; +-----------+---------------+-----------+-------------+ | Host | Db | User | Delete_priv | +-----------+---------------+-----------+-------------+ | localhost | sys | mysql.sys | N | | localhost | jobsearchplus | martin | N | +-----------+---------------+-----------+-------------+ 2 rows in set (0.00 sec)
- 我们还可以查询某个用户的权限
mysql> SHOW GRANTS FOR 'martin'@'localhost'; SHOW GRANTS FOR 'martin'@'localhost'; +---------------------------------------------------------------------------+ | Grants for martin@localhost | +---------------------------------------------------------------------------+ | GRANT USAGE ON *.* TO 'martin'@'localhost' | | GRANT SELECT, INSERT, UPDATE ON `jobsearchplus`.* TO 'martin'@'localhost' | +---------------------------------------------------------------------------+ 2 rows in set (0.00 sec)
Chapter 6: Tables and Indexes
Introduction
- 即便mysql里面存在很多很好用的GUI工具,但是你要知道这些工具的本质其实还是让你 手动操作,它转换成各种SQL语句然后发送.所以本质和你在mysql command line写SQL 是一样的,只不过GUI不容易出错
Storage Engines
- Mysql的一大优点就是它能够同时支持多个database storage engine
- storage engine的作用是管理table和它们存储在local computer的存储形式
- storage engine的作用最小单位是table,也就是说一个database里面的两个表可能是 不同的engine
- 在Mysql v5.5.5以前,mysql默认的storage engine是MyISAM,其最大可存储的容量大小 是256TB,考虑到大部分的操作系统和文件系统的能力,这个大小限制已经"高"出很多了
- MyISAM的特点让它适合作为data warehouse,data warehouse的特点是:
- 数据量大
- 高频率快速读取已经存在的数据
- 低频率的增加或者更新
- 支持数据压缩,所以在server上占用更少的空间
- MyISAM的缺点也很明显,第一它不支持外键约束!
- 如果某种数据库这种支持外键,从而能够保证primary key和foreign key的话,我们说 这种数据库支持referential integrity
- 举个例子,我们有tableA里面的itemB有一个外键指向tableC里面的itemD,这个itemD可 以完全不存在.
- 或者说一开始这个itemB指向这itemD,但是itemD被删除了,itemB没有"相应地"被删除, 但是数据库却不报错.
- 从v5.5.5开始,默认的storage engine变成了InnoDB:
- 最大数据支持变成了64TB
- 但是支持外键约束(所以也就保证了referential integrity)
- 支持事务(也就保证了数据完整性)
- innode 还带来了一个全新的叫做clustered index的feature,其作用是在物理上安装 index来排序数据,从而加快搜索的速度
- mysql上面还是有其他的engine的,只不过用的人不多
mysql> SHOW ENGINES; SHOW ENGINES; +--------------------+---------+----------------------------------------------------------------+--------------+------+------------+ | Engine | Support | Comment | Transactions | XA | Savepoints | +--------------------+---------+----------------------------------------------------------------+--------------+------+------------+ | InnoDB | DEFAULT | Supports transactions, row-level locking, and foreign keys | YES | YES | YES | | MRG_MYISAM | YES | Collection of identical MyISAM tables | NO | NO | NO | | MEMORY | YES | Hash based, stored in memory, useful for temporary tables | NO | NO | NO | | BLACKHOLE | YES | /dev/null storage engine (anything you write to it disappears) | NO | NO | NO | | MyISAM | YES | MyISAM storage engine | NO | NO | NO | | CSV | YES | CSV storage engine | NO | NO | NO | | ARCHIVE | YES | Archive storage engine | NO | NO | NO | | PERFORMANCE_SCHEMA | YES | Performance Schema | NO | NO | NO | | FEDERATED | NO | Federated MySQL storage engine | NULL | NULL | NULL | +--------------------+---------+----------------------------------------------------------------+--------------+------+------------+ 9 rows in set (0.00 sec)
Models and Schemas
- 使用MySQL workbench,你可以使用如下两种方式来创建数据库:
- 打开File >> new Model.这会打开一个新的model editor,然后按照我们前面说的data model的步骤一步一步的去做
- 使用各种方法(比如SQL)创建好数据库以后,反推EER diagram,这叫reverse engineering
MySQL Workbench
Getting Started
- 创建链接什么的就很简单了,会了命令行以后,GUI只不过是找到地方输入命令行的信 息就可以了
- 创建完mysql connection以后,界面会把connection workspace放在左边,右边是query editor(这里可以输入SQL得到你想要的结果)
Creating the Tables
- 我们前面已经创建了jobsearch_plus(使用全小写的名字是为了能够让windows和unix
都兼容).如果想再jobsearch_plus下面进行操作的话,必须加上下面这句
USE jobsearch_plus;
- 在workbench里面一旦你创建了数据库,即便是空的数据库,下面也会有四个选项:
- Tables
- Views
- Stored Procedures
- Functions
- 如今想创建table直接在Tables上右键就可以了.创建完了以后首先是要写上table的
总体setting(mac上需要点一下下拉框)
Name Collation Engine Comments leads utf8mb4-utf8mb4_unicode_ci InnoDB Main job leads table - 设定完table的main setting后,该是设计每个field了,workbench里面每个column的
的设置有如下:
- Column Name: 用来区分不同column.要求和编程语言里面的变量差不多:不能使用 关键字,使用A-Z,0-9
- Data Type: column的类型,其实就是使用多少内存来存储一个column
- Primary Key: 判断是不是table的主键.设置这个以后自动会设置NN(Not Null)和 UQ(Unqiue)
- Not Null(NN):某个column如果是Not Null的话,那么每次都要给这个column值,否 则插入违法
- Unique Index(UQ):设置某个column是否要求唯一,一旦设置了某个column唯一的话, 数据库也会自动在这个column上面建立索引(indx)
- Is Binary Column(BIN): 用来设置某个column为non-numeric field.因为二进制 文件可能含有有对字符来说是非法字符的内容
- Unsigned Data Type(UN): non-negative number
- Zero Fill(ZF): 对于numeric field来说,如果没有值,设置了这个ZF的话,会自动 填充0
- Auto Increment(AF): 主要用于PK键,会每次自动+1
- Default: 设置某个field的默认值(如果不给值的话,就使用默认值,ZF就是一种默 认值,默认为0)