MySQL的事务与索引
MySQL
1.事务
- 事务(Transaction)是并发控制的单位,是用户定义的一个操作序列。这些操作要么都做,要么都不做,是一个不可分割的工作单位。
1.1 什么是事务
- 要么都成功 要么都失败
- 事务就是将一组SQL语句放在同一批次内去执行
- 如果一个SQL语句出错,则该批次内的所有SQL都将被取消执行
- MySQL事务处理只支持InnoDB和BDB数据表类型
- 1.SQL 执行 A给B转账 A 1000 --> B 200
- 2.SQL 执行 B收到A的钱 A 800 --> B400
1.2 事务原则 : ACID原则 原子性,一致性,持久性,隔离性 (脏读,幻读…)
-
原子性(Atomic)
- 整个事务中的所有操作,要么全部完成,要么全部不完成,不可能停滞在中间某个环节。事务在执行过程中发生错误,会被回滚(ROLLBACK)到事务开始前的状态,就像这个事务从来没有执行过一样
-
一致性(Consist)
- 一个事务可以封装状态改变(除非它是一个只读的)。事务必须始终保持系统处于一致的状态,不管在任何给定的时间并发事务有多少。也就是说:如果事务是并发多个,系统也必须如同串行事务一样操作。其主要特征是保护性和不变性(Preserving an Invariant),以转账案例为例,假设有五个账户,每个账户余额是100元,那么五个账户总额是500元,如果在这个5个账户之间同时发生多个转账,无论并发多少个,比如在A与B账户之间转账5元,在C与D账户之间转账10元,在B与E之间转账15元,五个账户总额也应该还是500元,这就是保护性和不变性。
-
持久性(Durable)
- 在事务完成以后,该事务对数据库所作的更改便持久的保存在数据库之中,并不会被回滚。
-
隔离性(Isolated)
- 隔离状态执行事务,使它们好像是系统在给定时间内执行的唯一操作。如果有两个事务,运行在相同的时间内,执行相同的功能,事务的隔离性将确保每一事务在系统中认为只有该事务在使用系统。这种属性有时称为串行化,为了防止事务操作间的混淆,必须串行化或序列化请求,使得在同一时间仅有一个请求用于同一数据。
隔离导致的一些问题
- 脏读
- 指一个事务读取了另外一个事务未提交的数据
- 不可重复读
- 在一个事务内读取表中的某行数据,多次读取结果不同。(这个不一定是错误 只是某些场合不对)
- 幻读(虚读)
- 是指在一个事务内读取到了别的事务插入的数据,导致前后读取不一致。(一般是行影响,多了一行)
- 幻读和不可重复读有些类似,但是幻读强调的是集合的增减,而不是单条数据的更新。
1.3 事务隔离级别
- 为了解决以上的问题,主流的关系型数据库都会提供四种事务的隔离级别。事务隔离级别从低到高分别是:读未提交,读已提交,可重复读,串行化。事务隔离级别越高,越能保证数据的一致性和完整性,但是执行效率也越低,所以在设置数据库的事务隔离级别时需要做一下权衡
- mysql默认是可重复读
- 读未提交
- 读未提交(Read Uncommitted),是最低的隔离级别,所有的事务都可以看到其他未提交的事务的执行结果。只能防止第一类更新丢失,不能解决脏读,可重复读,幻读,所以很少应用于实际项目。
-
读已提交
- 读已提交(Read Committed),在该隔离级别下,一个事务的更新操作只有在该事务提交之后,另外一个事务才可能读取到同一笔数据更新后的结果。可以防止脏读和第一类更新丢失,但是不能解决可重复和幻读的问题。
-
可重复读(重要)
- 可重复读(Repeatable Read),mysql默认的隔离级别。在该隔离级别下,一个事务多次读同一个数据,在这个事务还没有结束时,其他事务不能访问该数据(包括了读写),这样就可以在同一个事务内两次读到的数据是一样的。可以防止脏读、不可重复读、第一类更新丢失,第二类更新丢失的问题,不过还是会出现幻读。
- 串行化
- 串行化(Serializable),这是最高的隔离级别。它要求事务序列化执行,事务只能一个接着一个的执行,不能并发执行。在这个级别,可以解决上面提到的所有并发问题,但是可能导致大量的超时现象和锁竞争,通常不会用这个隔离级别。
1.4 测试事务实现转账
- 执行事务:相关命令
-- ==========事务====================
-- mysql默认开启事务自动提交的
SET autoccommit = 0;/*关闭*/
SET autoccommit = 1;/*开启(默认)*/
-- s手动处理事务
SET autoccommit = 0; -- 关闭自动提交
-- 事务开启
START TRANSACTION -- 标记一个事务的开始,从这个开始的sql 都在同一个事务中
INSERT xx
INSERT yy
-- 提交 持久化(成功)
COMMIT
-- 回滚 回到原来的样子(失败)
ROLLBACK
-- 事务结束
SET autoccommit = 1;-- 开启自动提交
-- 了解
SAVEPOINT 保存点名称 -- 设置一个事务的保存点
ROLLBACK TO SAVEPOINT -- 回滚到保存点
RELEASE SAVEPOINT 保存点名称 -- 删除保存点
- 模拟场景
/*
课堂测试题目
A在线买一款价格为500元商品,网上银行转账.
A的银行卡余额为2000,然后给商家B支付500.
商家B一开始的银行卡余额为10000
创建数据库shop和创建表account并插入2条数据
*/
CREATE DATABASE `shop`CHARACTER SET utf8 COLLATE utf8_general_ci;
USE `shop`;
CREATE TABLE `account` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(32) NOT NULL,
`cash` DECIMAL(9,2) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8
INSERT INTO account (`name`,`cash`)
VALUES('A',2000.00),('B',10000.00)
-- 模拟转账
-- 转账实现
SET autocommit = 0; -- 关闭自动提交
START TRANSACTION; -- 开始一个事务,标记事务的起始点(一组事务)
UPDATE account SET cash=cash-500 WHERE `name`='A';-- A减500
UPDATE account SET cash=cash+500 WHERE `name`='B';-- B加500
COMMIT; -- 提交事务就会被持久化了
# rollback; --回滚
SET autocommit = 1; -- 恢复自动提交
2.索引
2.1 索引的介绍
- MySQL官方对索引的定义为:索引(index)是帮助MySQL高效获取数据的数据结构
- 提取句子主干 就可以得到索引的本质 : 索引是数据结构
-
索引的作用
- 提高查询速度
- 确保数据的唯一性
- 可以加速表和表之间的连接 , 实现表与表之间的参照完整性
- 使用分组和排序子句进行数据检索时 , 可以显著减少分组和排序的时间
- 全文检索字段进行搜索优化
-
索引的分类
在一个表中,主键索引只能有一个,唯一索引可以有多个
-
主键索引 (PRIMARY KEY)
- 唯一的标识,主键不可重复,只能有一个列作为主键
-
唯一索引 (UNIQUE KEY)
- 避免重复的列出现,唯一索引可以重复,多个列都可以标识唯一索引
-
常规索引(KEY/INDEX)
- 默认的,index,key关键字来设置
-
全文索引(FULLTEXT)
- 在特定的数据库引擎下才有, MySAM
- 快速定位数据
基础语法
/*
#方法一:创建表时
CREATE TABLE 表名 (
字段名1 数据类型 [完整性约束条件…],
字段名2 数据类型 [完整性约束条件…],
[UNIQUE | FULLTEXT | SPATIAL ] INDEX | KEY
[索引名] (字段名[(长度)] [ASC |DESC])
);
#方法二:CREATE在已存在的表上创建索引
CREATE [UNIQUE | FULLTEXT | SPATIAL ] INDEX 索引名
ON 表名 (字段名[(长度)] [ASC |DESC]) ;
#方法三:ALTER TABLE在已存在的表上创建索引
ALTER TABLE 表名 ADD [UNIQUE | FULLTEXT | SPATIAL ] INDEX
索引名 (字段名[(长度)] [ASC |DESC]) ;
#删除索引:DROP INDEX 索引名 ON 表名字;
#删除主键索引: ALTER TABLE 表名 DROP PRIMARY KEY;
#显示索引信息: SHOW INDEX FROM student;
*/
/*增加全文索引*/
ALTER TABLE `school`.`student` ADD FULLTEXT INDEX `studentname` (`StudentName`);
/*EXPLAIN : 分析SQL语句执行性能*/
EXPLAIN SELECT * FROM student WHERE studentno='1000';
/*使用全文索引*/
-- 全文搜索通过 MATCH() 函数完成。
-- 搜索字符串作为 against() 的参数被给定。搜索以忽略字母大小写的方式执行。对于表中的每个记录行,MATCH() 返回一个相关性值。即,在搜索字符串与记录行在 MATCH() 列表中指定的列的文本之间的相似性尺度。
EXPLAIN SELECT *FROM student WHERE MATCH(studentname) AGAINST('love');
/*
开始之前,先说一下全文索引的版本、存储引擎、数据类型的支持情况
MySQL 5.6 以前的版本,只有 MyISAM 存储引擎支持全文索引;
MySQL 5.6 及以后的版本,MyISAM 和 InnoDB 存储引擎均支持全文索引;
只有字段的数据类型为 char、varchar、text 及其系列才可以建全文索引。
测试或使用全文索引时,要先看一下自己的 MySQL 版本、存储引擎和数据类型是否支持全文索引。
*/
-- 索引的使用
-- 1、在创建表的时候给字段增加索引
-- 2、创建完毕后 增加索引
-- 显示所有索引信息
SHOW INDEX FROM `student`;
-- 增加一个索引 (索引名:列名)
ALTER TABLE school.`student` ADD FULLTEXT INDEX `StudentName`(`StudentName`);
-- EXPLAIN 分析SQL执行的状况
EXPLAIN SELECT * FROM student; -- 非全文索引
EXPLAIN SELECT * FROM student WHERE MATCH(StudentName) AGAINST('韩')
2.2 测试索引
- 创建使用的表
CREATE TABLE `app_user` (
`id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(50) DEFAULT'' COMMENT'用户昵称',
`email` VARCHAR(50) NOT NULL COMMENT'用户邮箱',
`phone` VARCHAR(20) DEFAULT'' COMMENT'手机号',
`gender` TINYINT(4) UNSIGNED DEFAULT '0'COMMENT '性别(0:男;1:女)',
`password` VARCHAR(100) NOT NULL COMMENT '密码',
`age` TINYINT(4) DEFAULT'0' COMMENT '年龄',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
`update_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8 COMMENT = 'app用户表'
- 插入100万数据
-- 插入100w条数据
DROP FUNCTION IF EXISTS mock_data;
DELIMITER $$ -- 写函数之前必须要写 标志
CREATE FUNCTION mock_data()
RETURNS INT
BEGIN
DECLARE num INT DEFAULT 1000000;
DECLARE i INT DEFAULT 0;
WHILE i < num DO
-- 插入语句
INSERT INTO app_user(`name`, `email`, `phone`, `gender`, `password`, `age`)
VALUES(CONCAT('用户', i), '24736743@qq.com', CONCAT('18', FLOOR(RAND()*(999999999-100000000)+100000000)),FLOOR(RAND()*2),UUID(), FLOOR(RAND()*100));
SET i = i + 1;
END WHILE;
RETURN i;
END;
SELECT mock_data();
- 索引效率测试
-- 加索引前
SELECT * FROM app_user WHERE `name` = '用户9999'; -- 1.159 sec
SELECT * FROM app_user WHERE `name` = '用户9999'; -- 0.642 sec
SELECT * FROM app_user WHERE `name` = '用户9999'; -- 0.631 sec
EXPLAIN SELECT * FROM app_user WHERE `name` = '用户9999';-- 分析看到查询了99999条记录
SELECT * FROM student
-- id _ 表名 _ 字段名
-- CREATE INDEX 索引名 on 表(字段)
CREATE INDEX id_app_user_name ON app_user(`name`);
-- 加索引后
SELECT * FROM app_user WHERE `name` = '用户9999'; -- 0.009 sec
EXPLAIN SELECT * FROM app_user WHERE `name` = '用户9999'; -- 查询一条记录
- 索引在小数据量的时候,用处不大,但是再大数据的时候,区分十分明显
2.3 索引原则
- 索引不是越多越好
- 不要对经常变动的数据加索引
- 小数据量的表不需要加索引
- 索引一般加在常用来查询的字段上
索引的数据结构
- Hash类型的索引
- Btree:InnoDB默认的
-- 我们可以在创建上述索引的时候,为其指定索引类型,分两类
hash类型的索引:查询单条快,范围查询慢
btree类型的索引:b+树,层数越多,数据量指数级增长(我们就用它,因为innodb默认支持它)
-- 不同的存储引擎支持的索引类型也不一样
InnoDB 支持事务,支持行级别锁定,支持 B-tree、Full-text 等索引,不支持 Hash 索引;
MyISAM 不支持事务,支持表级别锁定,支持 B-tree、Full-text 等索引,不支持 Hash 索引;
Memory 不支持事务,支持表级别锁定,支持 B-tree、Hash 等索引,不支持 Full-text 索引;
NDB 支持事务,支持行级别锁定,支持 Hash 索引,不支持 B-tree、Full-text 等索引;
Archive 不支持事务,支持表级别锁定,不支持 B-tree、Hash、Full-text 等索引;
3.MySQL权限管理与备份
3.1 用户管理
-
方式一:SQLyog可视化管理
-
方式二: SQL命令操作
- 用户表:mysql.user
- 本质:对这张表的增删改查
-- 创建用户 CREATE USER 用户名 IDENTIFIED BY '密码'
CREATE USER sanjin IDENTIFIED BY '123456'
-- 修改密码(修改当前密码)
SET PASSWORD = PASSWORD('111111')
-- 修改密码(修改指定用户密码)
SET PASSWORD FOR sanjin = PASSWORD('111111')
-- 重命名 rename user 原名字 to 新名字
RENAME USER sanjin TO sanjin2
-- 用户授权 ALL PRIVILEGES 全部的权限 库,表
-- ALL PRIVILEGES 除了给别人授权,其他都能干
GRANT ALL PRIVILEGES ON *.* TO sanjin2
-- 查询权限
SHOW GRANTS FOR sanjin2 -- 查看指定用户的权限
SHOW GRANTS FOR root@localhost
-- ROOT用户权限:GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' WITH GRANT OPTION
-- 撤销权限 REVOKE 哪些权限,在哪个库撤销,给谁撤销
REVOKE ALL PRIVILEGES ON *.* FROM sanjin2
-- 删除用户
DROP USER sanjin2
- 以上的操作都是物理外键,数据库级别外键,我们不建议使用!(避免数据库过多造成困扰)
- 最佳实践
- 数据库就是单纯的表,只用来存数据,只有行(数据)和列(字段)。
- 我们想使用多张表的数据,想使用外键(程序去实现)。
3.2 MySQL 备份
- 为什么要备份:
- 保证重要的数据不丢失
- 数据转移
- mysql数据库备份的方式
- 直接拷贝物理文件
- 在sqlyog这种可视化工具中手动导出:在想要导出的表或者库中,右键选择备份或导出
- 使用SQL命令行 mysqldump导出
#导出一张表
#mysqldump -h 主机 -u 用户名 -p 密码 数据库名 表名 >导出位置
mysqldump -hlocalhost -uroot -p123456 myschool student >D:/a.sql
# 多张表
#mysqldump -h 主机 -u 用户名 -p 密码 数据库名 表1 表2 表3 >导出位置
mysqldump -hlocalhost -uroot -p123456 myschool student result >D:/b.sql
# 数据库
#mysqldump -h 主机 -u 用户名 -p 密码 数据库名 >导出位置
mysqldump -hlocalhost -uroot -p123456 myschool >D:/c.sql
#导入
#在登陆的情况下,切换到指定的数据库
#source 备份文件
source D:/a.sql
#没有登陆的情况下
mysql -u用户名 -p密码 库名<备份文件
4.规范数据库设计
4.1 为什么要需要设计
-
当数据库比较复杂的时候,我们就需要设计了
-
糟糕的数据库设计:
- 数据冗余,浪费空间
- 数据库插入和删除都会麻烦、异常【屏蔽使用物理外键】
- 程序的性能差
-
良好的数据库设计:
- 节省内存空间
- 保证数据库的完整。
- 方便我们开发系统
-
软件开发中,关于数据库的设计
- 分析需求:分析业务和需要处理的数据库的需求
- 概要设计:设计关系图 E-R 图
设计数据库的步骤:(例如个人博客)
-
收集信息,分析需求
- 用户表(用户登录注销,用户的个人信息,写博客,创建分类)
- 分类表(文章分类,谁创建的)
- 文章表(文章的信息)
- 评论表
- 友链表(友链信息)
- 自定义表(系统信息,某个关键的字,或者一些主字段)key :value
- 说说表(发表心情 … id… content… create_time)
-
标识实体(把需求落地到每个字段)
-
标识实体 之间的关系
- 写博客:user --> blog
- 创建分类:user --> category
- 关注:user --> user
- 友链:links
- 评论:user-user-blog
4.2 数据库三大范式
- 为什么需要数据规范化
-
信息重复
-
更新异常
-
插入异常:无法正常显示信息
-
删除异常:丢失有效的信息
-
三大范式
-
第一范式 (1st NF)
- 第一范式的目标是确保每列的原子性,如果每列都是不可再分的最小数据单元,则满足第一范式
-
第二范式(2nd NF)
- 第二范式(2NF)是在第一范式(1NF)的基础上建立起来的,即满足第二范式(2NF)必须先满足第一范式(1NF)
- 第二范式要求每个表只描述一件事情
-
第三范式(3rd NF)
- 如果一个关系满足第二范式,并且除了主键以外的其他列都不传递依赖于主键列,则满足第三范式
- 第三范式需要确保数据表中的每一列数据都和主键直接相关,而不能间接相关
-
规范性 和 性能的问题
-
关联查询的表不得超过三张表
- 考虑商业化的需求和目标,(成本,用户体验!)数据库的性能更加重要
- 在规范性能的问题的时候,需要适当的考虑一下 规范性!
- 故意给某些表增加一些冗余的字段。(从多表查询中变为单表查询)
- 故意增加一些计算列(从大数据量降低为小数据量的查询:索引)