一、事务机制

  1.事务的概念

  事务是指一组互相依赖的操作单元的集合,用来保证对数据库的正确修改,保持数据的完整性,如果一个事务的某个单元操作失败,将取消本次事务的全部操作。

  比如将A账户的资金转入B账户,在A中扣除成功,在B中添加失败,导致数据失去平衡,事务将回滚到原始状态,即A中没少,B中没多。

  事务必须具备以下特征,简称ACID:

  原子性(Atomicity):每个事务是一个不可分割的整体,只有所有的操作单元执行成功,整个事务才成功;否则此次事务就失败,所有执行成功的操作单元必须撤销,数据库回到此次事务之前的状态。

  一致性(Consistency):在执行一次事务后,关系数据的完整性和业务逻辑的一致性不能被破坏。例如A与B转账结束后,资金总额不能变。

  隔离性(Isolation):在并发环境中,一个事务所做的修改必须与其他事务所做的修改相隔离。例如一个事务查看的数据必须是其他并发事务修改之前或修改完毕的数据,不能是修改中的数据。

  持久性(Durability):事务结束后,对数据的修改是永久保存的,即使因系统故障导致重启数据库系统,数据依然是修改后的状态。

  2.事务机制的必要性

  (1)创建银行的数据库和数据表

mysql> CREATE DATABASES db_bank;
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'DATABASES db_bank' at line 1
mysql> CREATE DATABASE db_bank;
Query OK, 1 row affected (0.00 sec) mysql> use db_bank;
Database changed
mysql> CREATE TABLE tb_account(
-> id int(10) unsigned NOT NULL auto_increment PRIMARY KEY,
-> name varchar(30),
-> balance FLOAT(8,2) unsigned DEFAULT 0
-> );
Query OK, 0 rows affected (0.02 sec)
mysql> INSERT INTO tb_account(name,balance) values('A',1000),('B',0);
Query OK, 2 rows affected (0.01 sec)
Records: 2 Duplicates: 0 Warnings: 0 mysql> select * from tb_account;
+----+------+---------+
| id | name | balance |
+----+------+---------+
| 1 | A | 1000.00 |
| 2 | B | 0.00 |
+----+------+---------+
2 rows in set (0.00 sec)

  (2)创建转账操作存储过程

mysql> delimiter //
mysql> CREATE PROCEDURE proc_transfer(IN id_from INT ,IN id_to INT ,IN money INT)
-> READS SQL DATA
-> BEGIN
-> UPDATE tb_account SET balance=balance+money WHERE id=id_to;
-> UPDATE tb_account SET balance=balance-money WHERE id=id_from;
-> END
-> //
Query OK, 0 rows affected (0.01 sec)

  (3)调用两次存储过程(第一条UPDATE语句没有执行成功,但是第二条UPDATE语句执行成功了,于是就多了500)

mysql> CALL proc_transfer(1,2,700);
Query OK, 1 row affected (0.01 sec) mysql> select * from tb_account;
+----+------+---------+
| id | name | balance |
+----+------+---------+
| 1 | A | 300.00 |
| 2 | B | 700.00 |
+----+------+---------+
2 rows in set (0.00 sec) mysql> CALL proc_transfer(1,2,500);
ERROR 1264 (22003): Out of range value for column 'balance' at row 1
mysql> select * from tb_account;
+----+------+---------+
| id | name | balance |
+----+------+---------+
| 1 | A | 300.00 |
| 2 | B | 1200.00 |
+----+------+---------+
2 rows in set (0.00 sec)

  3.关闭MySQL自动提交

  (1)查看自动提交功能是否关闭(开启时表示,如果不显式地开启一个事务,则每个SQL语句都被当做一个事务执行提交操作)

mysql> show variables like 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | ON |
+---------------+-------+
1 row in set, 1 warning (0.00 sec)

  (2)关闭自动提交功能(所有的SQL语句都是在一个事务中,直到显式地执行提交或者回滚时,该事务才结束,同时又会开启另一个新事务)

mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec) mysql> show variables like 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | OFF |
+---------------+-------+
1 row in set, 1 warning (0.00 sec)

  4.事务回滚

  (1)关闭自动提交功能后转账

mysql> select * from tb_account;
+----+------+---------+
| id | name | balance |
+----+------+---------+
| 1 | A | 300.00 |
| 2 | B | 1200.00 |
+----+------+---------+
2 rows in set (0.00 sec) mysql> call proc_transfer(1,2,500);
ERROR 1264 (22003): Out of range value for column 'balance' at row 1
mysql> select * from tb_account;
+----+------+---------+
| id | name | balance |
+----+------+---------+
| 1 | A | 300.00 |
| 2 | B | 1700.00 |
+----+------+---------+
2 rows in set (0.00 sec)

  (2)再打开一个命令行,查看数据表(关闭了自动提交功能后,如果不手动提交,那么UPDATE操作的结果将仅仅影响内存中的临时记录,并没有真正写入到数据库文件。)

mysql> use db_bank;
Database changed
mysql> select * from tb_account;
+----+------+---------+
| id | name | balance |
+----+------+---------+
| 1 | A | 300.00 |
| 2 | B | 1200.00 |
+----+------+---------+
2 rows in set (0.00 sec)

  (3)更新后的数据与想要的数据不一致,执行回滚操作

mysql> select * from tb_account;
+----+------+---------+
| id | name | balance |
+----+------+---------+
| 1 | A | 300.00 |
| 2 | B | 1700.00 |
+----+------+---------+
2 rows in set (0.00 sec) mysql> ROLLBACK;
Query OK, 0 rows affected (0.00 sec) mysql> select * from tb_account;
+----+------+---------+
| id | name | balance |
+----+------+---------+
| 1 | A | 300.00 |
| 2 | B | 1200.00 |
+----+------+---------+
2 rows in set (0.00 sec)

  5.事务提交

  (1)显示提交(关闭自动提交功能后,使用COMMIT命令显示地提交更新语句,相当于(ROLLBACK))。

mysql> select * from tb_account;
+----+------+---------+
| id | name | balance |
+----+------+---------+
| 1 | A | 300.00 |
| 2 | B | 1200.00 |
+----+------+---------+
2 rows in set (0.02 sec) mysql> call proc_transfer(1,2,500);
ERROR 1264 (22003): Out of range value for column 'balance' at row 1
mysql> COMMIT;
Query OK, 0 rows affected (0.00 sec) mysql> select * from tb_account;
+----+------+---------+
| id | name | balance |
+----+------+---------+
| 1 | A | 300.00 |
| 2 | B | 1700.00 |
+----+------+---------+
2 rows in set (0.00 sec)

  (2)隐式提交(执行“set autocommit=1”除了开启自动提交功能,还会提交之前所有的更新语句)

  6.MySQL中的事务(START TRANSACTION语句)  

  (1)创建存储过程

mysql> DELIMITER //
mysql> CREATE PROCEDURE prog_tran_account(IN id_from INT, IN id_to INT, IN money int)
-> MODIFIES SQL DATA
-> BEGIN
-> DECLARE EXIT HANDLER FOR SQLEXCEPTION ROLLBACK;
-> START TRANSACTION;
-> UPDATE tb_account SET balance=balance+money WHERE id=id_to;
-> UPDATE tb_account SET balance=balance-money WHERE id=id_from;
-> COMMIT;
-> END
-> //
Query OK, 0 rows affected (0.00 sec)

  (2)调用存储过程

mysql> CALL prog_tran_account(1,2,700);
-> select * from tb_account;
-> //
Query OK, 0 rows affected (0.00 sec) +----+------+---------+
| id | name | balance |
+----+------+---------+
| 1 | A | 300.00 |
| 2 | B | 1700.00 |
+----+------+---------+
2 rows in set (0.00 sec)

  各账户的余额并没有改变,而且也没有出现错误,这是因为对出现的错误进行了处理,并且进行了事务回滚。

mysql> call proc_tran_account(1,2,200);
-> select * from tb_account;
-> //
Query OK, 0 rows affected (0.01 sec) +----+------+---------+
| id | name | balance |
+----+------+---------+
| 1 | A | 100.00 |
| 2 | B | 1900.00 |
+----+------+---------+
2 rows in set (0.01 sec)

  将转账金额修改为200元,那么将实现正常转账。

  7.回退点

  (1)创建存储过程。

mysql> CREATE PROCEDURE proc_savepoint_account()
-> MODIFIES SQL DATA
-> BEGIN
-> DECLARE CONTINUE HANDLER FOR SQLEXCEPTION
-> BEGIN
-> ROLLBACK TO A;
-> COMMIT;
-> END;
-> START TRANSACTION;
-> START TRANSACTION;
-> INSERT INTO tb_account(name,balance)VALUES('C', 1000);
-> savepoint A;
-> UPDATE tb_account SET balance=balance+500 WHERE id=2;
-> UPDATE tb_account SET balance=balance-500 WHERE id=1;
-> COMMIT;
-> END
-> //
Query OK, 0 rows affected (0.01 sec)

  (2)调用存储过程。第一条插入语句执行成功,由于第二条更新语句出现错误,所以事务回滚到A了。

mysql> CALL proc_savepoint_account();
-> select * from tb_account;
-> //
Query OK, 0 rows affected (0.01 sec) +----+------+---------+
| id | name | balance |
+----+------+---------+
| 1 | A | 100.00 |
| 2 | B | 1900.00 |
| 3 | C | 1000.00 |
+----+------+---------+
3 rows in set (0.01 sec)

  二、锁机制

  数据库管理系统采用锁的机制来管理事务。当多个事务同时修改同一数据时,只允许持有锁的事务修改该数据,其他事务只能“排队等待”,直到前一个事务释放其拥有的锁。

  1.MySQL锁机制的基本知识

  (1)锁的类型

  在处理并发读或写时,可以通过实现一个由两种类型的锁组成的锁系统来解决问题。这两种类型的锁通常称为读锁和写锁。

  读锁也称为共享锁,它是共享的,或者说是相互不阻塞的。多个客户端在同一时间可以同时读取同一资源,互补干扰。

  写锁也成为排他锁,它是排他的,也就是说一个写锁会阻塞其他的写锁和读锁。这是为了确保在给定的时间里,只有一个用户能执行写入,并防止其他用户读取正在写入的同一资源,保证安全。

  (2)锁粒度

  一种提高共享资源并发性的方式就是让锁定对象更有选择性。也就是尽量只锁定部分数据,而不是所有的资源。这就是颗粒度的概念。它是指锁的作用范围,是为了对数据库中高并发相应和系统性能两方面进行平衡而提出的。

  颗粒度越小,并发访问性能越高,越适合做并发更新操作;颗粒度越大,并发访问性能就越低,越适合做并发查询操作。

  在给定的资源上,锁定的数据量越少,系统的并发程度越高,完成某个功能时所需要的加锁和解锁的次数就会越多,反而会消耗较多的资源,甚至会出现资源的恶性竞争,乃至于发生死锁。

  (3)锁策略

  锁策略是指在锁的开销和数据的安全性之间寻求平衡。但是这种平衡会影响性能,所以大多数商业数据库系统没有提供更多的选择,一般都是在表上施加行级锁,并以各种复杂的方式来实现,以便在数据比较多的情况下,提供更好的性能。

  表级锁是MySQL中最基本的锁策略,并且是开销最小的策略。它会锁定整张表,一个用户在对表进行操作前,需要先获得写锁,这会阻塞其他用户对该表的所有读写操作。只有没有写锁时,其他读取的用户才能获得读锁,并且读锁之间是不相互阻塞的。另外,由于写锁比读锁优先级高,所以一个写锁清秋可能会被插入到读锁队列的前面,但是读锁则不能插入到写锁的前面。

  行级锁可以最大限度地支持并发处理,同时也带来了最大的锁开销。在InnoDB或者一些其他存储引擎中的锁实现。

  (4)锁的生命周期

  锁的声明周期是指在一个MySQL会话内,对数据进行加锁到解锁之间的时间间隔。锁的生命周期越长,并发性能就越低,反之并发性能就越高。另外锁是数据库管理系统的重要资源,需要占据一定的服务器内存,锁的周期越长,占用的服务区内存时间就越长;相反占用的内存也就越短。因此,我们应该尽可能地缩短锁的生命周期。

  2.MyISAM表的表级锁

  MyISAM表不支持COMMIT和ROLLBACK命令。当用户对数据库执行插入、删除、更新等操作时,这些变化的数据都被立刻保存在磁盘中。这样,在多用户环境下,会导致诸多问题。为了避免同一时间有多个用户对数据库中指定表进行操作,可以应用表锁定来避免在用户操作数据表过程中受到干扰。当且仅当该用户释放表的操作锁定后,其他用户才可以访问这些修改后的数据表。

  (1)以读方式锁定数据表

  首先创建表并且为表加一个读锁。

mysql> create table tb_user(
-> id int(10) unsigned NOT NULL auto_increment PRIMARY KEY,
-> username varchar(30),
-> pwd varchar(30)
-> )ENGINE=MyISAM;
Query OK, 0 rows affected (0.02 sec) mysql> INSERT INTO tb_user(username,pwd)VALUES
-> ('shen','123456'),
-> ('lian','123456'),
-> ('chen','123456');
Query OK, 3 rows affected (0.00 sec)
Records: 3 Duplicates: 0 Warnings: 0 mysql> LOCK TABLE tb_user READ;
Query OK, 0 rows affected (0.01 sec) mysql> select * from tb_user;
+----+----------+--------+
| id | username | pwd |
+----+----------+--------+
| 1 | shen | 123456 |
| 2 | lian | 123456 |
| 3 | chen | 123456 |
+----+----------+--------+
3 rows in set (0.01 sec)

  分别执行两次操作。

mysql> INSERT INTO tb_user(username,pwd)VALUES('tian','123456');
ERROR 1099 (HY000): Table 'tb_user' was locked with a READ lock and can't be updated
mysql> UNLOCK TABLES;
Query OK, 0 rows affected (0.00 sec) mysql> INSERT INTO tb_user(username,pwd)VALUES('tian','123456');
Query OK, 1 row affected (0.00 sec) mysql> select * from tb_user;
+----+----------+--------+
| id | username | pwd |
+----+----------+--------+
| 1 | shen | 123456 |
| 2 | lian | 123456 |
| 3 | chen | 123456 |
| 4 | tian | 123456 |
+----+----------+--------+
4 rows in set (0.00 sec)

  (2)以写方式锁定数据表

  为表添加写锁,然后通过自身命令行读,可以查询表数据,这是因为以写方式锁定数据表并不能限制当前锁定的用户的查询操作。

mysql> LOCK TABLE tb_user WRITE;
Query OK, 0 rows affected (0.00 sec) mysql> select * from tb_user;
+----+----------+--------+
| id | username | pwd |
+----+----------+--------+
| 1 | shen | 123456 |
| 2 | lian | 123456 |
| 3 | chen | 123456 |
| 4 | tian | 123456 |
+----+----------+--------+
4 rows in set (0.00 sec)

  再打开一个新用户会话,保持原窗口不被关闭,

  然后在原来的会话中打开锁

mysql> UNLOCK tables;
Query OK, 0 rows affected (0.00 sec)

  新打开的窗口会自动地显示出查询数据。

  3.InnoDB表的行级锁

  (1)为采用InnoDB存储引擎的数据表tb_account在查询语句中设置读锁

SELECT * FROM tb_account LOCK IN SHARE MODE;

  (2)为采用InnoDB存储引擎的数据表tb_account在查询语句中设置写锁

SELECT * FROM tb_account FOR UPDATE;

  (3)在更新(包括INSERT UPDATE和DELTET)语句中,InnoDB存储引擎自动为更新语句影响的记录添加隐式写锁。

  (4)以上三种方式为表设置行级锁的生命周期非常短暂,为了延长行级锁的生命周期,可以采用开启事务实现。

  窗口一:

mysql> use db_bank;
Database changed
mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec) mysql> SELECT * FROM tb_account FOR UPDATE;
+----+------+---------+
| id | name | balance |
+----+------+---------+
| 1 | A | 100.00 |
| 2 | B | 1900.00 |
| 3 | C | 1000.00 |
+----+------+---------+
3 rows in set (0.00 sec)

  窗口二:

mysql> use db_bank;
Database changed
mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec) mysql> SELECT * FROM tb_account FOR UPDATE;

  窗口一:

mysql> COMMIT;
Query OK, 0 rows affected (0.00 sec)

  窗口二:

mysql> use db_bank;
Database changed
mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec) mysql> SELECT * FROM tb_account FOR UPDATE;
+----+------+---------+
| id | name | balance |
+----+------+---------+
| 1 | A | 100.00 |
| 2 | B | 1900.00 |
| 3 | C | 1000.00 |
+----+------+---------+
3 rows in set (35.82 sec)

  4.死锁的概念与避免

  死锁就是当两个或者多个处于不同序列的用户打算同时更新某相同的数据库时,因互相等待对方释放权限而导致双方一直处于等待状态。在实际应用中,两个不同序列的客户打算同时对数据执行操作,极有可能产生死锁。更具体地将讲,当两个事物相互等待操作对方释放的所持有的资源,而导致两个事务都无法操作对方持有的资源,这样无限期的等待被称作死锁。

  InnoDB表处理程序具有检查死锁这一功能,如果该处理程序发现用户在操作过程中产生死锁,该处理程序立刻通过撤销操作来撤销其中一个事务,以便使死锁消失。

  三、事务的隔离级别

  锁机制有效地解决了事务的并发问题,但也影响了事务的并发性能。所谓并发是指数据库系统同时为多个用户提供服务的能力。

  1.事务的隔离级别与并发问题(4种事务隔离级别)

  (1)Serializable(串行化):一个事务在执行过程中首先将其欲操作的数据锁定,待事务结束后释放。如果此时另一个事务也要操纵该数据,必须等待前一个事务释放锁定后才能继续进行。

  (2)Repeatable Read(可重复读):一个事务在执行过程中能够看到其他事务已经提交的新插入记录,看不到其他事务对已有记录的修改。

  (3)Read Committed(读已提交数据):一个事务在执行过程中能够看到其他事务已经提交的新插入记录,也能看到其他事务已经提交的对已有记录的修改。

  (4)Read Uncommitted(读未提交数据):一个事务在执行过程中能够看到其他事务未提交的新插入记录,也能看到其他事务未提交的对已有记录的修改。

  2.设置事务的隔离级别(只对当前会话有效)

mysql> SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
Query OK, 0 rows affected (0.01 sec)

  

Mysql数据库(十一)事务与锁机制的更多相关文章

  1. MySQL数据库(三)锁机制

    MyISAM默认使用表级锁,不支持行级锁 InnoDB默认使用行级锁,也支持表级锁 表级锁:开销小,加锁快:不会出现死锁:锁定粒度大,发生锁冲突的概率最高,并发度最低. 行级锁:开销大,加锁慢:会出现 ...

  2. [MySQL数据库之事务、读现象、数据库锁机制、多版本控制MVCC、事务隔离机制]

    [MySQL数据库之事务.读现象.数据库锁机制.多版本控制MVCC.事务隔离机制] 事务 1.什么是事务: 事务(Transaction),顾名思义就是要做的或所做的事情,数据库事务指的则是作为单个逻 ...

  3. MySQL 事务与锁机制

    下表展示了本人安装的MariaDB(10.1.19,MySQL的分支)所支持的所有存储引擎概况,其中支持事务的有InnoDB.SEQUENCE,另外InnoDB还支持XA事务,MyISAM不支持事务. ...

  4. MySQL数据库的事务管理

    当前在开发ERP系统,使用到的数据库为Mysql.下面介绍下如何开启事务,以及事务隔离的机制 : 1. 检查当前数据库使用的存储引擎. show engines; 2. 修改前my.ini中的文件如下 ...

  5. MySQL 各级别事务的实现机制

    MySQL 各级别事务的实现机制在处理cnctp项目已合包裹状态同步的问题时,发现读包裹状态和对包裹状态的更新不在一个事务内,我提出是否会因为消息并发导致状态一致性问题.在和同事讨论的过程中,我们开始 ...

  6. MYSQL数据库学习十六 安全性机制

    16.1 MYSQL数据库所提供的权限 16.1.1 系统表 mysql.user 1. 用户字段 Host:主机名: User:用户名: Password:密码. 2. 权限字段 以“_priv”字 ...

  7. django中使用mysql数据库的事务

    django中怎么使用mysql数据库的事务   Mysql数据库事务: 在进行后端业务开始操作修改数据库时,可能会涉及到多张表的数据修改,对这些数据的修改应该是一个整体事务,即要么一起成功,要么一起 ...

  8. MYSQL数据库重点:事务与锁机制

    一.事务 一组连续的数据库操作,每一次操作都成功,整个事务就成功,只要有一步出错,整个事务就失败: MySQL事务与存储引擎相关 1.MyISAM:不支持事务,用于只读程序提高性能 2.InnoDB: ...

  9. 重新学习MySQL数据库6:浅谈MySQL的中事务与锁

    『浅入深出』MySQL 中事务的实现 在关系型数据库中,事务的重要性不言而喻,只要对数据库稍有了解的人都知道事务具有 ACID 四个基本属性,而我们不知道的可能就是数据库是如何实现这四个属性的:在这篇 ...

随机推荐

  1. springboot 使用i18n进行国际化

    1.i18n介绍 i18n(其来源是英文单词 internationalization的首末字符i和n,18为中间的字符数)是“国际化”的简称.在资讯领域,国际化(i18n)指让产品(出版物,软件,硬 ...

  2. Redis系列---操作命令及数据类型

    本章将简单介绍下,Redis里面常用的数据类型以及我们常用到的一些命令,let's go 准备环境: Redis  详细见上一个章节 我们接下来操作的命令用的是Redis自带的客户端工具,在安装red ...

  3. java架构之路-(spring源码篇)springIOC容器源码解析(上)

    我们这次来叭叭一下Spring的源码,这次博客主要来说说Spring源码,先粗略的撸一遍,下篇博客选几个重点去说,由于过于复杂,我也是看了一点点,我们先来过一遍源码,然后上流程图,最后我们再回头总结一 ...

  4. CSS技巧 (2) · 多列等高布局

    前言  最近,面试的时候都碰到一些关于利用CSS实现多列等高布局或者一侧宽度固定,另一侧宽度自适应的问题,下面稍微总结一下: 先看一道题目 巧妙的多列等高布局 规定下面的布局,实现多列等高布局,要求两 ...

  5. Jetpack系列:Paging组件帮你解决分页加载实现的痛苦

    相信很多小伙伴们在项目实战中,经常会用到界面的分页显示.加载更多等功能.需要针对具体功能做针对性开发和调试,耗时耗力. Paging组件的使用将这部分的工作简化,从而让开发者更专注于业务的具体实现.下 ...

  6. ELK 学习笔记之 Logstash安装

    Logstash安装: https://www.elastic.co/downloads/logstash 下载解压: tar –zxvf logstash-5.6.1.tar.gz 在/usr/lo ...

  7. Chrome 浏览器默认样式覆盖自己 CSS 样式的解决

    检查 HTML 源代码,DOCTYPE 的声明是否写正确. HTML5 的 DOCTYPE 声明规范: <!DOCTYPE html> 参考链接: css - User agent sty ...

  8. SpringBoot-ElasticJob封装快速上手使用(分布式定时器)

    elastic-job-spring-boot qq交流群:812321371 1 简介 Elastic-Job是一个分布式调度解决方案,由两个相互独立的子项目Elastic-Job-Lite和Ela ...

  9. 关于ajax提交表单的一些实例及遇到的问题和解决办法

    ajax提交的表单有两种情况: 第一种:input type类型没有file上传文件类型的表单 第二种:input type类型有file上传文件类型的表单 之所以分为两种:是因为原生ajax是不能提 ...

  10. spring配置文件默认名称及位置,ContextLoaderListener监听器作用

    spring在web.xml中的配置 由于spring需要启动容器才能为其他框架提供服务,而web应用程序的入口是由web服务器控制的,因此无法在main()方法中通过创建ClassPathXmlAp ...