数据库三大范式以及数据库事务,数据库锁
一:数据库基本设计以及三大范式
数据库:简而言之可视为电子化的文件柜(存储电子文件的处所),用户可以对文件中的数据进行新增、截取、更新、删除等操作。它分为关系型数据库和非关系型数据,今天我们着重来讲一下关系型数据库。
1:什么是关系型数据库?其实说白了就是把对象封装起来,然后来表述对象与对象之间的关系。
2:数据关系一般分为几种,分别是什么?数据关系有三种,分别为一对一,一对多,多对多。下面分别解释举例说明一下这三种关系
- 一对一:比如一个人对应一张身份证,对应一个护照号,人跟身份证,人跟护照号这样就属于一对一的关系,一般两者之间的关系通过主外键或者相同主键。
- 一对多:国家对省,省对城市,城市对区域等,这些属于一对多的关系,一般两者之间的联系是通过主外键联系的。
- 多对多:学生对班级,老师对学生,这些是属于多对多的关系,两者之间的联系通过关系/映射/中间表。
3:上面的数据库以及之间的关系,已经解释ok,那怎么做数据设计?可以根据以下三部
- 需求分析,调研讨论明白需要存储什么
- 概要设计,E-R图设计,把表/属性的关系图使用powerdesigner或者codefirst画出来,解决多方面的沟通问题
- 详细设计,把表字段对应的属性映射对应出来(一般这一步会省略掉,主要最多的是1,2步)
4:数据库设计一般会遵循三大范式,分别是什么?下面解析说明。
A:每一列保持原子性,不可分割;
学员ID | 学员名字 | 家庭信息 |
1 | 可可 | 3口人,河南 |
2 | 馨馨 | 4口人,上海 |
如上图的家庭信息这一栏,我们如果这样写,那就是违背了原子性,因为家庭信息是可以分割的,可以分拆为家庭人员数,户籍所在地,可以拆分成如下:
学员ID | 学员名字 | 家庭人员 | 信息 |
1 | 可可 | 3口人 | 河南 |
2 | 馨馨 | 4口人 |
上海 |
B:每一列都得跟主键相关,一张表只应该描述一个对象;这个需要注意两点,一是:主键 ,二是:信息跟主键相关
ID | 员工名字 | 公司名字 | 公司地址 |
1 | 可可 | 缘爱 | 上海 |
2 | 馨馨 | 可馨 |
深圳 |
比如上面的员工表,里面增加了员工公司的一些信息,这样就违背了第二大范式,通过修改如下下面即可:
员工表:
ID | 员工名字 | 公司Id |
1 | 可可 | 1 |
2 | 馨馨 | 2 |
公司表:
ID | 公司名字 | 公司地址 |
1 | 缘爱 | 上海 |
2 | 可馨 |
深圳 |
C:每一列都得跟主键直接相关,而不是间接相关,如下面的公司名称就属于违背了第三大范式,但是有时候为了方便查询,第三大范式经常会被违背的,这个还要根据具体的业务逻辑去选择的。
ID | 员工名字 | 公司Id | 公司名字 |
1 | 可可 | 1 | 缘爱 |
2 | 馨馨 | 2 | 可馨 |
以上三大范式只是建议,其实都有可能被违背,话说一将不行,累死三军,所以正确的设计,会减少很多工作量。
二:数据库事务
1:什么是事务?事务是多条sql作为一个整体提交给数据库系统,要么全部执行完成,要么全部取消。是一个不可分割的逻辑单元。其实我们经常见到的单条update或者insert就是一个简单的事务。
2:为什么要用事务?事务保证一起成功或者一起失败,比如sql语句,一个下订单成功;另一个减掉库存;如果不使用事务,有可能刚刚执行下单成功,但是在执行减掉库存的时候错误,则会造成订单和库存不能保证统一的。
3:事务的基本语法
1 ******************************事务sql*********************************** 2 3 ---开启事务 4 begin tran 5 --错误扑捉机制,看好啦,这里也有的。并且可以嵌套。 6 begin try 7 --语句正确 8 insert into [Company] (Name,CreateTime,CreatorId) values ('字节','2019-03-20',1) 9 --CreatorId为int类型,出错 10 insert into [Company] (Name,CreateTime,CreatorId) values ('新启','2019-03-20','Test') 11 --语句正确 12 insert into [Company] (Name,CreateTime,CreatorId) values ('舞动','2019-03-20',2) 13 end try 14 begin catch 15 select Error_number() as ErrorNumber, --错误代码 16 Error_severity() as ErrorSeverity, --错误严重级别,级别小于10 try catch 捕获不到 17 Error_state() as ErrorState , --错误状态码 18 Error_Procedure() as ErrorProcedure , --出现错误的存储过程或触发器的名称。 19 Error_line() as ErrorLine, --发生错误的行号 20 Error_message() as ErrorMessage --错误的具体信息 21 if(@@trancount>0) --全局变量@@trancount,事务开启此值+1,他用来判断是有开启事务 22 rollback tran ---由于出错,这里回滚到开始,第一条语句也没有插入成功。 23 end catch 24 if(@@trancount>0) 25 commit tran --如果成功Company表中,将会有3条数据。 26 27 --表本身为空表,ID ,Numb为int 类型,其它为nvarchar类型 28 select * from [Company] 29 30 31 32 ******************************事务含save tran*********************************** 33 34 ---开启事务 35 begin tran 36 --错误扑捉机制,看好啦,这里也有的。并且可以嵌套。 37 begin try 38 --语句正确 39 insert into [Company] (Name,CreateTime,CreatorId) values ('字节','2019-03-20',3) 40 --加入保存点,如果下面的出错了,上面的还能保存 41 save tran SavePoint 42 --CreatorId为int类型,出错 43 insert into [Company] (Name,CreateTime,CreatorId) values ('新启','2019-03-20','Test') 44 --语句正确 45 insert into [Company] (Name,CreateTime,CreatorId) values ('舞动','2019-03-20',4) 46 end try 47 begin catch 48 select Error_number() as ErrorNumber, --错误代码 49 Error_severity() as ErrorSeverity, --错误严重级别,级别小于10 try catch 捕获不到 50 Error_state() as ErrorState , --错误状态码 51 Error_Procedure() as ErrorProcedure , --出现错误的存储过程或触发器的名称。 52 Error_line() as ErrorLine, --发生错误的行号 53 Error_message() as ErrorMessage --错误的具体信息 54 if(@@trancount>0) --全局变量@@trancount,事务开启此值+1,他用来判断是有开启事务 55 rollback tran ---由于出错,这里回滚到开始,第一条语句也没有插入成功。 56 end catch 57 if(@@trancount>0) 58 rollback tran SavePoint --如果成功Company表中,将会有3条数据。 59 60 --表本身为空表,ID ,Numb为int 类型,其它为nvarchar类型 61 select * from [Company]
--开始事务 begintransaction tran_bank; declare @tran_error int; set @tran_error = 0; begin try update bank set totalMoney = totalMoney - 10000 where userName = 'jack'; set @tran_error = @tran_error + @@error; update bank set totalMoney = totalMoney + 10000 where userName = 'jason'; set @tran_error = @tran_error + @@error; end try begin catch print'出现异常,错误编号:' + convert(varchar, error_number()) + ', 错误消息:' + error_message(); set @tran_error = @tran_error + 1; end catch if (@tran_error > 0) begin --执行出错,回滚事务 rollbacktran; print'转账失败,取消交易'; end else begin --没有异常,提交事务 committran; print'转账成功'; end go
4:事务的特点为ACID
原子性:要么都成功,要么都失败,一个事务中不可能停滞在中间某个环节。事务发送问题就会被回滚到事务执行之前的状态,就像没有执行过一样。
一致性:如果事务是并发,系统也会把事务串起来,一个一个的按序执行。
隔离线:AB两个事务同时操作一张表,B事务要么是在A事务前完成,要么是在A事务完成后执行,事务操作表的时候会锁表
持久性:事务一旦提交,则数据就会被固化下来,持久存在,直到下次再被修改。
三:数据锁
1:为什么要使用锁?
多数据同时访问一个数据资源时,保证操作有个先后顺序管理,处理并发问题,防止数据
A:修改丢失 例:多并发操作一张表,一个读,一个删除或者修改
B:不可重复读 例:多并发操作一张表,一个读,一个改或者删除
C:脏读/幻读 例:多并发操作一张表,一个修改一半,一个读取
2:数据锁一般分为几种?
A:乐观锁:认为没有并发,读取数据--更新数据---保存,比如更新数据时做一个判断,可以使用时间戳/Version/检查更新字段/检查全部字段作为where条件,其实这个是按照业务逻辑来定的,不过要保证所有的操作都按照这个规范来操作,不然也会有漏洞,比如通过其它渠道更新,乐观锁的好处是性能高。
B:悲观锁:认为任何时候都有可能有多线程并发,比如读数据时别人恰好在修改。悲观锁是基于数据锁的机制来完成,它分为:
- 共享锁 S锁 读锁,允许别的事务来读,但是不允许修改;读完就释放,锁定数据页;(除非holdlock就一直锁定)
- 排他锁 X锁 写锁,准备写数据,不允许读也不允许写;
- 更新锁 U锁 ,先查询再更新;
其实按照其它的方式可以分为行锁(where id=3),表锁(where 1=1)
3:怎么避免死锁?
- 不用锁就不会死锁,乐观锁
- 统一操作顺序--先A后B再C
- 最小单元锁,锁里面操作尽量减少
- 避免事务中等待用户输入
- 减少数据库并发
- 分库分表表分区
- 降低事务级别
- 设置死锁时间 set lock_timeout(锁超时时间)
备注:其实高并发下死锁是不可能避免的,只能减少。