MySQL-其它

未分类
(一)MySQL整体结构图

1,Connectors
接入方 支持协议很多

Management Serveices & Utilities:
系统管理和控制工具
例如:备份恢复,mysql复制集群等

3,Connection Pool
连接池:管理缓冲用户连接、用户名、密码、权限校验、线程处理等需要缓存的需求

4,SQL Interface
SQL接口:接受用户的SQL命令,并且返回用户需要查询的结果。比如select from就是调用SQL Interface

5,Parser: 解析器,SQL命令传递到解析器的时候会被解析器验证和解析。解析器是由Lex和YACC实现的。

6,Optimizer: 查询优化器,SQL语句在查询之前会使用查询优化器对查询进行优化

7,Cache和Buffer(高速缓存区): 查询缓存,如果查询缓存有命中的查询结果,查询语句就可以直接去查询缓存中取数据。

8,pluggable storage Engines
插件式存储引擎。存储引擎是MySql中具体的与文件打交道的子系统。也是Mysql最具有特色的一个地方。 Mysql的存储引擎是插件式的。

9,file system
文件系统,
数据、日志(redo,undo)、索引、错误日志、查询记录、慢查询等

(二)查询执行的路径
1.全览图

1.MySQL客户端/服务端通信接收进来
2.是否开了缓存,然后进行解析,解析成整个运行过程中系统能够识别的解析树,
3.解析树交给预处理模块儿,预处理模块儿检测解析树的语法合法性,还会去验证一些相关的权限,比如说列名相关的关键字相关的东西有没有违法或者冲突,这个模块儿会给你报1064等等相关的错误码,验证完了它会交给下一个步骤,查询优化器
4.查询优化器会去基于规则计算,拿到最优的执行计划去基于API调用存储引擎,
5.调用完了会把查询出来的数据返回给我们的客户端

阶段一,MySQL客户端/服务端通信

MySQL客户端与服务端通信的方式是”半双工”

半双工意思是A和B的通信是双向的,A可以和B传输内容,B也可以和A传输内容,但是同一时间,只能一端向另外一端发送内容,一端发送完内容后会一直等着等另一端去接收完了并响应

MySQL通讯特点和限制:
客户端一旦开始发送消息另一端要接收完整个消息才能响应.
客户端一旦开始接收数据没法停下来发送指令

查看命令 show full processlist /show processlist 第一个是普通用户的命令, 第二个是管理员的命令

Id是唯一标识
对于出现问题的连接 可以通过kill {id} 的方式进行连接和杀掉
假如出现了死锁,或者sql写的有问题,一直卡着,超过了10分 20 分钟还不行就可以直接根据id杀死

User是用户
Host 是连接信息
db 是连接的数据库

Time是状态维持了多久了

Command
Sleep 线程正在等待客户端发送数据
Query 连接线程正在执行查询
Locked 线程正在等待表锁的释放
Sorting result 线程正在对结果进行排序
Sending data 向请求端返回数据
Info 是sql语句
阶段二查询缓存
查询缓存是一个坑,但是有它存在的意义

工作原理:
缓存select操作的结果集和sql语句
新的select语句,先去查询缓存,判断是否存在可用的记录集;

判断标准:
与缓存的sql语句,是否完全一样,区分大小写,中间空格的数量也是必须一致的
(简单认为存储一个keyvalue结构,key是sql,value是sql的查询结果)

set global query_cache_type = 0 ???好像不是这个改变的,需要修改总配置文件

0就是关闭
1就是完全开启 也可以sql语句加 sql_no_cache 指定这个sql不让它去缓存.
2就是按需开启 就是SQL语句加了SQL_CACHE 关键字它才会去缓存.

阶段三查询优化的处理
查询优化又分为三个阶段:
解析SQL
通过lex词法分析,yacc语法分析将sql语句解析成为解析树.
预处理阶段
根据MySQL的语法的规则进一步检查解析树的合法性(比如说表和列是否存在等等),如: 检查数据的表和列是否存在,解析名字和别名的设置.进行权限和验证
查询优化器
优化器的主要作用就是找到最优的执行计划

查询优化器如何找到最优执行计划
1.使用等价变化规则
2.将可转换的外连接查询转换成内连接查询
3.优化count,min,max等函数
4.覆盖索引扫描
5.子查询优化
6.提前终止查询(limit查询,或者不成立的条件查询)
7.in的优化
8…

对客户端的操作(不是操作表的)
(一)
1.通过黑窗口访问MySQL
mysql 为登录命令 -h后面的参数是服务器的主机地址, -u 后面是数据库用户名 -p后面是用户登录密码

2.彻底卸载MySQL

卸载完了之后

运行 regedit 打开注册表
删除HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services\Eventlog\Application\MySQL文件夹
删除HKEY_LOCAL_MACHINE\SYSTEM\ControlSet002\Services\Eventlog\Application\MySQL文件夹。
删除HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Eventlog\Application\MySQL的文件夹。
如果没有相应的文件夹,就不用删除了
删除C盘下的“C:\ProgramData\MySQL ”文件夹,如果删除不了则用360粉碎掉即可,
该programData文件默认是隐藏的,设置显示后即可见,或者直接复制 C:\ProgramData 到地址栏回车即可进入!
将整个MySQL文件夹删除掉。

https://www.cnblogs.com/jpfss/p/6652701.html

3.更改MySQL的配置

有两种,一种是通过配置向导来更改MySQL配置,另一种是手工修改配置文件来更改配置.
通过配置向导

手工更改配置文件

4.查看mysql数据文件的存储文件位置并且

打开输入:
show global variables like “%datadir%”;

修改Mysql的表存放位置
默认存放位置是C盘,缺点就是重装系统后C盘的东西都会消失,这样的话导致表也会消失.
解决办法:修改Mysql的存放位置

查看附件

修改mysql数据文件的存放位置
C:\Program Files (x86)\MySQL\MySQL Server 5.5
找到 mysql安装位置,把my.ini 文件放到桌面等地方(不能再mysql安装位置操作,因为需要管理员权限,)
右键打开它
找到这个: datadir 后面的就是你存放的位置了

#Path to the database root

自行修改, 别忘了修改前备份文件
修改完成之后,需要重启 mysql的服务.
5.彻底解决乱码问题

http://note.youdao.com/noteshare?id=1b93fbd3173536331138fc7ed2a535c5

(二)用户管理
1.用户管理目录(MySQL5.7从入门到精通)

(三)数据库的备份与恢复
1.备份所有的数据库
黑窗口输入命令
在黑窗口输入:

MySQLdump -u root -p --all-databases >d:/1.sql

该语句创建一个 1.sql 文件在D盘下,其中文件包含了对系统中所有数据库的备份信息.

直接复制整个数据库目录

(四)数据库迁移

1.迁移数据文件
1.先停止MySQL服务
2.直接剪切数据文件夹放到你想存放的任意磁盘位置
3.在my.ini 设置数据库存放目录位置 datadir

datadir=F:\9WpsWorkSpace\MySQLData

(五)MySQL日志

(六)其它目录再看

操作数据库

(一)其它
1.创建数据库同时指定编码

CREATE DATABASE 数据库名字 DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;

COLLATE utf8_general_ci:数据库校对规则。ci是case insensitive的缩写,意思是大小写不敏感;相对的是cs,即case sensitive,大小写敏感;还有一种是utf8_bin,是将字符串中的每一个字符用二进制数据存储,区分大小写。
2.表的概念

建表语句指定utf-8 (字典表)
CREATE TABLE rests_dict
(
id varchar(60) PRIMARY KEY COMMENT ‘主键’,
dict_name varchar(40) COMMENT ‘字典名称’,
dict_type varchar(40) COMMENT ‘字典类型’,
dict_code varchar(40) COMMENT ‘字典代码(获取的值)’,
dict_value varchar(40) COMMENT ‘字典值(显示在页面的值)’,
dict_sort_number int COMMENT ‘字典排序值’,
remark varchar(200) COMMENT ‘备注’

) ENGINE = InnoDB
DEFAULT CHARSET = utf8 COMMENT =‘字典数据表’

分类
宽泛的定义下,存在三种类型的表,这三种表都可以在查询的from子句中使用.

永久表(使用create table 语句创建的表)
临时表(使用子查询所返回的表)
虚拟表(使用 create view 子句所创建的视图)

子查询产生的表
子查询值得是包含在另一个查询中的查询,子查询可以出现在select语句中的各个部分并且被包含在圆括号中,在from子句内,子查询的作用是根据其他查询子句(其中的from子句可以与其他表进行交互)产生临时表
SELECT *
FROM (SELECT id, user_uuid
FROM travel_order) e;

3.查看数据库的所有表信息
show table status from 数据库名

show table status from lmxi

4.主键
主键是唯一的,并且不允许为空,主键能够唯一地识别表中的一条记录,可以结合外键定义不同数据表之间的关系,并且可以加快数据库的查询速度,主键就相当于人的身份证一样,
主键分为两种:
单字段主键和多字段联合主键

单字段主键:
就是常规的一个字段是主键

多字段主键:
主键是多个字段联合组成的

5.外键约束
外键基本介绍

需要注意,外键约束,不能跨存储引擎来使用

添加外键约束
a.一对多关系
商品分类表 和 商品详情表

	添加外键约束:*************
		alter table 从表名 add constraint 起一个名字 foreign key (外键字段)
		references  主表名 (主键字段)

删除表的外键约束

6.默认约束

7.查看表数据结构
describe 表名
或者简写
desc 表名

8.查看表的建表语句

show create table 表名
结果

9.从一个表复制到另一个表

CREATE TABLE orderCopy AS
SELECT *
FROM order – 新建了一个表为orderCopy ,表结构和数据和order表是完全一样的

数据类型
1.数据类型基本介绍

MySQL支持多种数据类型,主要有数值类型,日期/时间 和字符串类型
1.数值数据类型: 包括整数类型 tinyint , smallint ,mediumint ,int ,bigint, 浮点小数数据类型float和double , 定点小数类型decimal
2.日期/时间类型: 包括 year , time ,date , datetime 和 timestamp
3.字符串类型: 包括char ,varchar,binary .varbinary ,blob ,text ,enum 和set 等.

2.尽量避免使用null
很多表都包含可为NULL(空值)的列,即使应用程序并不需要保存NULL也是如此,这是因为可为NULL是列的默认属性(3)。通常情况下最好指定列为NOT NULL,除非真的需要存储NULL值。
如果查询中包含可为NULL的列,对MySQL来说更难优化,因为可为NULL的列使得索引、索引统计和值比较都更复杂。可为NULL的列会使用更多的存储空间,在MySQL里也需要特殊处理。当可为NULL的列被索引时,每个索引记录需要一个额外的字节,在MyISAM里甚至还可能导致固定大小的索引(例如只有一个整数列的索引)变成可变大小的索引。
通常把可为NULL的列改为NOT NULL带来的性能提升比较小,所以(调优时)没有必要首先在现有schema中查找并修改掉这种情况,除非确定这会导致问题。但是,如果计划在列上建索引,就应该尽量避免设计成可为NULL的列。
当然也有例外,例如值得一提的是,InnoDB使用单独的位(bit)存储NULL值,所以对于稀疏数据(4)有很好的空间效率。但这一点不适用于MyISAM。

(二)数值类型
1.基本介绍
数值型数据类型主要是用来存储数字,MySQL提供了多种数值数据类型,不同的数据类型提供不同的取值范围,可以存储的值范围越大,其所需要的存储空间也就越大,MySQL主要提供的整数类型有: tinyint ,smallint ,mediumint ,int(integer) , bigint 整数类型的属性字段可以添加 auto_increment 自增约束条件.

类型名称 说明 存储需求
tinyint 很小的整数 1个字节
smallint 小的整数 2个字节
mediumint 中等大小的整数 3个字节
int(integer) 普通大小的整数 4个字节
bigint 大整数 8个字节
decimal 定点数
float 单精度浮点数
double 双精度浮点数
bit 位字段

MySQL的数值数据类型分为3大类,如下所示。
❏ 精确值类型,它包括整数类型和DECIMAL。整数类型用来存放没有小数部分的数值,如43、-3、0、-798432等。只要是能够用整数来表示的数据(如精确到磅的重量值、精确到英寸的长度值、家庭人口数、银河中的恒星数、培养皿中的细菌数等),都可以用一个整数类型的数据列来保存。DECIMAL类型保存的精确值可以有一个小数部分,如3.14159、-.00273、-4.78等。这种数据类型非常适合用来保存财务金额数据。只要有可能,进入数据库的整数和DECIMAL值就会和你录入的完全一样,不存在四舍五入的问题,用它们进行的计算也是精确的。
❏ 浮点类型,它细分为单精度(FLOAT)和双精度(DOUBLE)。这些类型和DECIMAL类型一样,也可以用来存放有小数部分的数值,但它们容纳的是可能发生四舍五入的近似值,如3.9E+4或-0.1E-100。如果对数值精确度的要求不那么严格或是数值大到DECIMAL类型无法表示,浮点类型会是不错的选择。诸如粮食平均亩产、空间距离、失业率之类的数据都很适合用浮点值来保存。
❏ BIT类型用来保存位字段值(bit-field value)。
可以把带小数部分的值放到一个整数类型的数据列里去,但它将被四舍五入为一个整数:如果小数部分大于或等于0.5,舍弃小数部分,整数部分加上1(负数是减去1)。类似地,你也可以把整数值放到一个浮点类型的数据列里去,它将被看做是一个小数部分等于零的浮点数。

float:浮点型,含字节数为4,32bit,数值范围为-3.4E38~3.4E38(7个有效位)
double:双精度实型,含字节数为8,64bit数值范围-1.7E308~1.7E308(15个有效位)
decimal:数字型,128bit,不存在精度损失(相对不存在,28个有效位后会报错),常用于银行帐目计算。(28个有效位)
float f = 345.98756f;//结果显示为345.9876,只显示7个有效位,对最后一位数四舍五入。
double d=345.975423578631442d;//结果显示为345.975423578631,只显示15个有效位,对最后一位四舍五入。

注:float和double的相乘操作,数字溢出不会报错,会有精度的损失。

decimal dd=345.545454879…//可以支持28位,对最后一位四舍五入。因为高精度,28位的有效位数,这才是财务计算中使用的最主要原因。

2.精确值数值数据类型
精确值数据类型包括整数类型和定点DECIMAL类型。
MySQL中的整数类型包括TINYINT、SMALLINT、MEDIUMINT、INT和BIGINT。INTEGER是INT的一个同义词。这几种数据类型的主要区别在于它们所代表的值的取值范围和它们所需要的存储空间各不相同。(取值范围越大,需要的存储空间就越多。)整数数据列可以被定义为UNSIGNED以表明不允许使用负数,这将把取值范围上移到从0开始的区间。
在定义整数列时,你可以给它指定一个可选的显示宽度M0指定的M值必须是1到255之间的一个整数。它决定着MySQL将用多少个字符来显示该数据列里的值。比如说,MEDIUMINT(4)定义了一个MEDIUMINT数据列,这个数据列的显示宽度是4个字符。如果你没有给整数列声明一个显式宽度,MySQL将自行确定一个默认的宽度,这个默认宽度通常是该整数列里“最长”的值的长度。需要特别注意的是,数据列里的值并不会因为你设置了显示宽度而被截短为M个字符。如果某个值需要有M个以上的字符才能打印出来,MySQL会把它完整地显示出来。
整数列的显示宽度M指的是MySQL将用多少个字符来显示该数据列里的值,与整数值需要用多少个字节来存储毫不相干。比如说,不管显示宽度是多少个字符,BIGINT值都要占用8个字节的存储空间。即使你把它写成BIGINT(4),BIGINT数据列所要求的存储空间也不可能奇迹般地缩小一半。这个M与数据列的取值范围也没有任何关系,你可以把某个数据列声明为INT(3),但这并不会把这个数据列里的最大值限定为999。
DECIMAL是一种定点类型,构成DECIMAL值的十进制数字的个数是固定的。这个事实的最大优点是DECIMAL值不像浮点数那样存在四舍五入的问题——这个特点使得DECIMAL类型非常适合用来保存财务数据。
NUMERIC和FIXED都是DECIMAL的同义词。

DECIMAL数据列也可以被定义成UNSIGNED。与整数类型不同的是,把一个DECIMAL类型的数据列定义为UNSIGNED不会扩大该数据列的取值范围,其效果只是“砍掉”整个负数部分而已。
对于DECIMAL数据列,你可以给出一个有效数字的最大个数M和一个小数部分数字个数D。它们分别对应于“精度”和“小数位数”概念,这些概念应该是你比较熟悉的。M的取值范围是从1到65,D的取值范围是从0到30且不得大于M0
M和D都是可选的。如果省略了D,它的默认值将是0。如果省略了M,它的默认值将是10。换句话说,以下等效关系是成立的:

DECIMAL类型的取值范围要取决于M和D的值。如果在保持D不变的情况下调整M的值,取值范围将随着M的变大而增加(表3-8)。如果在保持M不变的情况下调整D的值,取值范围将随着D的变大而减小(表3-9)。

3.浮点数与定点数
浮点数一般用于表示含有小数部分的数值,当一个字段被定义为浮点类型后,如果插入数据的精度超过该列定义的实际精度,则插入值会被四舍五入到实际定义的精度值,然后插入,四舍五入的过程不会报错.在MySQL中float,double(或real)用来表示浮点数.
定点数不同于浮点数,定点数实际上是以字符串形式存放的,所以,定点数可以更精确地保存数据.如果实际插入的数值精度大于实际定义的精度,则MySQL会进行警告(默认的SQLMode下),但是数据按照实际精度四舍五入后插入;如果SQLMode是TRADITIONAL(传统模式)下,则系统会直接报错,导致数据无法插入,在MySQL中,decimal(或numberic)用来表示定点数.

在精度要求比较高的应用中(比如货币)要使用定点数而不是浮点数来保存数据.

注意:在今后关于浮点数和定点数的应用中,用户要考虑到以下几个原则:
1.浮点数存在误差问题
2.对货币等精度敏感的数据,应该用定点数表示或存储;
3.在编程中,如果用到浮点数,要特别注意误差问题,并尽量避免做浮点数比较;
4.要注意浮点数中一些特殊值的处理

4.bit数据类型
BIT数据类型始见于MySQL 5.0.3版本,它是一种专门用来保存位字段值的类型。在定义一个BIT数据列时,你可以设定一个可选的最大宽度M,它表示以位计算的数据列的宽度。M的取值必须是1到64之间的一个整数。如果被省略,M的默认值将是1。
在默认的情况下,从BIT数据列检索出来的值不能显示为可打印形式。如果需要把一个位字段值显示为可打印的表示形式,办法是给它加上零或是使用CAST()函数:
5.挑选数值数据类型
在为数值数据列挑选数据类型时,你需要考虑数据的取值范围并挑选一个能够覆盖该范围的最小类型来使用。选择较大的类型会浪费存储空间,毫无必要地使数据表变得很大,会降低数据的处理效率。先说整数值,如果数据的取值范围很小,比如人的年龄或兄弟姐妹的人数,TINYINT类型就是最佳选择。MEDIUMINT类型可以用来表示好几百万个值,这对很多应用项目来说都已经足够了,但它在存储空间方面有额外开销。BIGINT类型的取值范围最大,但它占用的存储空间将是最小的整数类型(INT)的两倍,所以我们应该只在确有必要时才使用它。再看浮点值,DOUBLE类型所占用的存储空间是FLOAT类型的两倍。因此,如果你不需要非常高的精确度或者数据的取值范围不是非常大的话,用FLOAT类型代替DOUBLE类型可以节约一半的存储开销。
每个数值数据列的取值范围是由它的类型决定的。如果你试图把一个超出某数据列取值范围的值插入该数据列,结果将取决于是否已启用严格SQL模式。如果已启用,超出取值范围的值将导致一个错误;如果未启用严格模式,MySQL将截短这个值,MySQL会先把这个值替换为该数据列取值范围的上限值或下限值后再进行插入,同时生成一条警告消息。
数据值的截短处理是根据数据类型的取值范围而不是它的显示宽度进行的。比如说,一个声明为SMALLINT(3)类型的数据列的显示宽度只有3个字符,但它的取值范围却是从-32768到32767。如果你想把值12345插入到这个数据列里,那么,因为12345虽然要比这个数据列的显示宽度更长,但仍落在该数据列的取值范围内,所以插入操作不会出现截短处理,这个值仍将以12345的形式被插入和检索。可值99999就不同了,它超出了这个数据列的取值范围,所以它在插入时将被截短为32767,以后的检索操作所查到的结果也将是32767。
对于定点或浮点数据列,如果将被存储的值比该数据列所能容纳的小数位数多,MySQL将根据这个数据列的类型声明对小数点后面的数字进行取舍。比如说,如果你把1.23456插入到一个FLOAT(8, 1)数据列里,其结果将是1.2;如果你把这个值插入到一个FLOAT(8, 4)数据列里,其结果将是1.2346。也就是说,你必须根据自己对精确度的要求来选定一个小数点后面的位数。如果你的数据需要精确到千分之一,就不能把小数点后面的位数声明为两位。
6.数据类型的选用
3.2节描述了各种可供选用的数据类型和那些数据类型的基本特性,如它们所能容纳的值的类型,它们会占用多少存储空间,等等。但在创建数据表时又该如何决定使用哪种类型呢?本节将要讨论的问题可以帮助大家做出最好的选择。
字符串类型是最“通用”的数据类型。可以把任何东西保存在它们里面,因为数值和日期可以用字符串形式来表示。那么,是不是应该把所有的数据列都定义为字符串并满足于此呢?不是的。我们来看一个简单的例子。假设有一些看起来像是数值的数据。你当然可以把它们表示为字符串,但真的应该这么做吗?如果这么做了,会发生什么事情?
首先,你可能会耗用更多的空间,因为使用数值类型的数据列来保存数值要比使用字符串类型更有效率。你还将注意到因为对数值和字符串的处理方式的不同而表现在查询结果方面的一些差异。比如说,数值的排序结果与字符串的是不一样的。数值2小于数值11,但字符串’2’在词法上却是大于字符串’11’的。这个问题可以通过把数据列放到一个数值上下文环境里使用的办法来解决,如下所示:
select col_name + 0 as num … order by num ;
给数据列加上0就可以强行按数值方式进行排序,但这么做合理吗?这在某些场合里是一个很有用的技巧,但犯不上每次需要按数值方式排序时都用这一招吧。让MySQL把一个字符串数据列当做一个数值数据列来对待有几个很重要的问题。首先,这将迫使MySQL对数据列里的每一项数据进行从字符串到数值的转换,而这种做法会降低效率。其次,使用这样的数据列进行计算会导致MySQL不使用这些数据列上的任何索引,这将进一步降低查询的速度。如果从一开始就把数据保存为数值,就不会发生这两种会导致性能降低的事情了。
刚才的例子揭示了我们在挑选数据类型时需要考虑的几个因素。未经深入分析就选用一种表示形式取代另一种表示形式的简单做法往往会在存储要求、查询处理和整体性能方面导致不良后果。下面列出了一些我们在为数据列挑选类型时应该思考的问题。
这个数据列将容纳什么样的数据?数值?字符串?日期?坐标值?这个问题并不难回答,关键在于你必须向自己提出这个问题。你完全可以把任何类型的数据都表示为字符串,但正如我们刚才看到的那样,如果你能为数值形式的数据挑选一种更适用的类型的话,你得到的回报将是更好的性能。(对日期/时间和坐标形式的数据来说也是如此。)请注意,对你将要处理的数据的类型进行评估并不见得总是那么轻而易举,尤其是在数据来源不是你本人的时候。如果你正在为别人设计一个数据表,把“这个数据列将容纳什么样的数据”这个问题弄清楚将非常重要。要想做出一个最佳的决策,你必须保证你已经问过足够多的问题、收集到了足够多的信息。
数据是否都在某个特定的区间内?如果它们是整数,是不是总是非负值?如果是,你可以考虑选用UNSIGNED。如果它们是字符串,它们是不是总来自一个固定的、有限的集合?如果是,你将发现ENUM或SET是一种很有用的类型。
数据类型的取值范围和空间占用量是相互影响的。需要一种多“大”的类型?对于数值,可以选择一种取值范围有限的小类型,也可以选择一种更大的类型。对于字符串,应该根据它们的长短来做出选择。如果打算存储的数据没有超过10个字符的,就用不着选择CHAR(255)。
在性能和效率方面有没有需要考虑的因素?有些类型可以比其他类型处理得更有效率。数值操作通常都比字符串操作执行得更快。短字符串的比较操作要比长字符串的完成得更快速,硬盘方面的开销也更小。就MyISAM数据表而言,长度固定的数据行会比长度可变的数据行有更好的性能表现。
以下各节将对这些问题做更详细的讨论,尤其是性能问题,我们将在5.3节进行探讨。
在继续前进之前,我向提醒大家一句:虽然你已经在创建数据表时尽最大努力做出了最好的数据类型选择,但万一你的选择被事实证明不是最优的,也不会面临世界末日。可以利用ALTER TABLE语句把不妥当的类型改成一种更好的。事情也许很简单:如果发现某个整数数据列里的数据比你当初预想的大,把它从SMALLINT改成MEDIUMINT够不够?事情也许会比较复杂,比如把一个CHAR数据列改变为一个ENUM来容纳它的可取值。可以利用PROCEDURE ANALYSE()来获得关于数据表里的数据列的信息,如最大值、最小值、MySQL根据数据列的取值范围而建议使用的优化类型,等等。
select * from tb1_name procedure analyse();
这个查询的输出报告可以帮助判断是否可以选用一种更“小”的类型,这可以改善相关数据表的查询性能并减少数据表的存储空间占用量。关于PROCEDURE ANALYSE()更详细的信息请参见5.3节。

7.数据类型将容纳什么样的数据
在挑选数据类型时,首先要考虑的事情应该是这个数据列将用来容纳什么样的数据,因为这对你选择的类型有着最直接的影响。一般来说,应该做简明的决定:把数值保存在数值型数据列里,把字符串保存在字符串型数据列里,把日期和时间保存在日期/时间型数据列里。如果数值有一个小数部分,就应该选用DECIMAL或浮点类型,而不是某种整数类型。但是例外情况总是难免的。这里的原则是,必须了解你的数据才能胸有成竹地选出最适用的类型。如果数据来自你本人,你也许早就对如何取舍心中有数。从另一方面讲,如果是别人让你替他们设计一个数据表,事情就不一样了,你想了解的东西恐怕没那么容易搞清楚。一定要问足够多的问题,只有这样,才能发现那个数据表到底是用来容纳什么类型的数据的。
假设别人让你帮忙设计一个数据表,其中有一个数据列是用来记录“降雨量”的。它们是数值吗?或者“几乎”(通常是,但并非总是)是数值?比如说,在看电视新闻的时候,
天气预报员经常会说到降雨量。有时候,那是一个数值(比如“降雨量0.25英寸”),但有时只是说降雨量“稀少”,意思是“几乎没有”。这在天气预报节目里很正常,但怎样才能把这个“稀少”存储到数据库里呢?办法之一是把“稀少”定义为一个数值,以便选用一种数值类型来记录降雨量;办法之二是选用一种字符串类型,以便直接记录“稀少”这两个字。当然,你还可以想出一些更复杂的办法。比如说,同时使用一个数值数据列和一个字符串数据列来记录降雨量,视情况填写其中一个数据列,另一个则为NULL。不过,只要有可能,你肯定不会选择最后这个办法,那不仅会让数据表变得难以理解,还会大大增加编写查询代码的难度。
如果让我来设计这个数据表,我可能会尽量把所有的数据行都存储为数值形式,然后在必要时对它们进行转换以便于显示。我们不妨假设小于0.01英寸且不等于零的降雨量都可以归类为“稀少”,我们就可以用下面这样的代码来显示这个数据列里的值:
select if ( precip > 0 and precip < .01 ,’trace’,precip ) from …;
有些数据明显是数值,但你必须判断应该选用一种整数类型还是一种浮点类型。你必须把数据的计量单位和精确度弄清楚。整数级的计量单位能否满足精确度要求?是否需要增加一个小数部分?这个问题可以帮你区分整数、定点或浮点数据类型。比如说,如果你记录的“重量”只要求精确到千克,就可以选用一个整数数据列。如果记录要求精确到某位小数,就应该选用一个定点或浮点数据列。在某些特殊场合,你甚至可以使用多个数据列。比如说,分别以“千克”和“盎司”为单位来记录重量。
高度是一种可以用多种办法来表示的数值型信息。
❑ 用’6-2’这样的字符串来表示“6英尺,2英寸”。这种表示形式的优点是易于阅读和理解(肯定比“74英寸”好得多),但这样的值难以用来进行加法或求平均值之类的算术操作。
❑ 用一个数值数据列来记录英尺数,用另一个来记录英寸数。这可以让算术操作变得稍微容易点儿,但使用两个数据列怎么说也比只使用一个数据列时的情况来得复杂些。
❑ 用一个数值数据列来表示英寸数。这对数据库来说是最容易处理的,对人类来说却是最不利的。但不要忘了,数据的显示格式和它们的存储/处理格式并非必须保持一致。你可以利用MySQL中的许多函数重新编排数据的格式,把它们以易于阅读和理解的形式呈现给人们。那意味着这可能是表示“高度”的最佳办法。
货币(例如美元),是另一种数值型信息。在涉及金钱的计算里,人们需要和由美元和美分构成的数据打交道。它们乍看上去应该是些浮点值,但FLOAT和DOUBLE类型难免出现四舍五入错误,只适合用来处理对精确度要求不高的数据项。因为人们对自己的金钱都很敏感,所以你肯定需要一种可以提供完美精确度的数据类型。你有以下几种选择。
❑ 你可以把金额表示为一种DECIMAL(M,2)类型,其中M是你需要的取值范围的最大宽度。这种类型的数值精确到小数点后面两位。DECIMAL类型的优点是:数据值不存在四舍五入的错误,计算很精确。
❑ 你可以在系统内部选用一种整数类型以“分”为单位来表示所有与金钱有关的数据。这种表示方法的优点是计算过程在系统内部是使用整数进行的,速度快;缺点是在输入或输出的过程中必须通过对数据乘以或者除以100的办法来完成必要的转换。
某些“数字”其实根本不是真正的数值。电话号码、信用卡号码和社会保险号码都包含有不是数字的字符(比如空格和短划线),不能直接存储到一个数值类型的数据列里,除非你把其中的非数字字符都去掉。但即使把其中的非数字字符都去掉了,你恐怕还是更愿意把它们存储为字符串而不是数字,这样可以避免漏掉开头部分的“零”。
如果你需要保存一些日期信息,你应该问自己:它们包含一个时间值吗?或者,它们是否真的需要包含一个时间值?MySQL没有提供一种时间部分可选的日期类型:DATE没有时间部分,DATETIME必须有时间部分。如果时间部分确实是可选的,那就用一个DATE数据列来记录日期,再用一个TIME数据列来记录时间好了。然后允许那个TIME数据列可以取值为NULL,并把它解释为“无时间”:
create table mytb1
(
date DATE NOT NULL , # date is required
time TIME NULL # time is optional (may be NULL)
);

在某些场合,例如你需要为两个数据表建立“主从”关系而它们之间的“纽带”是日期信息的时候,判断是否需要一个时间值往往非常关键。假设你的研究需要进行一系列测试。在标准的主测试之后,你可能还需要进行几项辅助测试,具体进行哪几项辅助测试要根据主测试的结果来决定。你可以用一个“主从”关系来表示这些信息:把测试主题和标准主测试的情况保存在一个主控数据表里,把辅助测试的情况另行保存在一个细节数据表里,然后通过测试主题ID和测试日期把这两个数据表关联在一起。
在这个场景里,必须回答的问题是:只有日期够不够?日期和时间是不是都必不可少?这取决于你是否会在同一天多次进行同一项测试。如果是,就需要记录一个时间值(比如说,测试的开始时间),这既可以单独使用一个DATETIME数据列来实现,也可以使用一个DATE数据列和一个TIME数据列,这两个数据列都是必须填写的。如果没有记录时间值,一旦你在一天之内进行了两次某项测试,你将无法把该项测试的细节数据行和相应的主控数据行正确地关联在一起。
我曾经听过某些人这么说:“我不需要记录一个时间值,我从不在同一天做两次同样的测试。”有时候,他们还真的能说到做到。但我也见过不少这样的人后来焦头烂额地设法防止细节数据行与错误的主控数据行关联在一起,因为他们在一天之内做了多次同样的测试并在输入测试数据之后发现事情变得一团糟。很遗憾,到那时已经太晚了!
有时候,你可以通过往数据表里增加一个TIME数据列来解决这个问题。但如果你没有保留原始数据来源(比如原始书面记录)的话,已经输入到数据表里的数据行往往很难整理,你将根本无法把同一天进行的多次测试的细节数据行分别与其正确的主控数据行关联在一起。即使你保留了一份原始数据来源,这种整理也会非常麻烦,而且很可能会导致你当初为使用这些数据表而编写的应用程序出问题。总而言之,最好的解决办法是把这类问题向数据表的拥有者解释清楚,并确保自己在开始创建数据表之前已经对有关数据的各项特征了然于心。
有时候,你一开始只掌握了一些不够完整的数据,而这将影响到你对数据类型的选择。比如说,假设你正在收集某些人的出生和死亡日期以进行家谱研究,而你能收集到的资料只包括那些人的出生或死亡年份或者是年份和月份,没有准确的日子。如果选用一个DATE数据列来记录这些信息,你收集到的数据将无法输入——它们都是不完整的日期值,除非你能补足“日子”部分。要想在这种信息不够完整的情况下仍能把已经收集到的数据记录下来,可供选择的最佳方案大概是创建3个数据列来分别记录年、月和日,然后把已经收集到的信息输入相应的数据列,剩下的则填入NULL。另一个办法是坚持使用DATE值,如果日期值不够完整,就把相应的日或者月和日设置为0。这种“模糊”的日期可以用来表示不完整的日期值。
8.数据是否都在某个特定的区间内
在为某个数据列挑选数据类型时,把基本类型(整数、浮点数、字符串)确定下来之后,思考一下取值范围将有助于把选择面逐步缩窄到该基本类型中的某个特定类型上。假设需要存储一些整数值,你应该根据它们的取值范围来决定选用哪一种具体的整数类型。如果它们的取值范围是0到1000,可供选择的整数类型将是从SMALLINT到BIGINT。如果它们的取值上限是200万,SMALLINT就不够用了,可供选择的整数类型将只能是从MEDIUMINT到BIGINT。
当然,你完全可以只挑选一种最“大”的类型来容纳你想保存的数据(例如为上段中的例子选择BIGINT)。但一般应该选择足以满足你使用目的的最小类型。这将使数据表占用的存储空间最小化,随之而来的是更好的性能,这是因为比较短小的数据列往往比那些比较长大的数据列处理得更快。(读取比较短小的数据值所需要的磁盘读写次数更少,让键字缓存容纳更多的键字,加快索引搜索的速度。)
如果无法提前获知自己将要处理的数据的取值范围,就只能靠猜测或是选择BIGINT之类的“大”类型来预防最坏的可能性。如果靠猜测而选用的类型偏小了,事情还有补救:用ALTER TABLE语句及时“加大”的数据列。
有时候,你甚至会发现可以把某个数据列设置得更小。在第1章里,我们为考试成绩项目创建了一个score数据表,它有一个score数据列来记录学生们的考试和测验成绩。为方便当时的讨论,我们在创建该数据列时使用的是INT类型。根据上面的讨论,如果学生考试成绩的取值范围是0到100的话,TINYINT UNSIGNED将是一个更好的选择,因为它的存储空间占用量更少。
数据的取值范围还会影响到你可以为你选用的数据类型搭配使用哪些属性。比如说,如果数据不可能是负数,就可以加上UNSIGNED属性;反之,则不可以。
字符串类型没有像数值型数据列那样的“取值范围”,但它们都有一个长度范围,你所需要的最大长度对你可以选用哪些数据列类型有着决定性影响。如果需要存储的字符串都比256个字符短,可以选用CHAR、VARCHAR或TINYTEXT。如果需要存储更长的字符串,可以选用VARCHAR或某种更长的TEXT类型。
如果某个数据列所容纳的字符串来自一个有限集合,就应该考虑使用ENUM或SET数据类型。它们可能是很好的选择,因为它们在系统内部被表示为数值,对它们的操作以数值方式进行,所以它们的处理效率比其他字符串类型更高。它们往往比其他字符串类型更紧凑,因而更节约空间。还有,如果启用了“严格”SQL模式,还可以防止那些没在合法值清单里的字符串进入数据库。详见3.3节。
在确定你将与之打交道的数据的取值范围时,“总是”和“绝不”是两个最有用的词(例如“总是小于1000”或“绝不是负数”),它们可以帮你更准确地选出最适用的数据类型。但千万注意不要把这两个词用在不恰当的地方。尤其是在你向别人了解数据情况时,如果对方说出了这两个词,就更要注意。在人们说“总是”或“绝不”时,一定要确认他们的话能不能当真。有时候,当人们说他们的数据“总是”具备某种特征时,实际情况却是“差不多总是”。
假设你正在为一些教师设计一个数据表,他们告诉你:“学生们的考试分数总是在0到100之间。”因为这句话,你决定选用TINYINT类型,又因为数据都是非负数,所以你决定再加上UNSIGNED限定符。可你后来发现这些人在把数据录入数据库时有时会用“-1”来表示“学生因病缺考”。糟糕,他们当初可没告诉你还有这个!用NULL来代表这类特殊情况是个可以考虑的补救办法,但万一对方不认可,你将不得不记录一个“-1”,而这意味着你不能使用UNSIGNED数据列。(在遇到这种麻烦的时候,ALTER TABLE语句可以帮上大忙。)
有时候,多问一个简单的问题就可以轻松消除许多未来的烦恼:会不会有例外?只要存在某种例外,哪怕只有一例,都必须把它考虑进来。在与别人探讨数据库的设计问题时,你会发现有许多人有这样一种思维误区:只要例外情况不是经常发生,它们就无足轻重。在设计数据表时候可千万不能这么想。你需要提出的问题不是“例外经常发生吗?”而是“有没有发生过例外?”只要发生过例外,就必须把它们考虑进来。
9.与挑选数据类型有关的文件是互相影响的
千万不要以为与挑选数据类型有关的问题是彼此无关的。以数值类型为例,其取值范围与其存储空间占用量有关:取值范围越大,需要的存储空间就越大,而这又会影响到性能。创建一个AUTO_INCREMENT数据列来容纳唯一化的序列编号也有许多问题需要提前考虑到。这只是个简单的选择,但对数据类型、索引和NULL值的使用情况都会产生影响。
❑ AUTO_INCREMENT是一种数据列属性,最适合用于整数类型。这立刻就把我们的选择余地限制在了从TINYINT到BIGINT的范围内。
❑ AUTO_INCREMENT数据列只能用来生成一个正整数序列,所以应该把它定义为UNSIGNED。
❑ AUTO_INCREMENT数据列必须有索引。不仅如此,为防止该数据列里出现重复的值,它的索引还必须是唯一的,所以应该把该数据列定义为一个PRIMARY KEY或者是一个UNIQUE索引。
❑ AUTO_INCREMENT数据列必须具备NOT NULL属性。(即使你省略了NOT NULL,MySQL也会自动地加上它。)
以上这些意味着不能像下面这样定义一个AUTO_INCREMENT数据列:

mycol arbitrary_type AUTO_INCREMENT
必须像下面这样来定义它:
mycol integer_type UNSIGNED NOT NULL AUTO_INCREMENT, PRIMARY KEY (mycol)
或者像下面这样来定义它:
mycol integer_type UNSIGNED NOT NULL AUTO_INCREMENT , UNIQUE (mycol)

(三)日期和时间类型
1.基本介绍
MySQL的日期/时间数据类型,表中的CC、YY、MM、DD、hh、mm和ss分别代表世纪、年、月、日、时、分、秒。MySQL提供的时间类列类型有:日期与时间(合并表示或分开表示)、时间戳(一种专门用来记载某数据记录最近一次修改时间的类型)。此外,为了方便不需要完整日期而只需要年份数值的场合,MySQL还准备了一个YEAR类型。

数据类型 含义
date 日期值,格式为’ccyy-mm-dd’
time 时间值,格式为’hh:mm:ss’
datetime 日期加时间值,格式为’ccyy-mm-dd hh:mm:ss’
timestamp 时间戳值,格式为’ccyy-mm-dd hh:mm:ss’
year 年份值,格式为ccyy或yy

		      类型 		范围		            	         格式 

日期值 DATE 1000-01-01/9999-12-31 YYYY-MM-DD
时间值或持续时间 TIME ‘-838:59:59’/‘838:59:59’ HH:MM:SS
年份值 YEAR 1901/2155 YYYY
混合日期和时间值 DATETIME 1000-01-01 00:00:00/9999-12-31 23:59:59
YYYY-MM-DD HH:MM:SS
混合日期和时间值 时间戳 TIMESTAMP 1970-01-01 00:00:00/2037 年某时
YYYYMMDD HHMMSS

2.选择日期类型的原则
1.根据实际需要选择能够满足应用的最小存储的日期类型,如果应用只需要记录”年份”,那么用1个直接来存储YEAR类型完全可以满足,而不需要4个字节来存储DATE类型,这样不仅仅能节约存储,更能够提高表的操作效率.
2.如果要记录年月日时分秒,并且记录的年份比较久远,那么最好使用DATETIME,而不要使用TIMESTAMP,因为TIMESTAMP表示的日期范围比DATETIME要短得多.
3.如果记录的日期需要让不同时区的用户使用,那么最好使用TIMESTAMP,因为日期类型中只有它能够和实际时区相对应.

(四)字符串类型
1.基本介绍
字符串可以容纳任何东西,包括图像和声音等各种二进制数据.字符串可以彼此比较,而比较操作又分为区分字母大小写和不区分字母大小写两种情况.此外,你还可以对字符串进行模式匹配。(事实上,MySQL的数值类型也允许进行模式匹配,只不过字符串数据类型上的模式匹配操作更多见一些)

数据类型 含义
char 固定长度的非二进制(字符)字符串
varchar 可变长度的非二进制字符串
binary 固定长度的二进制字符串
varbinary 可变长度的二进制字符串
tinyblob 非常小的blob(二进制大对象)
blob blob
mediumblob 中等大小的blob
longblob 大blob
tinytext 非常小的非二进制字符串
text 小文本字符串
mediumtext 中等大小的非二进制字符串
longtext 大的非二进制字符串
enum 枚举集合,数据列的取值将是这个枚举集合中的某一个元素
set 集合,数据列的取值可以是零或者这个集合中的多个元素

char和varchar类型类似,都是用来存储字符串的,但它们保存和检索的方式不同,char属于固定长度的字符类型,而varchar属于可变长度的字符类型.

2.char和varchar
由于char是固定长度的,所有它在处理速度比varchar快很多,但是缺点是浪费存储空间,程序需要对行尾空格进行处理,对于那些长度变化不大并且对查询速度有较高要求的数据可以考虑使用char类型存储.
另外,随着MySQL版本的不断升级,varchar数据类型的性能也在不断改进并提高,所有在许多的应用中,varchar类型被更多地使用.

在MySQL中,不同的存储引擎对char和varchar的使用原则有所不同:

MyISAM存储引擎:建议使用固定长度的数据列代替可变长度的数据列.

MEMORY存储引擎:目前都使用固定长度的数据行存储,因此无论使用char或者varchar列都没有关系,两者都是作为char类型处理.

InnoDB存储引擎:建议使用varchar类型,对于InnoDB数据表,内部的行存储格式没有区分固定长度和可变长度列(所有数据行都使用指向数据列值的头指针),因此在本质上,使用固定长度的char列不一定比使用可变长度varchar列性能要好,因而,主要的性能因素是数据行使用的存储总量,由于char平均占用的空间多于varchar,因此使用varchar来最小化需要处理的数据行的存储总量和磁盘I/O是比较好的.

3.text与blob
一般在保存少量字符串的使用,我们会选择char或者varchar;而保存较大文本时,通常会选择使用text或者blob,二者之间主要的差别是blob能用来保存二进制数据,比如照片,而text只能保存字符数据,比如一篇文章或者日记,text和blob中又分别包括text,mediumtext,longtext和blob mediumblob,longblob三种不同的类型,它们之间主要的区别是存储文本长度和存储字节不同,用户应该根据实际情况选择能够满足需求的最小存储类型,

1.blob和text值会引起一些性能问题,特别是在执行了大量的删除操作时
http://note.youdao.com/noteshare?id=f9bb8e61f606a41a6864f8e6282ee9eb&sub=D07462E3377A47659E1F46 C1148FF023

  1. 可以使用合成的(Synthetic)索引来提高大文本字段(blob或text)的查询性能.
    简单来说,合成索引就是根据大文本字段的内容建立一个散列值,并把这个值存储在单独的数据列中,接下来就可以通过检索散列值找到数据行了.但是需要注意这种技术只能用于精确匹配的查询(散列值对于类似”<”或”>=”等范围搜索操作符是没有用处的).可以使用MD5()函数生成散列值,也可以使用SHA1()或者CRC32(),或者使用自己的应用程序逻辑来计算散列值,请记住数据性散列值可以很高效地存储,同样,如果散列算法生成的字符串带有尾部空格,就不要使用它们存储在char或者varchar列中,它们会受到尾部空格去除的影响,合成散列索引对那些blob或text数据列特别有用,用散列标识符值查找的速度比搜索blob列本身的速度快很多.
    合成索引使用方法:
    http://note.youdao.com/noteshare?id=7ffd317ed36cb368a6ea573d0f1d7eee&sub=AA1C94A2E3D648EF99C05A5F8D56ACE1

    3.在不必要的时候避免检索大型的blob或text值
    列如:select * 查询就不是很好的想法,除非能够确定作为约束条件的where子句只会找到所需要的数据行,否则,很可能毫无目的地在网络上传输大量的值.这也是blob或text标识符信息存储在合成的索引列中对用户有所帮助的例子.用户可以搜索索引列,决定需要的哪些数据行,然后从符合条件的数据行中检索blob或text值.

4.把blob或text列分离到单独的表中.
在某些环境中,如果把这些数据列已到到第二张数据表中,可以把原数据表中的数据列转换为固定长度的数据行格式,那么它就是有意义的.这会减少主表中的碎片,可以得到固定长度数据行的性能优势.它还可以使主数据表在运行select*查询的时候不会通过网络传输大量的blob或text值.

(五)空间数据类型
1.基本介绍
MySQL的空间数据类型,这里展现了各种几何值和位置值.
数据类型 含义
geometry 任何类型的坐标值
pcint 点(一对x,y坐标值)
linestring 曲线(一个或多个point值)
pclygon 多边形
geometrycollection geometry值的集合
multilinestring linestring值的集合
multipoint point值的集合
multipolygon polygon值的集合

并发
(一)并发控制
1.锁粒度
一种提高共享资源并发性的方式就是让锁定对象更有选择性。尽量只锁定需要修改的部分数据,而不是所有的资源。更理想的方式是,只对会修改的数据片进行精确的锁定。任何时候,在给定的资源上,锁定的数据量越少,则系统的并发程度越高,只要相互之间不发生冲突即可。
问题是加锁也需要消耗资源。锁的各种操作,包括获得锁、检查锁是否已经解除、释放锁等,都会增加系统的开销。如果系统花费大量的时间来管理锁,而不是存取数据,那么系统的性能可能会因此受到影响。
所谓的锁策略,就是在锁的开销和数据的安全性之间寻求平衡,这种平衡当然也会影响到性能。大多数商业数据库系统没有提供更多的选择,一般都是在表上施加行级锁(row-level lock),并以各种复杂的方式来实现,以便在锁比较多的情况下尽可能地提供更好的性能。
而MySQL则提供了多种选择。每种MySQL存储引擎都可以实现自己的锁策略和锁粒度。在存储引擎的设计中,锁管理是个非常重要的决定。将锁粒度固定在某个级别,可以为某些特定的应用场景提供更好的性能,但同时却会失去对另外一些应用场景的良好支持。好在MySQL支持多个存储引擎的架构,所以不需要单一的通用解决方案。下面将介绍两种最重要的锁策略。
2.表锁
表锁是MySQL中最基本的锁策略,并且是开销最小的策略。表锁非常类似于前文描述的邮箱加锁机制:它会锁定整张表。一个用户在对表进行写操作(插入、删除、更新等)前,需要先获得写锁,这会阻塞其他用户对该表的所有读写操作。只有没有写锁时,其他读取的用户才能获得读锁,读锁之间是不相互阻塞的。
在特定的场景中,表锁也可能有良好的性能。例如,READ LOCAL表锁支持某些类型的并发写操作。另外,写锁也比读锁有更高的优先级,因此一个写锁请求可能会被插入到读锁队列的前面(写锁可以插入到锁队列中读锁的前面,反之读锁则不能插入到写锁的前面)。
尽管存储引擎可以管理自己的锁,MySQL本身还是会使用各种有效的表锁来实现不同的目的。例如,服务器会为诸如ALTER TABLE之类的语句使用表锁,而忽略存储引擎的锁机制。
3.行级锁
行级锁可以最大程度地支持并发处理(同时也带来了最大的锁开销)。众所周知,在InnoDB和XtraDB,以及其他一些存储引擎中实现了行级锁。行级锁只在存储引擎层实现,而MySQL服务器层(如有必要,请回顾前文的逻辑架构图)没有实现。服务器层完全不了解存储引擎中的锁实现。在本章的后续内容以及全书中,所有的存储引擎都以自己的方式显现了锁机制。

(二)锁机制

1.锁基本简介
锁是用于管理不同事务对共享资源的并发访问

锁是数据库服务器用来控制数据资源被并行使用的一种机制,当数据库的一些内容被锁定时,任何打算修改(或者可能是读取)这个数据的用户必须等到锁被释放,大部分数据库使用下面两种锁策略之一:
1.数据库的写操作必须向服务器申请并获得写锁才能修改数据,而读操作必须申请和获得读锁才能查询数据,多用户可以同时读取数据,而一个表(或其他部分)一次只能分配一个写锁,并且拒绝读请求直到写锁释放.
2.数据库的写操作必须向服务器申请并获得写锁才能修改数据,而读操作不需要任何类型的锁就可以查询数据,另一方面,服务器要保证从查询开始到结束读操作看到一个一致的数据视图(即使其他用户修改,数据看上去也要相同),这个方法被称为版本控制.

这两种方法各有利弊,第一种方法有较多的并行读请求和写请求时等待时间过长;如果在修改数据时存在长期运行的查询,则第二种方法也是有问题的.

2.锁定粒度(表锁,页锁,行锁)

相对其它数据库而言,MySQL的锁机制比较简单,最显著的特点是不同的存储引擎支持不同的锁机制.比如MyISAM和MEMORY存储引擎采用的是表级锁.BDB存储引擎采(BDB已经被InnoDB取代.)用的是页面锁,但是也支持表级锁,InnoDB存储引擎即支持行级锁,也支持表级锁,但默认情况下使用行级锁.

决定如何锁定一个资源时也可以采用一不同的策略,服务器可能在3个不同级别之一应用锁,或者称作粒度:
表锁:
阻止多用户同时修改同一个表的数据
开销小,加锁快,不会出现死锁,锁定力度大,发生锁冲突的概率最高,并发度最低
行锁
阻止多用户同时修改某表中的同一行数据
开销大,加锁慢,会出现死锁,锁定粒度最小,发生锁冲突的概率最低,并发度也最高.
页锁
阻止多用户同时修改某表中的同一页(一页通常是一段2~16kb的内存空间)的数据.
开销和加锁时间界于表锁和行锁之间,会出现死锁,锁定粒度界于表锁和行锁之间,并发度一般.

从上述特点来说,很难说哪种锁更好,只能就具体应用的特点来说哪种锁更合适
仅从锁的角度来说:
表级锁更适合以查询为主,只有少量按索引条件更新数据的应用,如web应用;
而行级锁则更适合于有大量按索引条件并发更新少量不同数据,同时又有并发查询的应用,如一些在线事务处理(OLTP)系统.

表锁与行锁的区别:

表级锁和行级锁对比:

表级锁: MySQL中锁定 粒度最大 的一种锁,对当前操作的整张表加锁,实现简单,资源消耗也比较少,加锁快,不会出现死锁。其锁定粒度最大,触发锁冲突的概率最高,并发度最低,MyISAM和 InnoDB引擎都支持表级锁。

行级锁: MySQL中锁定 粒度最小 的一种锁,只针对当前操作的行进行加锁。行级锁能大大减少数据库操作的冲突。其加锁粒度最小,并发度高,但加锁的开销也最大,加锁慢,会出现死锁。

锁定粒度: 表锁>行锁
加锁效率: 表锁>行锁
冲突概率: 表锁>行锁
并发性能: 表锁<行锁

3.MyISAM表锁
MyISAM存储引擎只支持表锁,这也是MySQL开始几个版本中唯一支持的锁类型,随着应用对事物完整性和并发性要求的不断提高,MySQL才开始开发基于事物的存储引擎,后来慢慢出现了支持页锁的BDB存储引擎和支持行锁的InnoDB存储引擎,但是MyISAM的表锁依然是使用最为广泛的锁类型,
MySQL表锁有两种模式:表共享读锁(Table Read Lock) 和表独占写锁(Table Write Lock) .

对MyISAM表的读操作,不会阻塞其他用户对同一表的读请求,但会阻塞对同一表的写请求,对MyISAM表的写操作,则会阻塞其他用户对同一个表的读和写操作,MyISAM的表读操作与写操作之间,以及写操作之间是串行的,
当一个线程获得对一个表的写锁后,只有持有锁的线程可以对表进行更新操作,其他线程的读写操作都会等待,直到锁被释放为止.

4.InnoDB锁问题
InnoDB与MyISAM的最大不同的两点:
1.支持事务(Transaction)
2.采用了行级锁

行级锁与表级锁本来就有许多不同之处.事务的引入也带来了新的问题:

更新丢失:
当两个或者多个事务选择同一行,然后基于最初选定的值更新该行时,由于每个事物都不知道其他事务的存在,就会发生丢失更新的问题–最后的更新覆盖了其他事务所做的更新,列如,两个编辑人员制作了同一文档的电子副本,每个编辑人员独立地更改其副本,然后保存更改后的副本,这样就覆盖了原始文档,最后保存其更改副本的编辑人员覆盖另一个编辑人员所做的更改.如果在一个编辑人员完成并提交事务之前,另一个编辑人员不能访问同一文件,则可避免此问题.
脏读

一个事务正对一条记录进行修改,在这个事务完成并提交前,这条记录的数据就处于一个不一致的状态,这时,另外一个事务也来读取同一条记录,如果不加以控制,第二个事务读取了这些’脏’数据,并据此做进一步的处理,就会产生未提交的数据依赖关系,这种现象被形象的叫做’脏读’

不可重复读
一个事务在读取某些数据后的某个时间,再次读取以前读过的数据,却发现其读出的数据已经发生了改变或某些记录已经被删除了!这种现象叫做’不可重复读’
幻读
一个事务按相同的查询条件重新读取以前检索过的数据,却发现其它事务插入了满足其查询条件新数据,这种现象就称为’幻读’.

锁机制与InnoDB锁算法
MyISAM和InnoDB存储引擎使用的锁:

MyISAM采用表级锁(table-level locking),并且只支持表锁。

InnoDB支持行级锁(row-level locking)和表级锁,默认为行级锁

InnoDB存储引擎的锁的算法有三种:

Record lock:单个行记录上的锁

Gap lock:间隙锁,锁定一个范围,不包括记录本身

Next-key lock:record+gap 锁定一个范围,包含记录本身

InnoDB存储引擎支持行锁和表锁(另类的表锁,把所有的行都锁住,就相当于成了表锁)

InnoDB锁类型
共享锁(行锁) Shared Locks
排它锁(行锁) Exclusive Locks
意向锁共享锁(表锁) Intention Shared Locks
意向锁排它锁(表锁) Intention Exclusive Locks
自增锁 AUTO - INC Locks

行锁的算法:
记录锁 Record Locks
间隙锁 Gap Locks
临键锁 Next-key Locks
读写锁(共享锁和排它锁)
从邮箱中读取数据没有这样的麻烦,即使同一时刻多个用户并发读取也不会有什么问题。因为读取不会修改数据,所以不会出错。但如果某个客户正在读取邮箱,同时另外一个用户试图删除编号为25的邮件,会产生什么结果?结论是不确定,读的客户可能会报错退出,也可能读取到不一致的邮箱数据。所以,为安全起见,即使是读取邮箱也需要特别注意。
如果把上述的邮箱当成数据库中的一张表,把邮件当成表中的一行记录,就很容易看出,同样的问题依然存在。从很多方面来说,邮箱就是一张简单的数据库表。修改数据库表中的记录,和删除或者修改邮箱中的邮件信息,十分类似。
解决这类经典问题的方法就是并发控制,其实非常简单。在处理并发读或者写时,可以通过实现一个由两种类型的锁组成的锁系统来解决问题。这两种类型的锁通常被称为共享锁(shared lock)和排他锁(exclusive lock),也叫读锁(read lock)和写锁(write lock)。

这里先不讨论锁的具体实现,描述一下锁的概念如下:

1.读锁(共享锁)
读锁是共享的,或者说是相互不阻塞的。多个客户在同一时刻可以同时读取同一个资源,而互不干扰,但是只能读不能修改.

加锁释放锁方式:
select * from users where XXX =XXX LOCK IN SHARE MODE; --加读锁

此时别的客户如果想修改被添加读锁的资源的话,会被阻塞在那里

commit/rollback – 释放读锁

释放读锁之后别的客户就可以修改资源了.原先被阻塞的 修改行为 会自动执行完毕.

2.写锁(排它锁)
写锁检查x锁,排它锁不能与其它锁并存,如一个事务获取了一个数据行的排它锁,其它事务就不能再获取该行的锁(共享锁,排它锁),但是可以进行select查询只有该获取了排它锁的事务是可以对数据行进行读取和修改(其它的事务要读取可以来自于快照).

加锁释放锁方式:

delete/update/insert 默认加上X锁

此时别的连接无法拿到这行的共享锁和排它锁.,但是可以进行select查询

select * from table where … for update --手动加的.

commit/ rollback --释放锁

释放锁之后,别的连接就可以拿到这行的共享锁和排它锁了.

在实际的数据库系统中,每时每刻都在发生锁定,当某个用户在修改某一部分数据时,MySQL会通过锁定防止其他用户读取同一数据。大多数时候,MySQL锁的内部管理都是透明的。

InnoDB的行锁到底锁了什么
InnoDB的行锁是通过给索引上的索引项加锁来实现的.

只有通过索引条件进行数据检索(添加了索引的列,比如id),InnoDB才使用行级锁,否则,InnoDB将使用表锁(索引索引的所有的记录,这就是另类的表锁)

表锁:lock tables xxx read/write ;

演示:

id是主键自增索引 name 是唯一索引

第一次演示
A用户操作:
update users set lastUpdate =Now() where phoneNum =’13666666666’; – 执行成功了.此时这个语句拿到了写锁(update自动获取写锁)

B用户操作:
update users set lastUpdate =Now() where id = 2 ; --此时阻塞住了,A用户操作的是id为1 ,而我去修改id为2的记录却被阻塞了
停掉上一条sql语句
update users set lastUpdate = Now() where id = 1 ; --此时又被阻塞住了
停掉上一条sql语句
update users set lastUpdate = Now() where id = 3 ; --此时又被阻塞住了

结论: B用户的所有操作都被阻塞住了

第二次演示
A用户操作:

update users set lastUpdate = Now() where id = 1 ; – 已经加写锁了,还没提交

B用户操作:

update users set lastUpdate =Now() where id = 2 ; – 操作成功了,注意 id 和A用户操作的id是不一样的

update users set lastUpdate = Now() where id = 1 ; --此时又阻塞住了(修改的id和用户A的id是一样的)

停掉上一条sql语句

update users set lastUpdate = Now() where id = 3 ; --操作又成功了,注意 id 和A用户操作的id是不一样的

第三次演示
A用户操作

update users set lastUpdate = now() where ‘name’ =’seven’ --已经加锁了
此时没有提交事务

B用户操作

update users set lastUpdate = now() where ‘name’ = ‘seven’ --锁住了 ,注意名字和A用户操作是一样的

停掉上一条sql语句

update users set lastUpdate = now() where id = 1 ; --锁住了,因为 id为1的那条数据 name列就是 seven

停掉上一条sql语句

update users set lastUpdate = now() where ‘name’ = ‘qingshan’ --成功了 因为 name列数据和A用户操作的不一样

update users set lastUpdate = now() where id = 2 ; – 成功了,因为 id为2的那行name属性不是’seven’

意向锁共享锁(IS)和意向锁排它锁(IX)

意向锁(IS,IX)是InnoDB数据操作之前自动加的,不需要用户干预.

意向锁共享锁(IS)
表示事物准备给数据行加入共享锁,即一个数据行加共享锁前必须先取得该表的IS锁,意向共享锁之间是可以相互兼容的,可以理解它是Java代码里面的flag标记,根据标记来判断是否需要上锁

意向锁排它锁(IX)
表示事物准备给数据行加入排它锁,即一个数据行加排它锁前必须先取得该表的IX锁,意向排它锁之间是可以相互兼容的

意义:
当事物想去进行锁表时,可以先判断意向锁是否存在,存在时则可快速返回该表能不能启动表锁.
自增锁

针对自增列自增长的一个特殊的表级别锁

默认取值为1 ,代表连续,事务未提交ID永久丢失. (不要轻易的修改这个配置,没啥意义)

(三)事务

1.事务原理
事务操作原理:事务开启之后,所有的操作都会临时保存到事务日志,事务日志只有在得到commit命令才会同步到数据表,其他任何情况都会被清空(rollback,断电,断开连接)

客户端连接认证服务端,验证身份成功后建立连接,同时为当前用户当次连接开启一个临时的事务日志.
不开事务情况:当用户执行sql写操作的时候,服务器会接收sql语句并且执行sql语句,结果直接写入到数据表里面,然后直接同步到数据库里面
开启事务: 系统接收到 start transaction 指令之后,会改变原来的操作机制,后续的所有操作(写操作),会先写入到临时日志文件,如果有SQL操作,服务端先接收sql语句,执行sql语句,结果先写入到临时日志文件,此时临时日志文件会保存sql操作结果,假设有查询操作结果,会从数据表查询,经过临时日志文件结果进行加工返回.
事务结束: commit 或者rollback,都会清空临时的日志文件,commit会同步到数据表,rollback是直接清空

所以,只有服务端接收到commit命令之后才会将临时日志文件的sql操作结果保存到数据库中.如果断开连接或者是rollback了,sql操作结果不会保存到数据库里面,临时文件会自动清空.
2.事务四大特性
ACID
Atomic 原子性
事务的整个操作是一个整体,不可分割,要么全部成功要么全部失败
Consistency 一致性
执行事务前后,数据保持一致,多个事务对同一个数据读取的结果是相同的,一致性关注的是数据的可见性.保证在一个事务中的多次操作的数据中间状态是对其它事务不可见的,因为是中间状态,是一个过渡状态,与事务的开始和结束状态是不一致的.
举个例子,张三给李四转账100元。事务要做的是从张三账户上减掉100元,李四账户上加上100元。一致性的含义是其他事务要么看到张三还没有给李四转账的状态,要么张三已经成功转账给李四的状态,而对于张三少了100元,李四还没加上100元这个中间状态是不可见的。

也可以理解事务不应该把你的数据库弄的一团糟.
Isolation 隔离性
并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的;
Durability 持久性
一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。

锁机制: innodb 默认是行锁,但是如果在事务操作的过程中,没有使用到索引,那么系统会自动全表检索数据,自动升级为表锁
行锁: 只有当前行被锁住,别的用户不能操作
表锁: 整张表被锁住,别的用户都不能操作

3.什么是事务
事务是确保呈批的sql操作要么完全执行,要么完全不执行,来维护数据库的完整性.

事务最经典也经常被拿出来说例子就是转账了。假如小明要给小红转账1000元,这个转账会涉及到两个关键操作就是:将小明的余额减少1000元,将小红的余额增加1000元。万一在这两个操作之间突然出现错误比如银行系统崩溃,导致小明余额减少而小红的余额没有增加,这样就不对了。事务就是保证这两个关键操作要么都成功,要么都要失败。
4.事务处理
事务(transaction)是作为一个不可分割的逻辑单元而被执行的一组SQL语句,如有必要,它们的执行效果可以被撤销。并非所有的语句每次都能执行成功,有些语句还会对数据产生永久性的影响。事务处理是通过提交(commit)和回滚(rollback)功能实现的。如果某个事务里的所有语句都执行成功了,提交该事务将把那些语句的执行效果永久性地记录到数据库里。如果在事务过程中发生错误,回滚该事务将把发生错误之前已经执行的语句全部取消,数据库将恢复到开始这次事务之前的状态。
提交和回滚机制使我们能够确保尚未全部完成的操作不会影响到数据库,不会让新旧数据混杂在一起让数据库呈不稳定状态。财务转账是一个典型的事务处理例子,即把钱从一个账户转到另一个账户。假设Bill给Bob开了一张100美元的支票,Bill拿着这张支票去取钱。Bill的账户应该减少100美元,Bob的账户应该增加100美元:
update account set balance = balance - 100 where name = ‘bill’;
update account set balance = balance + 100 where name = ‘bob’;
可是,万一银行的计算机系统在这两条语句正在执行时发生了崩溃,整个操作将不完整。根据先执行的是哪一条语句,Bill的账户可能少了100美元而Bob的账户金额没增加,或者Bob的账户多了100美元而Bill的账户金额没减少。这两种情况都不正确。如果没有使用事务机制,你将不得不以手动方式分析你的日志以查明崩溃发生时都有哪些操作正在进行,应该以及如何撤销或继续完成哪些操作,等等。事务机制提供的回滚操作可以让你正确地处理好这些问题,把发生错误之前已经执行完的语句的效果撤销掉。(作为善后工作的一部分,你还需要确定哪些事务需要再次执行,但至少你不必担心那些未能全部完成的事务会损害数据库的完整性了。)
事务的另一种用途是确保某个操作所涉及的数据行在你正在使用它们时不会被其他客户修改。
MySQL在执行每一条SQL语句时都会自动地对该语句所涉及的资源进行锁定以避免各语句之间相互干扰。但这仍不足以保证每一个数据库操作总是能够得到预期的结果。要知道,有些数据库操作需要多条语句才能完成,而在此期间,不同的客户就有可能相互干扰。通过把多条语句定义为一个执行单元,事务机制可以防止在多客户环境里可能发生的并发问题。
5.利用事务来保证语句的安全执行
要想使用事务,就必须选用一种支持事务处理的存储引擎,如InnoDB或Falcon。MyISAM和MEMORY等其他存储引擎帮不上这个忙。如果你拿不准MySQL服务器是否支持任何事务存储引擎,请参见2.6.1节中的第1小节。
在默认的情况下,MySQL从自动提交(autocommit)模式运行,这种模式会在每条语句执行完毕后把它作出的修改立刻提交给数据库并使之永久化。事实上,这相当于把每一条语句都隐含地当做一个事务来执行。如果你想明确地执行事务,需要禁用自动提交模式并告诉MySQL你想让它在何时提交或回滚有关的修改。
执行事务的常用办法是发出一条START TRANSACTION(或BEGIN)语句挂起自动提交模式,然后执行构成本次事务的各条语句,最后用一条COMMIT语句结束事务并把它们作出的修改永久性地记入数据库。万一在事务过程中发生错误,用一条ROLLBACK语句撤销事务并把数据库恢复到事务开始之前的状态。START TRANSACTION语句“挂起”自动提交模式的含义是:在事务被提交或回滚之后,该模式将恢复到开始本次事务的START TRANSACTION语句被执行之前的状态。(如果自动提交模式原来是激活的,结束事务将让你回到自动提交模式;如果它原来是禁用的,结束当前事务将开始下一个事务。)
下面的例子演示了这个套路。首先,创建一个数据表供演示使用:
create table t (name char(20),unique(name) ) engine = InnoDB ;
这个语句将创建一个InnoDB数据表,但你完全可以根据个人喜好选用一种不同的事务存储引擎.
接下来,用start transaction 语句开始一次事务,往数据表里添加一些数据行,提交本次事务,然后看看数据表变成了什么样子:
start transaction;
insert into t set name = ‘William’ ;
insert into t set name = ‘wallace’ ;
commit

你可以看到一些数据行已被记录到了数据表里。如果你另外启动一个mysql程序的实例并在插入之后、但提交之前选取数据表t的内容的话,你将看不到那些数据行。在第一个mysql进程发出COMMIT语句之前,那些数据行对第二个mysql进程来说是不可见的。

如果子事务过程发生了一个错误,你可以用rollback语句把它试试,仍以数据表t为例:
start transaction;
insert into t set name = ‘gromit’;
insert into t set name = ‘wallace’ ;
rollback;

第二条INSERT语句试图把一个其name值与一个现有的数据行重复的数据行插入到数据表里。因为name数据列有一个UNIQUE索引,所以这条语句将执行失败。在发出ROLLBACK语句之后,这个数据表只包含在这次失败事务之前被插入的两个数据行。准确地说,在这次事务里,在发生错误之前已经执行的INSERT语句被撤销了,它们的影响没有被记录到数据表里。
如果在事务过程中发出一条START TRANSACTION语句,它将隐含地提交当前事务,然后开始一个新的事务.

6.并发事务的隔离性
因为MySQL是一个多用户数据库系统,所以不同的客户可能会在同一时间试图访问同一个数据表。诸如MyISAM之类的存储引擎使用了数据表级的锁定机制来保证不同的客户不能同时修改同一个数据表,但这种做法在更新量比较大的系统上会导致并发性能的下降。InnoDB存储引擎采用了另一种策略,它使用了数据行级的锁定机制为客户对数据表的访问提供了更细致的控制:在某个客户修改某个数据行的同时,另一个客户可以读取和修改同一个数据表里的另一个数据行。如果有两个客户想同时修改某个数据行,先锁定该数据行的那个客户将可以先修改它。这比数据表级的锁定机制提供了更好的并发性能。不过,这里还有一个问题:一个客户的事务在何时才能看到另一个客户的事务作出的修改。
InnoDB存储引擎实现的事务隔离级别机制能够让客户控制他们想看到其他事务作的哪些修改。它提供了多种不同的隔离级别以允许或预防在多个事务同时运行时可能发生的各种各样的问题,如下所示。

脏读(Dirty read):
指某个事务所作出的修改在它尚未被提交时就可以被其他事务看到。其他事务会认为数据行已经被修改了,但对数据行作出修改的那个事务还有可能会被回滚,这将导致数据库里的数据发生混乱。
丢失修改(Lost to modify)
指在一个事务读取一个数据时,另外一个事务也访问了该数据,那么在第一个事务中修改了这个数据后,第二个事务也修改了这个数据。这样第一个事务内的修改结果就被丢失,因此称为丢失修改。 例如:事务1读取某表中的数据A=20,事务2也读取A=20,事务1修改A=A-1,事务2也修改A=A-1,最终结果A=19,事务1的修改被丢失。
不可重复读(Unrepeatableread)
指同一个事务使用同一条SELECT语句每次读取到的结果不一样。比如说,如果有一个事务执行了两次同一个SELECT语句,但另一个事务在这条SELECT语句的两次执行之间修改了一些数据行,就会发生这种问题。

幻读(Phantom read)
指某个事务突然看到了一个它以前没有见过数据行。比如说,如果某个事务刚执行完一条SELECT语句就有另一个事务插入了一个新数据行,前一个事务再执行同一条SELECT语句时就可能多看到一个新的数据行,那就是一个幻影数据行。

InnoDB不会存在幻读,它是通过临界锁mvcc两种方案解决的

不可重复度和幻读区别

不可重复读的重点是修改,幻读的重点在于新增或者删除。

例1(同样的条件, 你读取过的数据, 再次读取出来发现值不一样了 ):事务1中的A先生读取自己的工资为 1000的操作还没完成,事务2中的B先生就修改了A的工资为2000,导致A再读自己的工资时工资变为 2000;这就是不可重复读。

例2(同样的条件, 第1次和第2次读出来的记录数不一样 ):假某工资单表中工资大于3000的有4人,事务1读取了所有工资大于3000的人,共查到4条记录,这时事务2 又插入了一条工资大于3000的记录,事务1再次读取时查到的记录就变为了5条,这样就导致了幻读。

7.事务隔离级别

READ-UNCOMMITTED(读取未提交)
最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
事务可以读取未提交的数据,但这也被称为脏读,这个级别会导致很多问题,从性能上来说,READ UNCOMMITTED 不会比其他的级别好太多,但缺缺乏其它级别的很多少出,除非真的有非常必要的理由,在实际应用中一般很少使用。

READ-COMMITTED(读取已提交)
允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
REPEATABLE-READ(可重复读)
对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生
SERIALIZABLE(可串行化):
这个隔离级别与REPEATABLE READ很相似,但对事务的隔离更彻底:某个事务正在查看的数据行不允许其他事务修改,直到该事务完成为止。换句话说,如果某个事务正在读取某些数据行,在它完成之前,其他事务将无法对那些数据行修改。简单来说,SERIALIZABLE会在读取的每一行数据上都加锁,所以可能导致大量的超时和锁争用的问题。实际应用中也很少用到这个隔离级别,只有在非常需要确保数据的一致性而且可以接受没有并发的情况下,才考虑采用该级别。
该级别可以防止脏读、不可重复读以及幻读

隔离级别允许的问题
隔离级别 读数据的一致性 脏读 不可重复读 幻影读
未提交读READ-UNCOMMITTED 最低级别,只能保证不读取物理上损坏的数据 是 是 是
已提交读READ-COMMITTED 语句级 否 是 是
可重复读REPEATABLE-READ 事务级 否 否 是
可序列化SERIALIZABLE 最高级别,事务级 否 否 否

InnoDB存储引擎默认使用的隔离级别是REPEATABLE READ(可重复读)。这可以通过在启动服务器时使用–transaction-isolation选项或在服务器运行时使用SET TRANSACTION语句来改变。该语句有3种形式:
set global transaction isolation level level ;
set session transaction isolation level level ;
set transaction isolation level level ;

这里需要注意的是:与 SQL 标准不同的地方在于 InnoDB 存储引擎在 REPEATABLE-READ(可重读)事务隔离级别下使用的是Next-Key Lock 锁算法,因此可以避免幻读的产生,这与其他数据库系统(如 SQL Server)是不同的。所以说InnoDB 存储引擎的默认支持的隔离级别是 REPEATABLE-READ(可重读) 已经可以完全保证事务的隔离性要求,即达到了 SQL标准的SERIALIZABLE(可串行化)隔离级别。

因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是READ-COMMITTED(读取提交内容):,但是你要知道的是InnoDB 存储引擎默认使用 REPEATABLE-READ(可重读)并不会有任何性能损失。

InnoDB 存储引擎在 分布式事务 的情况下一般会用到SERIALIZABLE(可串行化)隔离级别。

SUPER权限的客户可以使用SET TRANSACTION语句改变全局隔离级别的设置,该设置将作用于此后连接到服务器的任何客户。此外,任何客户都可以修改它自己的事务隔离级别,用SET SESSION TRANSACTION语句做出的修改将作用于在与服务器的本次会话里后续的所有事务,用SET TRANSACTION语句做出的修改只作用于下一个事务。客户在修改它自己的隔离级别时不需要任何特殊的权限。
本节里的绝大多数信息也适用于Falcon存储引擎。Falcon和InnoDB存储引擎在这方面的主要区别是:Falcon不支持READ UNCOMMITTED隔离级别,它目前也不支持SERIALIZABLE隔离级别(但Falcon开发团队正在为此而努力着)。

8.死锁

死锁是指两个或者多个事务在同一资源上相互占用,并请求锁定对方占用的资源,从而导致恶性循环的现象。当多个事务试图以不同的顺序锁定资源时,就可能会产生死锁。多个事务同时锁定同一个资源时,也会产生死锁。例如,设想下面两个事务同时处理StockPrice表:

事务1

START TRANSACTION;
UPDATE StockPrice SET close = 45.50 WHERE stock_id = 4 and date = '2002-05-01';
UPDATE StockPrice SET close = 19.80 WHERE stock_id = 3 and date = '2002-05-02';
COMMIT;

事务2

START TRANSACTION;
UPDATE StockPrice SET high = 20.12 WHERE stock_id = 3 and date = '2002-05-02';
UPDATE StockPrice SET high = 47.20 WHERE stock_id = 4 and date = '2002-05-01';
COMMIT;

如果凑巧,两个事务都执行了第一条UPDATE语句,更新了一行数据,同时也锁定了该行数据,接着每个事务都尝试去执行第二条UPDATE语句,却发现该行已经被对方锁定,然后两个事务都等待对方释放锁,同时又持有对方需要的锁,则陷入死循环。除非有外部因素介入才可能解除死锁。

为了解决这种问题,数据库系统实现了各种死锁检测和死锁超时机制。越复杂的系统,比如InnoDB存储引擎,越能检测到死锁的循环依赖,并立即返回一个错误。这种解决方式很有效,否则死锁会导致出现非常慢的查询。还有一种解决方式,就是当查询的时间达到锁等待超时的设定后放弃锁请求,这种方式通常来说不太好。InnoDB目前处理死锁的方法是,将持有最少行级排他锁的事务进行回滚(这是相对比较简单的死锁回滚算法)。

锁的行为和顺序是和存储引擎相关的。以同样的顺序执行语句,有些存储引擎会产生死锁,有些则不会。死锁的产生有双重原因:有些是因为真正的数据冲突,这种情况通常很难避免,但有些则完全是由于存储引擎的实现方式导致的。

死锁发生以后,只有部分或者完全回滚其中一个事务,才能打破死锁。对于事务型的系统,这是无法避免的,所以应用程序在设计时必须考虑如何处理死锁。大多数情况下只需要重新执行因死锁回滚的事务即可。

9.如何避免死锁

1.类似的业务逻辑以固定的顺序访问表和行(基本上是可以解决大部分的问题,比如转账,所有的功能接口都是先减钱再加钱)
2.大事务拆小,大事务更倾向于死锁,如果业务允许,将大事务拆小
3.在同一个事务中,尽可能做到一次锁定所需要的所有资源,减少死锁概率
4.降低隔离级别,如果业务允许,将隔离级别调低也是较好的选择(有点扯淡,但是也管用.)
5.为表添加合理的索引,可以看到如果不走索引将会为表的每一行记录添加上锁(或者说是表锁)
不加索引的事务语句拿到的锁都是表锁,表锁级别大,死锁概率就高了.

10.MVCC多版本并发控制
Multiversion concurrency control (多版本并发控制)

普通话解释
并发访问(读或写)数据库时,对正在事务内处理的数据做多版本的管理,以达到用来避免写操作的堵塞为目的,从而引发读操作的并发问题.

实现方式

创建InnoDB表之后它会默认的给你每行加隐藏的列:

DB_TRX_ID 数据行的版本号,是记录插入数据库的版本号
DB_ROLL_PT 删除版本号,

插入数据

假设系统的全局事务id从1开始

begin: – 拿到系统的事务的ID=1;

insert into teacher(name,age) value (‘seven’,18)
insert into teacher(name,age) value (‘qingshan,19)

commit;

结果:

数据行的版本号就是事务id

删除数据

数据的删除
假设系统的全局事务id目前到了22

begin; – 拿到系统的事务id = 22

delete teacher where id = 2 ;

commit ;

它会给删除的版本号记录成当前事务的id号

修改数据

修改操作
假设系统的全局事务id号目前到了33

begin ; – 拿到系统的事务id = 33
update teacher set age = 19 where id = 1 ;
commit ;

修改操作是先做命中的数据行,进行copy,将原来的行的数据的删除版本号的值设置为当前事务id(33)

select操作

查询操作

假设系统的全局事务id号目前到了44

begin ; – 拿到系统的事务id = 44 ;
select * from users ;
commit ;

数据库查询规则:
1.查找数据行版本早于当前事务版本的数据行(也就是行的系统版本号小于或者等于事务系统的版本号),这样可以确保事务读取的行,要么是在事务开始前已经存在的,要么是事务自身插入或者修改过的
2.查找删除版本号要么为null,要么大于当前事务版本号的记录,确保取出来的行记录在事务开启之前没有被删除.

11.事务回滚点
在某个成功的操作完成之后,后续的操作有可能成功有可能失败,但是不管成功还是失败,前面操作都已经成功了,可以在当前成功的位置,设一个点,可以供后续失败操作返回到该位置,而不是返回所有操作,这个点称之为回滚点.回滚点只有在步骤很多的时候才会使用到.
设置回滚点语法: savepoint 回滚点名字;
回到回滚点语法: rollback to 回滚点名字;

回滚点操作:
–开启事务
start transaction;
– 事务处理1 比如 张三加钱…
sql语句: updateXXXXX …
–设置回滚点
savepoint sp1 ;

– 银行扣税 (sql语句是错的)
sql语句: update XXXXXX …
–回滚到回滚点
rollback to sp1;
– 继续操作 银行扣税(sql语句是对的)
sql语句: update XXXXXX …

– 查询结果
select XXXXXXXXXX
– 提交
commit ;

12.事务数据表和非事务数据表可以混用么
在某次事务中混合使用事务数据表和非事务数据表是允许的,但最终的结果不一定符合你的期望。对非事务数据表进行操作的语句总是立刻生效,即便是自动提交模式处于禁用状态也是如此。事实上,非事务数据表永远待在自动提交模式下,每条语句都会在执行完毕后立刻提交。因此,如果你在一个事务里修改了一个非事务数据表,那么这个修改将无法撤销.

13.多版本并发控制
MySQL的大多数事务型存储引擎实现的都不是简单的行级锁。基于提升并发性能的考虑,它们一般都同时实现了多版本并发控制(MVCC)。不仅是MySQL,包括Oracle、PostgreSQL等其他数据库系统也都实现了MVCC,但各自的实现机制不尽相同,因为MVCC没有一个统一的实现标准。
可以认为MVCC是行级锁的一个变种,但是它在很多情况下避免了加锁操作,因此开销更低。虽然实现机制有所不同,但大都实现了非阻塞的读操作,写操作也只锁定必要的行。
MVCC的实现,是通过保存数据在某个时间点的快照来实现的。也就是说,不管需要执行多长时间,每个事务看到的数据都是一致的。根据事务开始的时间不同,每个事务对同一张表,同一时刻看到的数据可能是不一样的。如果之前没有这方面的概念,这句话听起来就有点迷惑。熟悉了以后会发现,这句话其实还是很容易理解的。
前面说到不同存储引擎的MVCC实现是不同的,典型的有乐观(optimistic)并发控制和悲观(pessimistic)并发控制。下面我们通过InnoDB的简化版行为来说明MVCC是如何工作的。
InnoDB的MVCC,是通过在每行记录后面保存两个隐藏的列来实现的。这两个列,一个保存了行的创建时间,一个保存行的过期时间(或删除时间)。当然存储的并不是实际的时间值,而是系统版本号(system version number)。每开始一个新的事务,系统版本号都会自动递增。事务开始时刻的系统版本号会作为事务的版本号,用来和查询到的每行记录的版本号进行比较。下面看一下在REPEATABLE READ隔离级别下,MVCC具体是如何操作的。
SELECT
InnoDB会根据以下两个条件检查每行记录:

1.InnoDB只查找版本早于当前事务版本的数据行(也就是,行的系统版本号小于或等于事务的系统版本号),这样可以确保事务读取的行,要么是在事务开始前已经存在的,要么是事务自身插入或者修改过的。
2.行的删除版本要么未定义,要么大于当前事务版本号。这可以确保事务读取到的行,在事务开始之前未被删除。

只有符合上述两个条件的记录,才能返回作为查询结果。
INSERT
InnoDB为新插入的每一行保存当前系统版本号作为行版本号。
DELETE
InnoDB为删除的每一行保存当前系统版本号作为行删除标识。
UPDATE
InnoDB为插入一行新记录,保存当前系统版本号作为行版本号,同时保存当前系统版本号到原来的行作为行删除标识。
保存这两个额外系统版本号,使大多数读操作都可以不用加锁。这样设计使得读数据操作很简单,性能很好,并且也能保证只会读取到符合标准的行。不足之处是每行记录都需要额外的存储空间,需要做更多的行检查工作,以及一些额外的维护工作。
MVCC只在REPEATABLE READ和READ COMMITTED两个隔离级别下工作。其他两个隔离级别都和MVCC不兼容(4),因为READ UNCOMMITTED总是读取最新的数据行,而不是符合当前事务版本的数据行。而SERIALIZABLE则会对所有读取的行都加锁

MySQL视图
(一)概念
1.视图的含义
视图 是一种有结果(有行有列)但是没结果(结构中不真实存放数据)的虚拟表,虚拟表的结果来源不是自己定义的,而是从对应的基表中产生(视图的数据来源)
视图为虚拟的表,他们包含的不是数据而是根据需要检索数据的查询,视图提供了一种封装select语句的层次,可用来简化数据处理,重新格式化或保护基础数据.

创建视图语法:
create view 视图名字 as select 语句 ; --select语句可以是普通查询,可以是连接查询,可以是连接查询,可以是子查询
2.性能问题
因为视图不包含数据,所以每次使用视图时,都必须处理查询执行时需要的所有检索,如果你用多个联结和过滤创建了复杂的视图或者嵌套了视图,性能可能会下降的很厉害.因此,在部署使用了大量的视图的应用前,应该进行测试.

3.视图的意义
1.视图可以节省sql语句:将一条复杂的查询语句使用视图进行保存,以后可以直接对视图进行操作.
2.视图操作是主要针对查询的,如果对视图结构进行处理(删除),不会影响基表数据(相对安全)
3.视图往往是在大项目中使用,而且是多系统中使用(可以对用户隐藏列,隐藏数据,提供有用的数据)
4.视图可以对外提供友好型,不同的视图提供不同的数据,对外好像专门设计.
5.视图可以更容易的进行权限控制(隐藏基表的数据,保证安全,不让无关的人知道)

4.创建中间表建立两个表的关联
演示订单表和商品详情表添加多对多关联demo

创建订单表

CREATE TABLE orders(
oid INT PRIMARY KEY AUTO_INCREMENT,
otime DATE
);

创建商品详情表

CREATE TABLE products(
pid INT PRIMARY KEY AUTO_INCREMENT,
pname VARCHAR(40)
);

先创建中间表 必须有两个外键,分别引用 以上两张表主键

CREATE TABLE orders_products_item(
itemid INT PRIMARY KEY AUTO_INCREMENT,
o_id INT,
p_id INT

);

需要注意一点,中间表的外键指向的主表的主键的数据类型和长度必须一致,不然无法添加中间表关联
比如:主表主键是 int(32) ,那么中间表指向主表的外键也必须是int(32)

#添加外键约束
格式:
ALTER TABLE 中间表表名 ADD CONSTRAINT fk_item_orders
FOREIGN KEY (要作为主表关联的中间表的外键,) REFERENCES 主表(主表的主键);

列子:
ALTER TABLE orders_products_item ADD CONSTRAINT fk_item_orders
FOREIGN KEY (o_id) REFERENCES orders (oid);

ALTER TABLE orders_products_item ADD CONSTRAINT fk_item_products
FOREIGN KEY (p_id) REFERENCES products (pid);

5.作用

6.查看视图
describe 视图名
或者简写
desc 视图名

7.修改视图

8.更新视图

9.删除视图

存储过程和触发器
(一)存储过程
1.存储过程概念优点

存储过程是一条或者多条sql语句的集合,可以视为批文件.,
作用不仅限于批处理.

存储过程是一种没有返回值的函数

Transact-SQL中的存储过程,非常类似于Java语言中的方法,它可以重复调用。当存储过程执行一次后,可以将语句缓存中,这样下次执行的时候直接使用缓存中的语句。这样就可以提高存储过程的性能。

Ø 存储过程的概念

存储过程Procedure是一组为了完成特定功能的SQL语句集合,经编译后存储在数据库中,用户通过指定存储过程的名称并给出参数来执行。

存储过程中可以包含逻辑控制语句和数据操纵语句,它可以接受参数、输出参数、返回单个或多个结果集以及返回值。

由于存储过程在创建时即在数据库服务器上进行了编译并存储在数据库中,所以存储过程运行要比单个的SQL语句块要快。同时由于在调用时只需用提供存储过程名和必要的参数信息,所以在一定程度上也可以减少网络流量、简单网络负担。

存储过程的优点:

    A、 存储过程允许标准组件式编程

    存储过程创建后可以在程序中被多次调用执行,而不必重新编写该存储过程的SQL语句。而且数据库专业人员可以随时对存储过程进行修改,但对应用程序源代码却毫无影响,从而极大的提高了程序的可移植性。

    B、 存储过程能够实现较快的执行速度

    如果某一操作包含大量的T-SQL语句代码,分别被多次执行,那么存储过程要比批处理的执行速度快得多。因为存储过程是预编译的,在首次运行一个存储过程时,查询优化器对其进行分析、优化,并给出最终被存在系统表中的存储计划。而批处理的T-SQL语句每次运行都需要预编译和优化,所以速度就要慢一些。

    C、 存储过程减轻网络流量

    对于同一个针对数据库对象的操作,如果这一操作所涉及到的T-SQL语句被组织成一存储过程,那么当在客户机上调用该存储过程时,网络中传递的只是该调用语句,否则将会是多条SQL语句(多条sql语句将会产生大量的网络传输)。从而减轻了网络流量,降低了网络负载。

    D、 存储过程可被作为一种安全机制来充分利用

    系统管理员可以对执行的某一个存储过程进行权限限制,从而能够实现对某些数据访问的限制,避免非授权用户对数据的访问,保证数据的安全。

    E、 存储过程安全性高

存储过程可以屏蔽对底层数据库对象的直接访问,使用Exceute权限调用储存过程,无需拥有访问底层数据库对象的显式权限.

2.存储过程使用

存储过程分为存储过程和函数,

创建存储过程: create procedure

创建存储函数 create function

使用call语句来调用存储过程,只能用输出变量返回值.
函数可以从语句外调用(通过引用函数名)也能返回标量值.
存储过程也可以调用存储过程.

3.利用存储过程批量插入数据

DELIMITER #
CREATE PROCEDURE insertMerchant()
BEGIN
DECLARE i INT DEFAULT 1;
WHILE i <= 100
DO
INSERT INTO merchant (city, name, phone, type, title, mobile_phone, address, avg_moeny, content, photo_url,
creation_time,status)
VALUES (“台北市”,“商家名字”,“商家电话”,0,“标题”,“联系电话”,“联系地址”,“人均消费”,“内容”,“图片地址”,now(),0);

SET i = i + 1;
END WHILE;
COMMIT;
END #

CALL insertMerchant(); /运行存储过程/

DROP PROCEDURE insertMerchant; /删除存储过程/

(二)触发器

1.什么是触发器
事先为某张表绑定好一段代码,当表中的某些内容发生改变的时候(增删改),系统会自动触发代码,执行

触发器: 事件类型 触发时间 触发对象

事件类型: 增删改 三种类型 insert delete update
触发时间: 前后 : before 和 after
触发对象: 表中的每一条记录(行)
一张表中只有拥有一种触发时间的一种类型的触发器:最多一张表能用6个触发器. 就是 触发时间和事件类型的笛卡尔积.

2.谨慎使用触发器
1.公司基本不能使用,一是可移植性差,二是影响性能.
2.如果我在某表中建了一个触发器,他不知道,如果他要接手我的程序,则会带来不方便的地方.
3.建立触发器后会带来不可预期的影响.
4.大量使用触发器的时候,如果将来业务逻辑发生变化,或者需要修订的时候,你会发现调试起来极其麻烦,很多时候出了问题很难找出来,我曾经吃了大亏.

概念
1.范式
数据库范式

是为了解决一种数据的存储与优化的问题.终极目标是为了减少数据的冗余

范式:是一种分层结构的规范,分为六层:每一层都比上一层更加严格,若要满足下一层范式,前提是满足上一层范式.
六层范式: 1Nf 2NF 3NF…6NF, 其中1NF是最底层,要求最低,6NF最高层,最严格.

MySQL属于关系型数据库,有空间浪费:也是致力于节省存储空间: 与范式所有解决的问题不谋而合,在设计数据库的时候,会利用到范式来指导设计.
但是数据库不单要解决空间问题,要保证效率问题,范式只为了解决空间问题,所以数据库的设计又不可能完全按照范式的要求时间:一般情况下,只有前三种范式需要满足.
范式在数据库的设计当中是有指导意义,但是不是强制规范.

1NF (第一范式)
在设计表存储数据的时候,如果表中设计的字段存储的数据在取出来使用之前还需要额外的处理(比如说拆分),那么说表的设计不满足第一范式,(因为第一范式要求字段的数据具有原子性,也就是不可再分)

解读,也就是说我设计出来的字段查出来就可以直接用了, 而不应该再分开再用,如果需要这样的话,说明数据库设计的不合理,你应该分开存进去而不是每次取出来再拆开

2NF(第二范式)
在数据表设计的过程中,如果有复合主键(多字段主键),且表中有字段并不是由整个主键来确定的,而是依赖主键中的某一个字段(主键的一部分):如果存在字段依赖主键部分的问题,称之为部分依赖
第二范式要解决我们表的设计不允许部分依赖

解决方案: 取消复合主键,使用逻辑主键,比如 id 主键 自增或者uuid

3FN(第三范式)
要满足第三范式,必须先满足第二范式

第三范式:理论上讲,应该一张表中的所有字段都应该直接依赖主键(逻辑主键:代表的是业务主键),如果表设计中存在一个字段,并不直接依赖主键,而是通过某个非主键字段依赖,最终实现依赖主键,把这种不是直接主键而是直接依赖非主键字段的依赖关系称之为传递依赖
解读传递依赖:本来我是直接依赖这个人,但是我不是直接依赖这个人,而是通过第三者去依赖.
第三范式解决: 依赖传递问题.

解决方案:将存在传递依赖的字段,以及依赖的字段本身单独取出,形成一个单独的表,然后在需要对应的信息的时候,使用对应的实体表的主键加进来.

后面的四层五层六层范式就不需要刻意遵守了
越遵守范式相比查询效率就低,在到达第三范式的时候就开始降低效率了,如果你在使用第四范式的时候意味着更减少数据冗余但是效率会更低了.

越大型的项目数据冗余越大,冗余大效率就高.查询效率高用户使用体验好.尤其是电商项目,因为用户不喜欢等待.为什么男的不愿意陪女的逛街.就是等不及.

逆规范化
有时候,在设计表的时候,如果一张表中有几个字段是需要从另外的表中去获取信息,理论上讲,的确可以获取到想要的数据,但是效率会低一点.会刻意的在某些表中,不去保存另外表的主键(逻辑主键),而是直接保存想要的数据信息,这样一来,在查询数据的时候,一张表可以直接提供数据,而不需要多表查询(效率低),但是会导致数据冗余增加.

逆规范化:就是磁盘利用率和效率的对抗, 你想效率高就直接数据都放在一张表里面,不用关联查询,查询的时候直接从一张表里面查询出来,效率高,但是会有冗余.

(二)web项目问题
1.取出时间和数据库存入的数据不一致
数据库里面的时间和从数据库里面取出的时间相差了8个小时,
解决办法:

在连接数据库的配置信息中,url 后面的 serverTimezone=UTC ——> 改为 serverTimezone=Asia/Shanghai 
还是由于时区问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值