4729 2018-03-16 2020-06-26
前言:MySQL数据库开篇。
一、准备工作
1、mysql
1、安装
# mariadb是mysql的一个分支,完全兼容mysql
# 暂时在deepin中没有找到mysql版本信息,sudo apt-cache search mysql
hk-pc@hk-pc:~$ sudo apt-get install mariadb-server
# 默认root可直接免密登录,但其他用户即使知道密码也无法登录
sudo mysql -uroot -p
# 让其他用户可以凭借密码登录
MariaDB [(none)]> UPDATE mysql.user SET authentication_string = PASSWORD('mypassword'), plugin = 'mysql_native_password' WHERE User = 'root' AND Host = 'localhost';
MariaDB [(none)]> FLUSH PRIVILEGES;
MariaDB [(none)]> exit
service mysql restart
# 笔者centos服务器可以直接搜到mysql8,可以直接安装
yum install mysql-community-server.x86_64
# 记第一次centos安装mysql8,输入以下命令显示初始密码
sudo grep 'temporary password' /var/log/mysqld.log
2、常用命令
命令 | 说明 |
---|---|
service mysql status | 显示服务状态 |
service mysql stop | 停止服务 |
service mysql start | 开启服务 |
service mysql restart | 重启服务 |
mysql> status; | 查看mysql版本 |
mysql> set password for root@localhost = password("123456"); | 修改密码方式一 |
mysql> ALTER USER 'root'@'localhost' IDENTIFIED BY 'MyNewPass4!'; | 修改密码方式二 |
注:对于centos和mysql8,上述mysql可替换为mysqld(本人服务器环境)。
3、向外暴露连接
- 首先编辑文件/etc/mysql/mysql.conf.d/mysqld.cnf,注释掉bind-address = 127.0.0.1或在my.cnf下mysqld节点添加bind-address=0.0.0.0。
# 执行以下命令看查看mysql配置文件
[root@izwz93pwxi9qhdp46ajo0qz ~]# mysql --help | grep cnf
order of preference, my.cnf, $MYSQL_TCP_PORT,
/etc/my.cnf /etc/mysql/my.cnf /usr/etc/my.cnf ~/.my.cnf
- 重启服务,进入mysql命令行,依次执行命令
# 针对mysql8以前版本
grant all privileges on *.* to 'root'@'%' identified by 'root密码';
flush privileges;
# 先查看阿里ECS是否开放了该端口
# 特别的,针对mysql8,有如下命令
# mysql8需要手动创建用户
mysql> create user 'root'@'%' identified by 'root密码';
Query OK, 0 rows affected (0.10 sec)
# 手动授权
mysql> grant all privileges on *.* to 'root'@'%';
Query OK, 0 rows affected (0.00 sec)
# 更改加密方式
mysql> alter user 'root'@'%' identified with mysql_native_password by 'root密码!';
Query OK, 0 rows affected (0.08 sec)
2、字符集
mysql服务字符集和数据库字符集默认为latin1,这个可以通过配置文件修改,可以有两种方式解决(1是建表时指定字符集,2是修改默认字符集-/ect/mysql/my.cnf),修改内容如下
[mysql]
# 修改数据库默认字符集,也可以指定为utf8mb4以存储表情字符
default-character-set=utf8
[mysqld]
# 修改服务器连接默认字符集
character_set_server=utf8
3、导入和导出
如果没有Navicat,那么此时就需要用到命令了
# 导出单个数据库中的所有表
mysqldump -u root -p newxiaokui>~/newxiaokui.sql
# 导入单个数据库(数据库必须已存在)
mysql -u root -p newxiaokui</home/newxiaokui.sql
# 以下为完整示例
MariaDB [test_dj]> create table temp(id int, name varchar(32), primary key(id));
MariaDB [test_dj]> insert into temp values(1, '11'), (2, '22');
Query OK, 2 rows affected (0.00 sec)
Records: 2 Duplicates: 0 Warnings: 0
MariaDB [test_dj]> select * from temp;
+----+------+
| id | name |
+----+------+
| 1 | 11 |
| 2 | 22 |
+----+------+
2 rows in set (0.00 sec)
# 导出库
MariaDB [test_dj]> system mysqldump -uroot -p test_dj>~/test_dj.sql;
Enter password:
MariaDB [test_dj]> system vi ~/test_dj.sql
# 导出表
MariaDB [test_dj]> system mysqldump -uroot -p test_dj temp>~/temp.sql;
Enter password:
MariaDB [test_dj]> system vi ~/temp.sql
# 导入库 数据库必须已存在
MariaDB [(none)]> system mysql -uroot -p199710 test_dj < ~/test_dj.sql;
MariaDB [(none)]> system mysql -uroot -p199710 test_dj < ~/test_dj.sql;
# 导入表
MariaDB [test_dj]> drop table temp;
Query OK, 0 rows affected (0.02 sec)
MariaDB [test_dj]> system mysql -uroot -p199710 test_dj < ~/temp.sql
二、SQL
1、学生表student
表字段为sno、sname、ssex、sage、sdept,sno为主键。
学号(sno) | 姓名(sname) | 性别(ssex) | 年龄(sage) | 所在系(sdept) |
---|---|---|---|---|
95001 | 李勇 | 男 | 20 | 计算机系 |
95002 | 李晨 | 女 | 19 | 数学系 |
95003 | 王敏 | 女 | 18 | 经济系 |
95004 | 张立 | 男 | 19 | 数学系 |
2、课程表course
表字段为cno、cname、cpno、credit,cno为主键。
课程号(cno) | 课程名(cname) | 先行课(cpno) | 学分(credit) |
---|---|---|---|
1 | 数据库 | 5 | 4 |
2 | 数学 | 2 | |
3 | 信息系统 | 1 | 4 |
4 | 操作系统 | 6 | 3 |
5 | 数据结构 | 7 | 4 |
6 | 数据处理 | 2 | |
7 | Python | 6 | 4 |
3、学生选修表sc
表字段为sno、cno、grade,sno与cno为主键。
学号(sno) | 课程号(cno) | 成绩(grade) |
---|---|---|
95001 | 1 | 90 |
95001 | 2 | 85 |
95001 | 3 | 88 |
95002 | 2 | 90 |
95002 | 3 | 80 |
95002 | 4 | 60 |
95003 | 5 | 70 |
95003 | 6 | 50 |
95003 | 7 | 60 |
95004 | 1 | 50 |
95004 | 2 | 50 |
注:后面更新/添加了部分数据,所以前面写的sql会有部分出入。
4、插入上述数据
真实操作,在Linux下依次输入以下命令
service mysql start
mysql -uroot -p
create database test_sql;
use test_sql;
create table student(sno int(11) primary key, sname varchar(16), ssex char(8), sage int(2), sdept varchar(16));
create table course(cno int(11) primary key, cname varchar(16), cpno int(11), credit int(2));
create table sc(sno int(11), cno int(11), grade int(2), primary key(cno, sno));
insert into student values ('95001','LiYong','male','20','computer');
insert into student values ('95002','LiuChen','female','19','jinji'),('95003','WangMin','female','18','math'),('95004','ZhangLi','male','19','computer');
insert into course values(1, 'database', 5, 4);
insert into course values(2, 'math', null, 2);
insert into course values(3,'information',1,4),(4,'os',6,3),(5,'ds',7,4),(6,'dd',null,2),(7,'python',6,4);
insert into sc values(95001,1,90);
insert into sc values(95001,2,85),(95001,3,88),(95002,2,90),(95002,3,80),(95002,4,60);
insert into sc values(95003,5,70),(95003,6,50),(95003,7,60);
insert into sc values(95004,1,50),(95004,2,50);
5、单表查询
- 查询学生表中所有学生的姓名、性别及年龄信息
# 结果如下
mysql> select sname, ssex, sage from student;
+---------+--------+------+
| sname | ssex | sage |
+---------+--------+------+
| LiYong | male | 20 |
| LiuChen | female | 19 |
| WangMin | female | 18 |
| ZhangLi | male | 19 |
+---------+--------+------+
4 rows in set (0.00 sec)
- 查询学生表中年龄最大的女性的名字(where)
# 结果如下,limit 1 = limit 0, 1 起始位置 + 行数
mysql> select sname,sage from student where ssex = 'female' order by sage desc limit 1;
+---------+------+
| sname | sage |
+---------+------+
| LiuChen | 19 |
+---------+------+
1 row in set (0.00 sec)
# 查询年龄第二大的女学生名字
mysql> select sname, ssex, sage from student where ssex = 'female' order by sage desc limit 1, 2;
+---------+--------+------+
| sname | ssex | sage |
+---------+--------+------+
| WangMin | female | 18 |
+---------+--------+------+
1 row in set (0.01 sec)
- 按年龄排序,查询学生表中排行第三的学生的名字(limit)
limit用法为limit + [位置偏移量]-可选 + 显示行数。
# 先查看所有
mysql> select sname, sage from student order by sage asc, sname asc;
+---------+------+
| sname | sage |
+---------+------+
| WangMin | 18 |
| LiuChen | 19 |
| ZhangLi | 19 |
| LiYong | 20 |
+---------+------+
4 rows in set (0.00 sec)
# 确定目标
mysql> select sname, sage from student order by sage asc, sname asc limit 2,1;
+---------+------+
| sname | sage |
+---------+------+
| ZhangLi | 19 |
+---------+------+
1 row in set (0.00 sec)
- 统计学生年龄段人数(group by)
group by关键字通常与集合函数一起使用,如max()、min()、count()、sum()、avg()等。
# 统计年龄分段
mysql> select sage, count(sage) from student group by sage;
+------+-------------+
| sage | count(sage) |
+------+-------------+
| 18 | 1 |
| 19 | 2 |
| 20 | 1 |
+------+-------------+
3 rows in set (0.00 sec)
# 一种更高级的实现
mysql> select sage,count(sage),group_concat(sname) as names from student group by sage;
+------+-------------+-----------------+
| sage | count(sage) | names |
+------+-------------+-----------------+
| 18 | 1 | WangMin |
| 19 | 2 | LiuChen,ZhangLi |
| 20 | 1 | LiYong |
+------+-------------+-----------------+
3 rows in set (0.00 sec)
group by还可以与having关键字配合(高级,此处涉及到多表查询)。
# 查询课程被选次数超过2的课程名称及具体次数
# 首先看下具体的课程信息,得知正确信息应是cno为2的课程信息,次数为3
mysql> select * from sc order by cno;
+-------+-----+-------+
| sno | cno | grade |
+-------+-----+-------+
| 95001 | 1 | 90 |
| 95004 | 1 | 50 |
| 95001 | 2 | 85 |
| 95002 | 2 | 90 |
| 95004 | 2 | 50 |
| 95001 | 3 | 88 |
| 95002 | 3 | 80 |
| 95002 | 4 | 60 |
| 95003 | 5 | 70 |
| 95003 | 6 | 50 |
| 95003 | 7 | 60 |
+-------+-----+-------+
11 rows in set (0.00 sec)
# 不能为后面的count(cno)取别名,前面的可以
mysql> select cno, count(cno) from sc group by cno having count(cno) > 2;
+-----+------------+
| cno | count(cno) |
+-----+------------+
| 2 | 3 |
+-----+------------+
1 row in set (0.00 sec)
mysql> select cno, count(cno) as n from sc group by cno having n > 2;
+-----+---+
| cno | n |
+-----+---+
| 2 | 3 |
+-----+---+
1 row in set (0.00 sec)
# 完整sql,看来同学们还是比较喜欢数学的
mysql> select course.cno, course.cname, t.n from course, (select cno, count(cno) as n from sc group by cno having n > 2) as t where course.cno = t.cno;
+-----+-------+---+
| cno | cname | n |
+-----+-------+---+
| 2 | math | 3 |
+-----+-------+---+
1 row in set (0.00 sec)
# 内连接版本
mysql> select course.cno, course.cname, t.n from course inner join (select cno, count(cno) as n from sc group by cno having n > 2) as t on t.cno = course.cno;
+-----+-------+---+
| cno | cname | n |
+-----+-------+---+
| 2 | math | 3 |
+-----+-------+---+
1 row in set (0.00 sec)
- 查询先行课程为空的课程名称及学分(null)
mysql> select cname, credit from course where cpno is null;
+-------+--------+
| cname | credit |
+-------+--------+
| math | 2 |
| dd | 2 |
+-------+--------+
2 rows in set (0.00 sec)
mysql> select cname, credit from course where cpno is not null;
+-------------+--------+
| cname | credit |
+-------------+--------+
| database | 4 |
| information | 4 |
| os | 3 |
| ds | 4 |
| python | 4 |
+-------------+--------+
5 rows in set (0.00 sec)
6、多表查询
- 查询选修3号课程的学生的学号、姓名及成绩,分数降序排列(两表)
mysql> select sno, grade from sc where sno = 3 order by grade desc;
+-----+-------+
| sno | grade |
+-----+-------+
| 3 | 88 |
| 3 | 80 |
+-----+-------+
2 rows in set (0.00 sec)
# 这里的sc.sno可以替换为student.sno,但是不能为sno
mysql> select sc.sno, sname, grade from student,sc where student.sno = sc.sno and cno = 3 order by grade desc;
+-------+---------+-------+
| sno | sname | grade |
+-------+---------+-------+
| 95001 | LiYong | 88 |
| 95002 | LiuChen | 80 |
+-------+---------+-------+
2 rows in set (0.00 sec)
# 使用where子句定义连接条件比较简单明了,但在某些时候会影响查询性能,而内连接语法是ANSI SQL的标准规范
mysql> select sc.sno,sname,grade from sc inner join student on sc.sno = student.sno where sc.cno = 3 order by grade asc;
+-------+---------+-------+
| sno | sname | grade |
+-------+---------+-------+
| 95002 | LiuChen | 80 |
| 95001 | LiYong | 88 |
+-------+---------+-------+
- 查询所有同学的姓名、所选课程名及分数信息(三表)
mysql> select sname,cname,grade from student,sc,course where student.sno = sc.sno and sc.cno = course.cno;
+---------+-------------+-------+
| sname | cname | grade |
+---------+-------------+-------+
| LiYong | database | 92 |
| LiYong | math | 85 |
| LiYong | information | 88 |
| LiuChen | math | 90 |
| LiuChen | information | 80 |
+---------+-------------+-------+
5 rows in set (0.00 sec)
# 内连接
mysql> select sname,cname,grade from sc inner join student on sc.sno = student.sno inner join course on sc.cno = course.cno;
+---------+-------------+-------+
| sname | cname | grade |
+---------+-------------+-------+
| LiYong | database | 92 |
| LiYong | math | 85 |
| LiYong | information | 88 |
| LiuChen | math | 90 |
| LiuChen | information | 80 |
+---------+-------------+-------+
5 rows in set (0.00 sec)
- 查询选修了2门以上的课程的学生学号及姓名(group by)
# 与 查询课程被选次数超过2的课程名称及具体次数 类似
mysql> select course.cno, course.cname, t.n from course inner join (select cno,count(cno) as n from sc group by cno having n > 2) as t on t.cno = course.cno;
# 当然也可以这么写,后者更加简洁
mysql> select course.cno, course.cname, count(sc.cno) from sc inner join course on sc.cno = course.cno group by sc.cno having count(sc.cno) > 2;
mysql> select student.sno, sname, count(cno) from sc,student where sc.sno = student.sno group by sc.sno having count(sc.sno) > 2;
+-------+--------+------------+
| sno | sname | count(cno) |
+-------+--------+------------+
| 95001 | LiYong | 3 |
+-------+--------+------------+
1 row in set (0.00 sec)
mysql> select sc.sno, sname, count(cno) from sc inner join student on sc.sno = student.sno group by sc.sno having count(sc.sno) > 2;
+-------+--------+------------+
| sno | sname | count(cno) |
+-------+--------+------------+
| 95001 | LiYong | 3 |
+-------+--------+------------+
1 row in set (0.00 sec)
- 左、右外连接
Left join(左连接):返回左表中的所有记录和右表中与连接字段相等的记录。
Right join(右连接):返回右表中的所有记录和左表中与连接字段相等的记录。
两者可以左右交换来等价替换。
# 为了描述方便,我们新建95005号学生
mysql> insert into student values(95005, 'test', 'man', 10, 'test');
Query OK, 1 row affected (0.00 sec)
# 左外连接,以studet为左,等价于以sc为右,显示所有学生的选课情况
# 以左为尊,即A left outer B / A right outer B,其中出发点都是A
mysql> select student.sno, student.sname, sc.cno from student left outer join sc on student.sno = sc.sno;
+-------+---------+------+
| sno | sname | cno |
+-------+---------+------+
| 95001 | LiYong | 1 |
| 95004 | ZhangLi | 1 |
| 95001 | LiYong | 2 |
| 95002 | LiuChen | 2 |
| 95004 | ZhangLi | 2 |
| 95001 | LiYong | 3 |
| 95002 | LiuChen | 3 |
| 95002 | LiuChen | 4 |
| 95003 | WangMin | 5 |
| 95003 | WangMin | 6 |
| 95003 | WangMin | 7 |
| 95005 | test | NULL |
+-------+---------+------+
12 rows in set (0.00 sec)
# 右外连接,以course为右,显示所有课的选择情况
mysql> select course.cno, course.cname, sc.sno from sc right outer join course on sc.cno = course.cno;
+-----+-------------+-------+
| cno | cname | sno |
+-----+-------------+-------+
| 1 | database | 95001 |
| 1 | database | 95004 |
| 2 | math | 95001 |
| 2 | math | 95002 |
| 2 | math | 95004 |
| 3 | information | 95001 |
| 3 | information | 95002 |
| 4 | os | 95002 |
| 5 | ds | 95003 |
| 6 | dd | 95003 |
| 7 | python | 95003 |
| 8 | test | NULL |
+-----+-------------+-------+
12 rows in set (0.00 sec)
# 左 + 右
mysql> select course.cno, course.cname, sc.sno, student.sname from sc right outer join course on sc.cno = course.cno left outer join student on sc.sno = student.sno;
+-----+-------------+-------+---------+
| cno | cname | sno | sname |
+-----+-------------+-------+---------+
| 1 | database | 95001 | LiYong |
| 2 | math | 95001 | LiYong |
| 3 | information | 95001 | LiYong |
| 2 | math | 95002 | LiuChen |
| 3 | information | 95002 | LiuChen |
| 4 | os | 95002 | LiuChen |
| 5 | ds | 95003 | WangMin |
| 6 | dd | 95003 | WangMin |
| 7 | python | 95003 | WangMin |
| 1 | database | 95004 | ZhangLi |
| 2 | math | 95004 | ZhangLi |
| 8 | test | NULL | NULL |
+-----+-------------+-------+---------+
12 rows in set (0.00 sec)
- 一条稍稍复杂的sql
select a.ORD_DATE, a.BIZ_CODE, a._POINT, b.PARTNER_ID, b.PARTNER_NAME from (select ORD_DATE, BIZ_CODE, APP_ID, sum(POINT) as _POINT from pc_orders group by ORD_DATE, BIZ_CODE, APP_ID) as a inner join pc_partner_app as b on a.APP_ID = b.APP_ID
具体含义这里就不解释,稍稍记录一下。
7、正则查询
下面是一些常见的正则表达式
选项 | 说明 | 例子 | 匹配值示例 |
---|---|---|---|
^ | 匹配文本的开始字符 | ^b:匹配以字母b开头的字符串 | book,big |
$ | 匹配结束字符 | st$:匹配以st结尾的字符串 | test,resist |
. | 匹配任意单字符 | b.t:匹配b和t之间的一个字符 | bit,but |
* | 匹配零个或多个在它前面的字符 | f*n:f和n之前可以任意个字符 | fn,fan |
+ | 匹配前面的字符1次或多次 | ba+:匹配以b开头后面紧跟1个或多个a的字符串 | ba,bare,battle |
字符串 | 匹配包含指定字符串的文本 | fa:匹配包含“fa”的字符串 | fan,father |
[字符集合] | 匹配字符集合中的任何一个字符 | [xz]:匹配x或者z | dizzy,extra |
[^字符集合] | 匹配不在括号中的任何字符 | [^abc]: 匹配任何不包含a、b或c的字符串 | desk,fox |
字符串{n} | 匹配前面的字符串至少n次 | b{2}:匹配有2个或更多的b字符的字符串 | bb,abbba |
字符串{n,m} | 匹配字符至少n次,至多m次 | b{2,4}:匹配最少有2个,最多4个的b字符串 | bb,abbbba |
示例如下
# 所有学生信息
mysql> select * from student;
+-------+---------+--------+------+----------+
| sno | sname | ssex | sage | sdept |
+-------+---------+--------+------+----------+
| 95001 | LiYong | male | 20 | computer |
| 95002 | LiuChen | female | 19 | jinji |
| 95003 | WangMin | female | 18 | math |
| 95004 | ZhangLi | male | 19 | computer |
+-------+---------+--------+------+----------+
# 查询系以co开头,er结尾的
mysql> select * from student where sdept regexp '[^co][er$]';
+-------+---------+------+------+----------+
| sno | sname | ssex | sage | sdept |
+-------+---------+------+------+----------+
| 95001 | LiYong | male | 20 | computer |
| 95004 | ZhangLi | male | 19 | computer |
+-------+---------+------+------+----------+
2 rows in set (0.00 sec)
# 查询名字中包含ong或in的
mysql> select * from student where sname regexp 'ong|in';
+-------+---------+--------+------+----------+
| sno | sname | ssex | sage | sdept |
+-------+---------+--------+------+----------+
| 95001 | LiYong | male | 20 | computer |
| 95003 | WangMin | female | 18 | math |
+-------+---------+--------+------+----------+
2 rows in set (0.00 sec)
# 查询包含a~c和数字1~3,太复杂的就先放一放吧
mysql> select * from student where sdept regexp '[a-c1-3]';
+-------+---------+--------+------+----------+
| sno | sname | ssex | sage | sdept |
+-------+---------+--------+------+----------+
| 95001 | LiYong | male | 20 | computer |
| 95003 | WangMin | female | 18 | math |
| 95004 | ZhangLi | male | 19 | computer |
+-------+---------+--------+------+----------+
3 rows in set (0.00 sec)
三、理论基础
1、范式
一范式:表中数据项不可再分,是最小项(原子项)。
例如:【联系人表】(姓名,性别,电话),其中电话项可再分为【联系人表】(姓名,性别,家庭电话,公司电话)。由于电话不是最小项,可再分,所以前者不符合一范式,后者符合一范式。
二范式:首先必须满足一范式,二是表必须有一个主键,三是没有包含在主键中的列必须完全依赖于主键,而不能只依赖于主键的一部分。
例如:【订单明细表】(订单ID,产品ID,单价,数量)与【产品表】(产品ID,产品名称),其中的单价项依赖于产品ID,可把单价划分到产品表,如【产品表】(产品ID,产品名称,单价)。由于单价并不是依赖于完整主键,所以前者不符合二范式,后者符合二范式。
三范式:首先必须满足二范式,二是非主键列必须直接依赖于主键,不能存在传递依赖。即不能存在:非主键列 A 依赖于非主键列 B,非主键列 B 依赖于主键的情况。
例如:【成绩表】(学生ID,课程ID,成绩)与【学生信息表】(学生ID,所属院系,院系地址),其中的院系地址依赖于所属院系,而所属院系依赖与学生ID,存在传递依赖。可修改为【学生信息表】(学生ID,所属院系)与【院系表】(所属院系,院系地址),以符合三范式。
其中二范式和三范式比较容易混淆,区分的关键是非主键列是否依赖部分主键/完整主键(2NF),是否存在传递依赖(3NF)。
2、事务
- ACID
事务是用户定义的一个数据库操作序列,这些操作要么全做,要么全不做,是一个不可分割的工作单位。
事务具有4个特性:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持续性(Durability)/永久性(Permanence)。
下面对事务的ACID个特性做简单说明:
原子性:事务是数据库的逻辑工作单位,其中包括的所有操作要么都做,要么都不做。
一致性:事务执行的结果必须是使数据库从一个一致性变到另一个一致性状态。比如A+100,那么B必须-100,两者缺一不可,必须都做或都不做,这点与原子性密切相关。
隔离性:一个事务的执行不能被其他事务干扰,并发执行的各个事务之间不能互相干扰。
永久性:持续性又称永久性,指一个事务一旦提交,它对数据库中的数据的改变就应该是永久的的,不能够被任意撤销。
- 隔离级别
多个事务的并发操作可能破坏事务的ACID特性,为了保证事务的隔离性和一致性,数据库需要对并发操作进行正确的调度。并发操作带来的数据不一致性包括丢失修改、不可重复读、和读“脏”数据。
丢失修改(lost update):两个事务T1和T2读入同一数据并修改,T1先提交,T2后提交的结果破坏了T1提交的结果,导致T1的修改被丢失。
不可重复读(non-repeatable read):不可重复读是指事务T1读取数据后,事务T2执行对数据进行了更新/删除/添加,导致T1再次读取时,发现两次读取不一致。其中,对于删除和添加两种情况,又称为幻读。
读“脏”数据(dirty read):读“脏”数据是指事务T1修改某一数据并将其写回磁盘,事务T2读取同一数据后,T1由于某种原因被撤销,这时被T1修改过的数据恢复原值,T2读到的数据就与数据库中的数据不一致,则T2读到的数据就为“脏”数据。
并发控制的主要技术有封锁(locking)、时间戳(timestamp)、乐观控制法(optimistic scheduler)和多版本并发控制(mulit-version concurrency control,MVCC)等,这里就不细讲了。
最后就是隔离级别了,MySQL数据库提供了四种隔离级别(来源于网络):
串行化 (Serializable):可避免脏读、不可重复读、幻读的发生。是事务的最高隔离级别,他会强制对事务进行排序,使之不会发生冲突,从而解决脏读、幻读、重复读的问题。实际上,就是在每个读的数据行上加锁,这个级别可能导致大量的超时现象和锁竞争,实际应用中很少使用。
可重复读 (Repeatable read):是MySQL默认的事务隔离级别,它可以避免脏读,不可重复读的问题,确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。但理论上,该级别会出现幻读的情况,不过MySQL的存储引擎通过多版本并发控制机制,解决了该问题,因此该级别是可以避免幻读的。可以简单理解为,为数据加了读锁,在读锁未释放之前不能修改,因此读到的总是不变的。
读已提交 (Read committed):大多数的数据库管理系统的默认级别都是READ COMMITTED(提交读),该级别下的事务只能读取其他事务已经提交的内容,可以避免脏读,但不能避免不可重复读和幻读(属于不可重复读的一部分)的情况。不可重复读就是在事务内重复读取了别的线程已经提交的数据,但两次读取的结果不一致,原因是查询的过程中其他事务做了更新的操作。幻读是指在一个事务内两次查询中数据条数不一致,原因是查询的过程中,其他的事务做了添加/删除操作。例如事务1准备把A = 100写成A = 200,事务2在事务1提交之前读到A = 100,并将A改为0。事务2提交后,事务1读到A = 0,并将其改为200并提交。
读未提交(Read uncommitted ):READ UNCOMMITTED(未提交读)是事务中最低的级别,该级别下的事务可以读取到另一个事务中未提交的数据,基本没有任何保险措施,也被称为脏读(Dirty Read),这是相当危险的。由于该级别比较低,在实际开发中避免不了任何情况,所以一般很少使用。例如事务1准备把A = 100写成A = 200,而在事务1未提交之前,事务2看到的都是A = 100。
3、索引
参考这篇,写得真好。
总访问次数: 299次, 一般般帅 创建于 2018-03-16, 最后更新于 2020-06-26
欢迎关注微信公众号,第一时间掌握最新动态!