mysql事务学习

mysql事务学习

mysql的基础架构

Mysql是由SQL接口,解析器,优化器,缓存,存储引擎组成的。

  1. connectors(连接管理器):与其他编程语言中的sql 语句进行交互

  2. Management Serveices & Utilities:系统管理和控制工具

  3. Connection Pool (连接池):管理缓冲用户连接,线程处理等需要缓存的需求

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

  5. Parser (解析器):QL命令传递到解析器的时候会被解析器验证和解析。

    主要功能:

    ​ a . 将SQL语句分解成数据结构,并将这个结构传递到后续步骤,后面SQL语句的传递和处理就是基于这个结构的

    ​ b. 如果在分解构成中遇到错误,那么就说明这个sql语句是不合理的,语句将不会继续执行下去

  6. Optimizer (查询优化器):SQL语句在查询之前会使用查询优化器对查询进行优化(产生多种执行计划,最终数据库会选择最优化的方案去执行,尽快返会结果) 他使用的是“选取-投影-联接”策略进行查询。

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

    ​ 这个缓存机制是由一系列小缓存组成的。比如表缓存,记录缓存,key缓存,权限缓存等

  8. Engine (存储引擎):存储引擎是MySql中具体的与文件打交道的子系统。也是Mysql最具有特色的一个地方。

    ​ Mysql的存储引擎是插件式的。它根据MySql AB公司提供的文件访问层的一个抽象接口来定制一种文件访问机制(这种访问机制就叫存储引擎)

mysql访问流程:

面向用户的一端叫做:连接管理器

连接管理器负责接收用户连接,并建立用户连接

然后,到达查询缓存和分析器(如果缓存中有结果,那么直接从告诉缓存中返回给用户(只和读操作的查询操作有关系);如果缓存没有命中,那么交给分析器,做四大分析,如果分析的结果,发现缓存中有结果,再交给缓存,否则向下给优化器)

优化器完成优化,交给存储引擎(真正的执行者),然后交给存储端

连接管理器需要做的事:

  • 接受请求

  • 创建线程

  • 认证用户

  • 建立安全连接

mysql并发控制

并发控制:两个(或更多)用户,试图同时读写同一个数据,都会带来并发控制问题

myslq依赖的并发技术:

锁:读锁,写锁

时间戳:记录每个事务的启动执行时间

MVCC多版本快照隔离

多版本并发控制(MVCC):每一个用户在操作数据的时候,都不是操作原数据,而是数据的一个副本(快照),以时间戳为标志谁先谁后。

mysql的读写锁

锁:是最简单的并发控制机制(并不是多版本并发控制的机制)

读锁:共享锁,允许同时读,不允许写

写锁:独占锁,既不允许其他用户读,也不允许写

mysql对表手动加锁方式:

mysql> HELP LOCK 
Name: 'LOCK'
Description:
Syntax:
LOCK TABLES
    tbl_name [[AS] alias] lock_type
    [, tbl_name [[AS] alias] lock_type] ...
//  tbl_name:表名     lock_type:锁类型
lock_type:
    READ [LOCAL]
  | [LOW_PRIORITY] WRITE

UNLOCK TABLES   //解锁

加锁示例:


MariaDB [fsx]> SHOW TABLES;
+---------------+
| Tables_in_fsx |
+---------------+
| fsx           |
+---------------+
MariaDB [fsx]> DESC fsx;
+-------+---------------------+------+-----+---------+----------------+
| Field | Type                | Null | Key | Default | Extra          |
+-------+---------------------+------+-----+---------+----------------+
| cid   | tinyint(3) unsigned | NO   | PRI | NULL    | auto_increment |
| name  | varchar(30)         | NO   |     | NULL    |                |
| sex   | enum('boy','sex')   | YES  |     | NULL    |                |
| age   | tinyint(4)          | NO   |     | NULL    |                |
+-------+---------------------+------+-----+---------+----------------+
MariaDB [fsx]> LOCK TABLES fsx READ;
Query OK, 0 rows affected (0.00 sec)
MariaDB [fsx]> INSERT INTO fsx (name,sex,age) VALUES("pqy","girl",21);
ERROR 1099 (HY000): Table 'fsx' was locked with a READ lock and can't be updated
MariaDB [fsx]> UNLOCK TABLES;
Query OK, 0 rows affected (0.00 sec)

MariaDB [fsx]> INSERT INTO fsx (name,sex,age) VALUES("pqy","girl",21);
Query OK, 1 row affected, 1 warning (0.07 sec)

mysql服务器支持表级锁,行锁需要由存储引擎完成

  • 表锁

  • 页锁

  • 行锁

mysql事务


当需要对数据表执行一系列多个操作的情况下,为了防止这些操作中的部分操作执行成功而另一些操作执行失败,从而导致数据不正确,我们就需要使用事务了。

事务:多项操作指向一个处理单元,要么同时执行,要么都不执行。一个事务需要大量的cpu和io操作。 一般能够启动事务的操作:一堆sql语句,ODBC中启动事务的指令。

一个RDBMS支持事务,指的是能够满足四种ACID测试:

  • 原子性:操作不可被打断

  • 一致性:事务完成前后结果一致

  • 隔离性:两个事务同时执行,第一个事务在完成之前不能被第二个事务所知道,即事务之间互不影响

  • 持久性:即使服务器down机,数据不能丢失

mysql是插件式存储引擎,事务处理能力是由引擎提供的,MYISAM不支持事务,INnoDB支持事务。

一致性实现

一致性:在隔离状态下,数据库一定要从一个状态平稳转换到另一个状态,两个状态的状态基不变,服务器状态不变。如:

A账户原本有1000rmb,给B账户转500rmb,B账户原本也是1000rmb,转账结束后,AB账户金钱总和应该是200rmb不变

事务日志:可以完成对突然down机而未为完成的事务进行还原,从而提高ACID兼容性

  • 重作日志(redo log):每一个操作在真正写入数据库之前写入日志中,即可以根据日志无限重作

  • 撤销日志(undo log):在操作之前把原有的状态保留,万一需要还原,可以进行撤销所有操作

所有的mysql操作,先在内存中完成,事务结束后,写入到日志中,过一段时间,写入磁盘空间(数据文件),即由重作日志再完成一次

所以,在事务中,所有操作都是执行了两次,一次日志,一次磁盘数据文件。在日志中的操作是特别快的,因为日志仅仅记录操作,而不是操作数据本身。

如果一个事物结束,写入到了日志,但是从日志往数据文件中写入的时候,突然down机:

在每次启动时,mysql都会先读取日志,然后进行同步,也就是修复过程。

一般情况下,日志文件有两个,叫日志组,写满一个时,另一个接管,写满的日志文件去同步。日志文件不是越大越好,根据事务进行选择。切事务日志和数据文件不再同一磁盘上,保证一个磁盘坏了,可以还原。(牵扯到备份技术,和raid技术)

隔离性实现

如何启动事务,看下一节介绍

尽量让不同事务之间的干扰降低,所以让事务进行串行执行,但是这又失去了并发能里。为了折中解决该问题,sql引入了隔离级的技术,共有四种隔离级:

  • READ-UNCOMMITTED 读未提交:别的事务操作,立刻可以发现,这是最低的隔离级别

  • READ-COMMITTED 读提交:别的事务提交后,才能看到

  • REPEATABLE-READ 可重读:无论提交与否,至始至终,看到的数据不变

  • SERIABLIZABLE串行:

由上往下,并发能力越来越低,串行,隔离能力越来越高mysql默认是REPEATABLE READ


MariaDB [fsx]> SHOW GLOBAL VARIABLES LIKE '%iso%';  //全局变量
+---------------+-----------------+
| Variable_name | Value           |
+---------------+-----------------+
| tx_isolation  | REPEATABLE-READ |
+---------------+-----------------+
//要想永久生效,修改配置文件
MariaDB [fsx]> SET GLOBAL tx_isolation="REPEATABLE-READ"; //临时修改全局变量
Query OK, 0 rows affected (0.00 sec)
MariaDB [fsx]> SET tx_isolation="READ-UNCOMMITTED"; //临时修改局部变量
Query OK, 0 rows affected (0.00 sec)
MariaDB [fsx]> SELECT @@tx_isolation;
+------------------+
| @@tx_isolation   |
+------------------+
| READ-UNCOMMITTED |
+------------------+

事务隔离级别对事务的影响

读未提交

打开两个mysql会话,都设置隔离级别未READ-UNCOMMITTED,两个会话都打开事务

会话1:


MariaDB [fsx]> SELECT @@tx_isolation;
+-----------------+
| @@tx_isolation  |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set (0.00 sec)

MariaDB [fsx]> SET tx_isolation="READ-UNCOMMITTED";
Query OK, 0 rows affected (0.00 sec)

MariaDB [fsx]> SELECT @@tx_isolation;
+------------------+
| @@tx_isolation   |
+------------------+
| READ-UNCOMMITTED |
+------------------+
1 row in set (0.00 sec)
MariaDB [fsx]> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)

MariaDB [fsx]> SELECT * FROM fsx;
+-----+------+------+-----+
| cid | name | sex  | age |
+-----+------+------+-----+
|   4 | qpy  | girl |  21 |
|   5 | cooc | girl |  31 |
|   6 | fsx  | boy  |  24 |
+-----+------+------+-----+

会话2:


MariaDB [fsx]> SET tx_isolation="READ-UNCOMMITTED";
Query OK, 0 rows affected (0.00 sec)
MariaDB [fsx]> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)

会话修改fsx表中一个数据:


MariaDB [fsx]> UPDATE fsx SET age=22 WHERE name="qpy";
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

因为隔离级别是读未提交,所以在会话2中查看:


MariaDB [fsx]> SELECT * FROM fsx;
+-----+------+------+-----+
| cid | name | sex  | age |
+-----+------+------+-----+
|   4 | qpy  | girl |  22 |
|   5 | cooc | girl |  31 |
|   6 | fsx  | boy  |  24 |
+-----+------+------+-----+
3 rows in set (0.00 sec)

此时,会话1中撤回修改操作:


MariaDB [fsx]> ROLLBACK;
Query OK, 0 rows affected (0.34 sec)

再在会话2中查看fsx表:


MariaDB [fsx]> SELECT * FROM fsx;
+-----+------+------+-----+
| cid | name | sex  | age |
+-----+------+------+-----+
|   4 | qpy  | girl |  21 |
|   5 | cooc | girl |  31 |
|   6 | fsx  | boy  |  24 |
+-----+------+------+-----+
3 rows in set (0.00 sec)

这就出现了幻读

读提交

打开两个mysql会话,都设置隔离级别未READ-COMMITTED,两个会话都打开事务

会话1:


MariaDB [fsx]> SET tx_isolation="READ-COMMITTED";
Query OK, 0 rows affected (0.00 sec)

MariaDB [fsx]> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)

MariaDB [fsx]> SELECT * FROM fsx;
+-----+------+------+-----+
| cid | name | sex  | age |
+-----+------+------+-----+
|   4 | qpy  | girl |  21 |
|   5 | cooc | girl |  31 |
|   6 | fsx  | boy  |  24 |
+-----+------+------+-----+
3 rows in set (0.00 sec)

会话2:


MariaDB [fsx]> SET tx_isolation="READ-COMMITTED";
Query OK, 0 rows affected (0.00 sec)

会话1修改fsx表中数据,不提交情况下,会话2看不到fsx表的数据变化:

会话1修改数据:


MariaDB [fsx]> UPDATE fsx SET age=22 WHERE name="qpy";
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

会话2查看:


MariaDB [fsx]> SELECT * FROM fsx;
+-----+------+------+-----+
| cid | name | sex  | age |
+-----+------+------+-----+
|   4 | qpy  | girl |  21 |
|   5 | cooc | girl |  31 |
|   6 | fsx  | boy  |  24 |
+-----+------+------+-----+
3 rows in set (0.00 sec)

当会话1提交后,会话2可以看到改变:

会话1提交:


MariaDB [fsx]> COMMIT;
Query OK, 0 rows affected (0.07 sec)

会话2查看:


MariaDB [fsx]> SELECT * FROM fsx;
+-----+------+------+-----+
| cid | name | sex  | age |
+-----+------+------+-----+
|   4 | qpy  | girl |  22 |
|   5 | cooc | girl |  31 |
|   6 | fsx  | boy  |  24 |
+-----+------+------+-----+
3 rows in set (0.00 sec)

所谓读提交,就是别人提交后才可读,不提交就看不到变化 这样在一次会话中,仍会出现幻读

可重读

打开两个mysql会话,都设置隔离级别未REPEATABLE-READ,两个会话都打开事务

会话1:


MariaDB [fsx]> SET tx_isolation="REPEATABLE-READ";
Query OK, 0 rows affected (0.00 sec)

MariaDB [fsx]> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)

MariaDB [fsx]> SELECT * FROM fsx;
+-----+------+------+-----+
| cid | name | sex  | age |
+-----+------+------+-----+
|   4 | qpy  | girl |  28 |
|   5 | cooc | girl |  31 |
|   6 | fsx  | boy  |  24 |
+-----+------+------+-----+
3 rows in set (0.00 sec)

会话2:


MariaDB [fsx]> SET tx_isolation="REPEATABLE-READ";
Query OK, 0 rows affected (0.00 sec)

MariaDB [fsx]> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)

MariaDB [fsx]> SELECT * FROM fsx;
+-----+------+------+-----+
| cid | name | sex  | age |
+-----+------+------+-----+
|   4 | qpy  | girl |  28 |
|   5 | cooc | girl |  31 |
|   6 | fsx  | boy  |  24 |
+-----+------+------+-----+
3 rows in set (0.00 sec)

会话1修改fsx表数据,并且提交:


MariaDB [fsx]> UPDATE fsx SET age=43 WHERE name="qpy";
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

MariaDB [fsx]> SELECT * FROM fsx;
+-----+------+------+-----+
| cid | name | sex  | age |
+-----+------+------+-----+
|   4 | qpy  | girl |  43 |
|   5 | cooc | girl |  31 |
|   6 | fsx  | boy  |  24 |
+-----+------+------+-----+
3 rows in set (0.00 sec)

MariaDB [fsx]> COMMIT;
Query OK, 0 rows affected (0.07 sec)

会话2查看fsx表:


MariaDB [fsx]> SELECT * FROM fsx;
+-----+------+------+-----+
| cid | name | sex  | age |
+-----+------+------+-----+
|   4 | qpy  | girl |  28 |
|   5 | cooc | girl |  31 |
|   6 | fsx  | boy  |  24 |
+-----+------+------+-----+
3 rows in set (0.00 sec)
//注意:尽管会话1提交了,但此时会话2还是不能看到变化,也就是REPEATABLE-READ比READ-COMMIT隔离级别更高

当会话2也进行提交,再次打开一个会话,查看fsx表:


MariaDB [fsx]> COMMIT;
Query OK, 0 rows affected (0.00 sec)
MariaDB [fsx]> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)

MariaDB [fsx]> SELECT * FROM fsx;
+-----+------+------+-----+
| cid | name | sex  | age |
+-----+------+------+-----+
|   4 | qpy  | girl |  43 |
|   5 | cooc | girl |  31 |
|   6 | fsx  | boy  |  24 |
+-----+------+------+-----+
3 rows in set (0.00 sec)
//此时,数据变化

可重读,隔离的是同时打开的事务之间,即使提交完成,任何操作也都不可见,所以事务提交前后会有幻读问题

串行

打开两个mysql会话,都设置隔离级别未REPEATABLE-READ,两个会话都打开事务

会话1:


MariaDB [fsx]> SET tx_isolation="SERIALIZABLE";
Query OK, 0 rows affected (0.00 sec)

MariaDB [fsx]> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)

MariaDB [fsx]> SELECT * FROM fsx;
+-----+------+------+-----+
| cid | name | sex  | age |
+-----+------+------+-----+
|   4 | qpy  | girl |  21 |
|   5 | cooc | girl |  31 |
|   6 | fsx  | boy  |  24 |
+-----+------+------+-----+
3 rows in set (0.00 sec)

MariaDB [fsx]> UPDATE fsx SET age=44 WHERE name="qpy";
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

会话2如果此时也对fsx表中同样数据进行修改,则会进入阻塞状态:

ariaDB [fsx]> SET tx_isolation="SERIALIZABLE";
Query OK, 0 rows affected (0.00 sec)

MariaDB [fsx]> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)

MariaDB [fsx]> SELECT * FROM fsx;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

串行的隔离级别,禁止了并发,所以必须一个事务提交之后,另一个事务才进行对同一个数据的操作

原子性

原子性:一个事务可能有多个执行单元,这些语句要么同时都完成,要么都不完成,当作一个整体看待。即事务锁引起的所有数据库操作在数据库中完全执行,否则完全不执行

持久性

持久性:一旦事务成功执行完成,无论任何情况,任何意外,都必须保证事务结果可得到,不可再次发生变化,表现出一致性。

提供持久性的方法:

  • 事务提交之前,将数据写入磁盘(随机IO),持久性存储。效率低,且撤销时还需要从磁盘上撤销

  • 结合事务日志完成,先写入事务日志中(顺序IO),然后写入数据文件

事务一旦提交,就无法撤销,需要通过补偿事务来完成撤销。

事务的状态:

  1. 活动事务

  2. 部分提交事务:在提交过程中,一部分写入到磁盘,另一部分在写入。最后一条语句执行后,提交前

  3. 失败

  4. 终止

状态事务转换,从活动事务开始,到部分提交或者失败,如果部分提交完成,则提交;如果提交失败,则到终止执行状态。

为了进肯能提高资源利用率(IO吞吐量,减少等待时间,事务调度等),事务提交的过程允许并发,

事务调度策略:

可恢复调度

无级连调度

启动事务

START TRANSACTION:启动一个事务

SQL

COMMIT:提交一个事务

ROLLBACK:回滚,不提交

事务回滚示例:


MariaDB [fsx]> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)

MariaDB [fsx]> SELECT * FROM fsx;
+-----+-------+------+-----+
| cid | name  | sex  | age |
+-----+-------+------+-----+
|   1 | pqy   |      |  21 |
|   2 | jerry | boy  |  22 |
+-----+-------+------+-----+
2 rows in set (0.00 sec)

MariaDB [fsx]> DELETE FROM fsx WHERE cid LIKE 1;
Query OK, 1 row affected (0.00 sec)

MariaDB [fsx]> SELECT * FROM fsx;
+-----+-------+------+-----+
| cid | name  | sex  | age |
+-----+-------+------+-----+
|   2 | jerry | boy  |  22 |
+-----+-------+------+-----+
1 row in set (0.00 sec)
MariaDB [fsx]> ROLLBACK;
Query OK, 0 rows affected (0.34 sec)

MariaDB [fsx]> SELECT * FROM fsx;
+-----+-------+------+-----+
| cid | name  | sex  | age |
+-----+-------+------+-----+
|   1 | pqy   |      |  21 |
|   2 | jerry | boy  |  22 |
+-----+-------+------+-----+
2 rows in set (0.00 sec)

事务提交示例:


MariaDB [fsx]> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)

MariaDB [fsx]> DELETE FROM fsx WHERE cid LIKE 1;
Query OK, 1 row affected (0.00 sec)

MariaDB [fsx]> SELECT * FROM fsx;
+-----+-------+------+-----+
| cid | name  | sex  | age |
+-----+-------+------+-----+
|   2 | jerry | boy  |  22 |
+-----+-------+------+-----+
1 row in set (0.00 sec)
MariaDB [fsx]> COMMIT;
Query OK, 0 rows affected (0.11 sec)
//此时提交后就无法回滚
MariaDB [fsx]> ROLLBACK;
Query OK, 0 rows affected (0.00 sec)

MariaDB [fsx]> SELECT * FROM fsx;
+-----+-------+------+-----+
| cid | name  | sex  | age |
+-----+-------+------+-----+
|   2 | jerry | boy  |  22 |
+-----+-------+------+-----+
1 row in set (0.00 sec)

事务未提交之前,所有操作都可以撤销

mysql中有一个自动提交的全局变量,autocommit


MariaDB [fsx]> SELECT @@autocommit;
+--------------+
| @@autocommit |
+--------------+
|            1 |
+--------------+
1 row in set (0.00 sec)
是否会自动提交每一个sql语句,如果设置为1,当没有明确启动事务,则会自动提交;如果设置为0,当没有明确启动事务,则不会自动提交,也就是可以ROLLBACK

关闭自动提交,则不明确启动事务,也可以ROLLBACK


MariaDB [fsx]> SET autocommit=0;
Query OK, 0 rows affected (0.00 sec)

MariaDB [fsx]> SELECT * FROM fsx;
+-----+-------+------+-----+
| cid | name  | sex  | age |
+-----+-------+------+-----+
|   2 | jerry | boy  |  22 |
+-----+-------+------+-----+
1 row in set (0.00 sec)

MariaDB [fsx]> DELETE FROM fsx WHERE cid=2;
Query OK, 1 row affected (0.00 sec)

MariaDB [fsx]> SELECT * FROM fsx;
Empty set (0.00 sec)

MariaDB [fsx]> ROLLBACK;
Query OK, 0 rows affected (0.06 sec)

MariaDB [fsx]> SELECT * FROM fsx;
+-----+-------+------+-----+
| cid | name  | sex  | age |
+-----+-------+------+-----+
|   2 | jerry | boy  |  22 |
+-----+-------+------+-----+
1 row in set (0.00 sec)

猜你喜欢

转载自blog.csdn.net/fsx2550553488/article/details/80512843