数据库原理与应用
如果一个关系模式R的所有属性都是不可分的基本数据项,则R∈1NF
第一范式是对关系模式的最起码的要求。不满足第一范式的数据库模式不能称为关系数据库
若关系模式R∈1NF,并且每一个非主属性都完全函数依赖于任何一个候选码,则R∈2NF,即不存在非主属性对码的部分函数依赖。
一个关系模式不属于2NF,会产生以下问题:
插入异常
如果插入一个新学生,但该生未选课,即该生无Cno,由于插入元组时,必须给定码值,因此插入失败。
删除异常
如果S4只选了一门课C3,现在他不再选这门课,则删除C3后,整个元组的其他信息也被删除了。
修改复杂
如果一个学生选了多门课,则Sdept,Sloc被存储了多次。如果该生转系,则需要修改所有相关的Sdept和Sloc,造成修改的复杂化。
在关系模式R(U)中,如果X→Y,并且对于X的任何一个真子集X′,都有
X′ →/ Y, 则称Y完全函数依赖于X。
若X→Y,但Y不完全函数依赖于X,则称Y部分函数依赖于X。
关系模式R 中若不存在这样的码X、属性组Y及非主属性Z(Z Y), 使得X→Y,Y→Z成立,Y → X,则称R ∈ 3NF
在R(U)中,如果X→Y,(Y 不是X的子集),Y→X, Y→Z, 则称Z对X传递函数依赖。
注: 如果Y→X, 即X←→Y,则Z直接依赖于X
设关系模式R∈1NF,若X →Y且Y ⊆ X时X必含有码,则R∈BCNF。
换言之,在关系模式R中,如果每一个决定属性集都包含候选码,则R∈BCNF。
3NF——只消除非主属性对主属性的传递依赖;
BCNF——消除所有属性对主属性的传递依赖。
关系模式R∈1NF,如果对于R的每个非平凡多值依赖X→→Y(Y X),X都含有码,则R∈4NF
视图(view
)是一种虚拟存在的表,是一个逻辑表,本身并不包含数据。通过视图,可以展现基表(用来创建视图的表)的部分数据;视图数据来自定义视图的查询表。
为什么用这个玩意?看视图的优点:
①. 简单:使用视图的用户完全不需要关心后面对应的表的结构、关联条件和筛选条件,对用户来说已经是过滤好的复合条件的结果集;
②. 安全:使用视图的用户只能访问他们被允许查询的结果集,对表的权限管理并不能限制到某个行或列,但是通过视图就可以简单的实现;
③. 数据独立:一旦视图的结构确定了,可以屏蔽表结构变化对用户的影响,源表增加列队视图没有影响;源表修改列名,则可以通过修改视图来解决,不会造成对访问者的影响。
总而言之,使用视图的大部分情况是为了保障数据安全性,提高查询效率。
CREATE [OR REPLACE] [ALGORITHM = {UNDEFINED | MERGE | TEMPTABLE}]
VIEW view_name [(column_list)]
AS select_statement
[WITH [CASCADED | LOCAL] CHECK OPTION]
参数说明:
OR REPLACE
:表示替换已有视图;ALGORITHM
:表示视图选择算法,默认算法是UNDEFINED
(未定义的): MySQL
自动选择要使用的算法 ;merge
合并;temptable
临时表;column_list
:可选参数,指定视图中各个属性的名词,默认情况下与select
语句中查询的属性相同;select_statement
:表示select
语句;[WITH [CASCADED | LOCAL] CHECK OPTION]
:表示视图在更新时保证在视图的权限范围之内;cascade
是默认值,表示更新视图的时候,要满足视图和表的相关条件;local
表示更新视图的时候,要满足该视图定义的一个条件即可。create view view_stuinfo(id,name,class) as select stu_id,stu_name,stu_class from stu;
#学生表stu中含有学生全部的信息(包括隐私),因此视图可以在一定程度上保护数据安全。
#可以自定义列名,也可以不写,则保持查询结果的原样
create view view_stuinfo as select stu_id,stu_name,stu_class from stu;
视图是逻辑表,也就是说视图不是真实的表,但操作视图和操作普通表的语法是一样的。
用户可以在视图中无条件地使用select
语句查询数据。但使用insert
、update
和delete
操作需要在创建视图时满足以下条件(满足以下条件的视图称为可更新视图):
from
子句中只能引用有1
个表(真实表或可更新视图);with
、distinct
、group by
、having
、limit
等子句;union
、intersect
、except
等集合操作;select
子句的字段列表不能包含聚合、窗口函数、集合返回函数。在不可更新视图中操作视图可能会报错ERROR 1288 (HY000)
DROP VIEW view_name;
view_name为视图名称
为了方便读者快速查找书中的术语,很多书籍在最后附加了索引页,术语按字母排序,同时给出页码。这样读者可以根据术语名,快速获取页码,而不用翻阅整本书。
一般情况,在没有索引下,数据库系统必须扫描整个表(一行一行地检查),才能获取到所有满足条件的行,很明显这种方法的效率是非常低的。于是数据库也可以给数据加索引。
索引大体可分为单列索引(普通索引,唯一索引,主键索引)、组合索引、全文索引、空间索引四类。本实训我们主要介绍单例索引和组合索引:
一个索引只包含单个列,但一个表中可以有多个单列索引;
仅加速查询 最基本的索引,没有任何限制,是我们大多数情况下使用到的索引;
索引列中的值必须是唯一的,但允许为空值,而主键索引是不允许为空的;
是一种特殊的唯一索引,不允许有空值。
在表的多个字段上创建的索引,只有在查询条件中使用了这些字段的左边字段时,索引才会被使用,使用组合索引时遵循最左前缀集合。
只能在varchar或text类型上创建
//创建
CREATE [UNIQUE|FULLTEXT|SPATIAL] INDEX 索引名称 ON 表名{字段名称[(长度)] [ASC|DESC]}
//修改
ALTER TABLE tbl_name ADD [UNIQUE|FULLTEXT|SPATIAL] INDEX索引名称(字段名称[(长度)][ASC|DESC]);
创表时创建普通索引:
CREATE table mytable(
id INT NOT NULL,
username VARCHAR(16) NOT NULL,
INDEX [indexName] (username)
);
建表后创建普通索引:
create INDEX 索引名称 on 表名(字段名);
#或者
ALTER TABLE 表名 ADD INDEX 索引名称 (字段名);
唯一索引:
CREATE UNIQUE INDEX 索引名称 ON 表名(字段名);
#或者
ALTER TABLE 表名 ADD UNIQUE (字段名);
主键索引:主键索引一般在建表时创建,例如一般表的id
字段,会设为 int
而且是 AUTO_INCREMENT
自增类型。
CREATE TABLE mytable (
id int(11) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (id)
);
组合索引:组合索引就是在多个字段上创建一个索引。(应用场景:当表的行数远远大于索引键的数目时,使用这种方式可以明显加快表的查询速度)
CREATE INDEX 索引名称 ON 表名(字段1,字段2,字段3);
#或者
ALTER TABLE 表名 ADD INDEX 索引名称(字段1,字段2,字段3);
ALTER TABLE student ADD INDEX name_city_score (name,city,score);
#实际上组合索引直接创建了如下三个索引:
namename
cityname
city score
查询时也会根据查询语句以上三个索引进行匹配,即查询名字或者同时查询名字城市又或者查询名字城市分数时都可以使用组合索引,但是查询城市分数等就不能使用,这是因为遵循了最左匹配原则,必须从左开始进行匹配。
alter index 索引名称 rename to 索引新名称;
#使用drop删除索引
drop index index_name [on table_name] ;
#使用alter删除索引
alter table table_name drop index index_name ;
alter table table_name drop primary key ; #删除主键索引
查询表中的所有索引:show index from 表名;
查询结果部分字段解释:
字段名 | 说明 |
---|---|
Table | 创建索引的表 |
Non_unique | 表示索引非唯一,1 代表非唯一索引,0 代表唯一索引,意思就是该索引是不是唯一索引 |
Key_name | 索引名称 |
Seq_in_index | 表示该字段在索引中的位置,单列索引的话该值为1 ,组合索引为每个字段在索引定义中的顺序(只需要知道单列索引该值就为1 ,组合索引为别的) |
Column_name | 表示定义索引的列字段 |
Sub_part | 表示索引的长度 |
Null | 表示该字段是否能为空值 |
Index_type | 表示索引类型 |
#检索记录行符合条件的10条数据
select * from table where xxx="xxx" limit 10;
#检索记录行符合条件的11-17条数据
select * from table where xxx="xxx" limit 7 offset 10;
#在实际使用中,我们可以直接把offset直接省略掉
select * from table where xxx="xxx" limit 10,7; #11-17条数据
select * from table where xxx="xxx" limit 7,10; #8-17条数据
简单的说存储过程就是具有名字的一段代码,用来完成一个特定的功能,如查找xx分到xx分的同学(xx分可根据老师心情输入参数)。
DELIMITER // #将结束符定义为//
CREATE PROCEDURE 存储过程名(参数) #无参就不写参数,只写括号
BEGIN
#sql语句集if\case...
END //
DELIMITER ; #将分隔符重新设置为分号(;)
#示例
DEIMITER //
CREATE PROCEDURE proc1()
BEGIN
SELECT * FROM user;
END //
DELIMITER ;
创建带有参数的存储过程 存储过程的参数有三种:
IN
:输入参数,也是默认模式,表示该参数的值必须在调用存储过程时指定,在存储过程中修改该参数的值不能被返回;OUT
:输出参数,该值可在存储过程内部被改变,并可返回;INOUT
:输入输出参数,调用时指定,并且可被改变和返回。call 存储过程名
查询在数据库中已经创建过的存储过程:
SHOW PROCEDURE STATUS WHERE db='数据库名';
查看存储过程的详细定义信息:
SHOW CREATE PROCEDURE 数据库.存储过程名;
删除存储过程:
DROP PROCEDURE [IF EXISTS] 数据库名.存储过程名;
可以把一系列要执行的操作称为事务,而事务管理就是管理这些操作要么完全执行,要么完全不执行。一般来说,一个程序中包含多个事务。
经典的事务举例:A
要给B
转钱,首先A
的钱减少了,但是突然的数据库断电了,导致无法给B
加钱,然后由于丢失数据,B
不承认收到A
的钱;在这里事务就是确保加钱和减钱两个都完全执行或完全不执行,如果加钱失败,那么不会发生减钱。
事务管理的意义:保证数据操作的完整性;
A
转钱给B
的前后两个人钱的总金额不会改变);通常事务并发会出现几种现象:1.脏读;2.不可重复读(幻读,幻影);3.丢失修改。
并发控制的主要技术:封锁,时间戳,乐观控制法,多版本并发控制.
针对上面所出现的问题,数据库提出了对应的解决方案,就是事务的隔离级别:
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
未提交读(Read uncommitted) | 可能 | 可能 | 可能 |
已提交读(Read committed) | 不可能 | 可能 | 可能 |
可重复度(Repeatable read) | 不可能 | 不可能 | 可能 |
可串行化(Serializable) | 不可能 | 不可能 | 不可能 |
未提交读:就是一个事务可以读取另一个未提交事务的数据。
已提交读:就是一个事务要等另一个事务提交后才能读取数据。( O\fracle
数据库的默认级别)
可重复读:就是在开始读取数据(事务开启)时,不再允许修改操作,也是 MySQL
数据库的默认隔离级别。
可串行化:意思是说这个事务执行的时候不允许别的事务 并发写 操作的执行。这是事务隔离的最高级别,虽然最安全最省心,但是效率太低,一般不会用。
select @@tx_isolation;
SET SESSION TRANSACTION ISOLATION LEVEL 事务级别;
在 MySQL
命令行的默认下,事务采用自动提交(autocommit=1
)模式。意味着,当你执行一个修改sql
语句,MySQL
会立刻更新存储到磁盘中。也就是会立马执行commit
操作。
因此开启一个手动事务必须使用begin
,start transaction
或者set autocommit=0
:
begin;
#或者
start TRANSACTION;
#或者
set autocommit=0;
commit
表示提交事务,即提交事务的所有操作。具体地说,就是将事务中所有对数据库的更新写回到磁盘上的物理数据库中,事务正常结束。
提交事务也有对应的三种方式:
commit;
#或者
commit TRANSACTION;
#或者
commit WORK;
提交事务,意味着将事务开始以来所执行的所有数据修改成为数据库的永久部分,因此也标志着一个事务的结束。一旦执行了该命令,将不能回滚事务。只有在所有修改都准备好提交给数据库时,才执行这一操作。
rollback
表示事务回滚,即在事务运行的过程中发生了某种故障,事务不能继续执行,系统将事务中对数据库的所有已完成的操作(对数据库的更新操作)全部撤销,回滚到事务开始时的状态,同时,系统将释放由事务控制的资源。因此,这条语句也标志着事务的结束。
事务内部的故障有的可以通过事务程序本身发现,更多的故障是非预期的,是不能由应用程序处理的(溢出、死锁)。
事务撤销(UNDO)
也称软故障,是指造成系统停止运转的任何事件,使得系统要重启。如操作系统故障、系统断电、DBMS代码错误。这类故障影响正在运行的所有事务,但不破坏数据库。
恢复子系统需要撤销所有未完成的事务外,还要重做(REDO)所有已提交的事务,使数据库真正恢复到一致的状态。
也称硬故障,硬故障指的是外存故障。如磁盘损坏、磁头碰撞、强磁干扰
# 恢复
恢复机制的关键问题:如何建立冗余数据,如何利用冗余数据恢复。
将整个数据库复制到其它存储介质上保存起来的过程。这些备用的数据称为后备副本(backup)或后援副本。但重装后备副本只能将数据库恢复到转储使的状态。
动态转储:转储期间允许对数据库进行存取或修改。
静态转储:转储期间不允许对数据库进行任何非转储操作。
增量转储:每次只转储上一次转储后更新的数据
海量转储:每次转储全部数据库
日志文件使用来记录事务对数据库的更新操作的文件.
登记日志文件的两个规则:
MySQL除了默认开启错误日志,其它日志默认都是关闭状态.
记录MySQL的启动、停止信息以及在MySQL运行过程中的错误信息,
二进制日志用于存储描述数据库更改的事件.常用于主从复制和数据恢复
常规查询日志提供了 MySQL 操作的常规记录
记录执行时间超过一定阈值并且需要查询检查给定数量的行的 SQL 语句.
# MySQL存储引擎
引擎:数据存在数据库中不同的格式和方法。
MySQL
最常用引擎:MyISAM
和InnoDB
,在MySQL 5.5.5
以前,默认的存储引擎为MyISAM
,之后版本默认为InnoDB
。InnoDB
对事物完整性更好以及有更高的并发性,下面了解一下他们之间的区别:
对比项 | MyISAM | InnoDB |
---|---|---|
主外键 | 不支持 | 支持 |
事务 | 不支持 | 支持 |
行表锁 | 锁。操作一条记录也会锁住整个表 | 行锁。操作时只锁某一行 |
缓存 | 只缓存索引,不缓存真实数据 | 不仅缓存索引,还缓存真实数据 |
表空间 | 小 | 大 |
关注点 | 性能 | 事务 |
默认安装 | 是 | 是 |
问:锁是什么,有什么用?
答:锁的主要作用是管理共享资源的并发访问,锁可以用于实现事务的隔离。
问:为什么要加锁?
答:为了避免多个事务同时操作数据库导致数据异常,一般会通过锁机制解决。
show open tables;
查看数据库中有没有加锁的表,若In_use
列中值为0
,说明表没有被锁。
也可以直接查被加锁的表:``show open tables where In_use>=1;`
共享锁(S
),又称为读锁,获得共享锁之后,针对同一份数据,多个读操作可以同时进行,互不影响,但无法修改和删除数据,除非加锁方commit。
在查询语句后面增加LOCK IN SHARE MODE
,MySQL(InnoDB)
会对查询结果中的每行都加共享锁:
select ... lock in share mode;
排他锁(X
),又称为写锁、独占锁。获得排他锁后,在当前写操作没有完成前,它会阻断其他写锁和读锁。
排它锁就是我在客户端 A
给数据 C
添加了排它锁,那么我在客户端 B
只能在客户端 A
commit
之后,才能select
数据。
换句话说,只要我在客户端 B
用锁进行了查询,那我我都需要等待 A
commit
之后,如果此时我客户端 B
不加锁,我是可以查询到的。
添加排它锁的方式:
select ... for update;
lock table 表名 read(write),表名2 read(write);
lock table account write,mylock read; #给account表上写锁,给mylock表上读锁:
unlock tables;
一级封锁协议: 解决了丢失修改问题,不能保证可重复读和不读脏数据
二级封锁协议: 解决了丢失修改和不读脏数据,不能保证可重复读
三级封锁协议: 解决了丢失修改,不读脏数据,可重复读等问题
TwoPhase Locking(2PL):扩展阶段(加锁) + 收缩阶段(解锁)
如果加锁和解锁交叉进行则违反两段锁协议
避免活锁的简单方法是采取先来先服务的策略
死锁的办法是提前预防或者诊断并解除死锁
declare err int default 0; #定义一个err变量并初始值为0
declare continue handler for sqlexception set err =1; #当有sql异常的时候,sql继续往下执行,并将变量err的值改变为1
有了变量的值接收是否有异常 SQL
,就能方便我们使用判断事务是否提交了,关于更多的自定义异常学习可以学习完本关后自行搜索资料学习。
文件结构:
flask_edu
├── app
| ├── __init__.py
| ├── models.py # 数据模型文件
| ├── templates # 用于存储html文件的文件夹
├── config.py # 配置文件
└── test.py # 操作数据库的文件
Flask
操作数据库有两种方式,一种是使用Flask-sqlalchemy
模块实现,另一种是直接使用pymysql
模块,通过编写sql
语句操作数据库。
SQLAlchemy
是一个基于Python
实现的ORM
框架。该框架建立在DB API
之上,使用关系对象映射进行数据库操作,简言之便是:将类和对象转换成SQL
,然后使用数据API
执行SQL
并获取执行结果。而Flask-SQLAlchemy
是Flask扩展,它将对SQLAlchemy
的支持添加到Flask
应用程序中。它比原始的操作数据库的方法更加方便简洁。使用该模块依然需要使用pymysql
连接的数据库,建立连接需要从两部分入手:
通过修改配置文件config.py来连接到Mysql数据库使配置生效,SQLAlchemy实例化数据库(本实训通过配置__init__.py实现)
实现连接数据库功能:
class Config()
SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://root:123456@localhost:3306/database'
# 'mysql+pymysql://用户名称:密码@ip:端口/数据库名称'
SQLALCHEMY_TRACK_MODIFICATIONS = False #该配置项用于设置数据发生变更之后是否发送信号给应用
创建Flask
实例,使config.py
文件生效:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from config import Config
app = Flask(__name__) # 创建Flask实例
db = SQLAlchemy(app)
app.config.from_object(Config)
数据库表模型,实现关系对象映射:
from app import db
class Demo(db.Model):
__table__ = "demo" #表名
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(255)) #id和name都为表中的字段名,
test.py
设置路由,以及编写查询语句等操作的文件:
from app import db
from models import Demo
#查询数据
Demo.query.all() #查询所有数据
Demo.query.all()[1:3] #查询第二到第四条数据
Demo.query.count() #查询有多少条数据
Demo.query.first() #查询第一条数据
Demo.query.filter_by(id=3).first()
Demo.query.filter(Demo.id==3).first() #这两条都用于查询id为3的数据,`filter_by`里面不能用`!= `还有`> <` 等等,所有`filter`用得更多,`filter_by`只能用`=`
Demo.query.filter(and_(Demo.name.startwith("B"),Demo.id.endswith(1))).all() # 查询name以'B'开头,id以1结尾的所有数据,与操作用and_,或用or_ ,非用not
Demo.query.order_by("id").all() #查询所有数据,并以id排序
常用的过滤器:
名称 | 说明 |
---|---|
filter() | 把过滤器添加到原查询上,返回一个新查询 |
filter_by() | 把等值过滤器添加到原查询上,返回一个新查询 |
limit() | 使用指定的值限制原查询返回的结果数量,返回一个新查询 |
offset() | 偏移原查询返回的结果,返回一个新查询 |
order_by() | 根据指定条件对原查询结果进行排序,返回一个新查询 |
group_by() | 根据指定条件对原查询结果进行分组,返回一个新查询 |
在Flask Web
应用程序中也可以使用原始SQL
对数据库执行增删改查操作。使用这种方法则不需要配置config.py
文件和models.py
文件,但是需要编写更多的SQL
语句。
创建一个Flask
实例,没有编写配置文件,所以不需要实现config.py
:
from flask import Flaskapp = Flask(__name__) # 创建Flask实例
设置路由,以及编写查询语句等操作的文件:
import pymysql
connect = pymysql.Connect(host='localhost', port=3306,user='root',passwd='123123',db='database',charset='utf8') #连接数据库
cursor = connnect.cursor()sql = "SELECT id,name FROM demo WHERE id = 2 " # sql语句
cursor.execute(sql)
result = cursor.fetchone() # 查看查询结果
conn = pymysql.connect(host='localhost', user='root',passwd='123456',charset='utf8')
#参数说明
host:数据库主机名,默认是用本地主机
user:数据库登陆名,默认是当前用户
passwd:数据库登陆的密码,默认为空
charset:连接时的编码格式,要求与数据库的编码一致
在安装MySQL
数据库环境时,会选择默认编码格式,建议选择utf8
格式;若登录名或密码错误时,返回如下错误:
pymysql.err.OperationalError: (1045, "Access denied for user 'a'@'localhost' (using password: YES)")
前面已经介绍过如何获取数据库连接对象,但是不能在这个对象上直接对数据库进行操作, 还需要获取对应的操作游标才能进行数据库的操作,游标是一种数据访问对象,可用于创建数据库和数据表,也可用于在表中迭代一组行或者向表中插入新行。
cursor = conn.cursor()
dbName = 数据库名称
conn.select_db(dbName)
#或者
conn.secect_db('数据库名称')
如果在创建数据库连接对象时指定了连接的数据库时,就不需要再指定数据库对象:
dbName = 你的数据库名称
conn = pymysql.connect(host='localhost', user='root',passwd='123456',charset='utf8',db=dbName)
MySQL
是使用SQL
语句对数据库进行操作,创建表可使用:
cursor.execute("create database dbName") #建库
cursor.execute("create table tablename (字段名 字段属性,字段名 字段属性,……)") #建表
sql = "insert into %s (indata, inNum, brand) values ('%s', '%s', '%s')" % (tablename, '2017-8-19', 1000, 'Chevrolet')
cursor.execute(sql) #sql语句和cursor分开
MySQL
支持的字段属性包括:
日期和时间数据类型。
data:3字节,日期,格式:2014-09-18
time:3字节,时间,格式:08:42:30
datetime:8字节,日期时间,格式:2014-09-18 08:42:30
timestamp:4字节,自动存储记录修改的时间
year:1字节,年份
数值数据类型。
tinyint:1字节,范围(-128~127)
smallint:2字节,范围(-32768~32767)
mediumint:3字节,范围(-8388608~8388607)
int: 4字节,范围(-2147483648~2147483647)
bigint:8字节,范围(+-9.22*10的18次方)
浮点型。
float(m, d):4字节,单精度浮点型,m总个数,d小数位
double(m, d):8字节,双精度浮点型,m总个数,d小数位
decimal(m, d):decimal是存储为字符串的浮点数
字符串数据类型。
char(n):固定长度,最多255个字符varchar(n):可变长度,最多65535个字符
tinytext:可变长度,最多255个字符text:可变长度,最多65535个字符
mediumtext:可变长度,最多2的24次方-1个字符
longtext:可变长度,最多2的32次方-1个字符
插入数据时使用insert
语句,需要指定每个字段的值,如:
sql = "insert into %s (indata, inNum, brand) values ('%s', '%s', '%s')" % (tablename, '2017-8-19', 1000, 'Chevrolet')
python
中可以用%
对字符串进行格式化操作,%s
是优先用str
()函数进行字符串转化,%
左侧的字符串称为格式化字符串,右侧是希望格式化的值,格式化字符串的%s
部分称为转换说明符,标记了需要插入转换值的位置。
Python
执行完select
语句后可以从数据库中获取到数据,但需要执行fetchxxx
语句后才能将数据取回本地进行操作。fetchxxx
语句包括:
fetchall() #接收全部的返回结果行
fetchmany(size=None) #接收size条返回结果行
fetchone() #返回一条结果行
另外还可以用下面的方法移动游标:
scroll(value, mode='relative') #指针移动value条
在完成插入之后需要将插入事务提交,否则会导致相应的表死锁,虽然不主动关闭连接也会过期,但是会较长时间占用mysql
宝贵的连接资源。
conn.commit()
conn.close()
安全性控制的防范对象是非法用户。
完整性检查和控制的防范对象是不合语义的数据和不正确的数据。
日志文件是用于记录对数据的所有更新操作。
日志文件用来记录对数据库中数据进行的每一次更新操作。