This slide is based on Jeffrey D. Ullman's work, which can be download from his website.
也参考了姜承尧写的书《MySQL技术内幕InnoDB存储引擎》(第2版)
数据库系统通常会被许多用户访问, 并且很多处理(如查询、修改等)同时发生.
与操作系统支持进程的交互所不同的是, DBMS 需要对有问题的交互进行进程维持.(needs to keep processes from troublesome interactions.)
事务(Transaction)是数据库区别于文件系统的重要特性之一.
用户A和用户B持有某银行同一账户和密码, 也就是说假设他们有完全相同的两张银行卡, 并且他们在同一时间从不同地点的两台 ATM 机上都取了 $\$ 100$.
对于文件同步的情况, 当系统重启, 会出现文件可能不同步的情况.
通常具有某些强大的并发(concurrency)方面的性质.
Formed in SQL from single statements 或明确的程序员控制
目的: 事务会把数据库从一种一致状态转换为另一种一致状态.
在数据库提交工作时, 可以确保要么所有修改都已经保存了, 要么所有修改都不保存.
MySQL 有很多存储引擎, 其中
也就是说, 严格来说, 事务需要满足上面四个条件. 但是弱形式的事务也是被支持的.
原子性指整个数据库事务是不可分割的工作单位. 只有使事务中所有的数据库操作都执行成功, 才算整个事务成功. 事务中任何一条SQL语句执行失败, 已经执行成功的SQL语句也必须撤销, 数据库状态应该退回到执行事务前的状态.
隔离性还有其他的称呼, 如
事务一旦提交, 其结果就是永久性的. 即使发生宕机等故障, 数据库也能将数据恢复. (外部原因导致数据丢失的情况不属于这里的持久性. 这里的持久性指的是保证事务系统的高可靠性(High Reliability), 而不是高可用性(High Availability).)
从事务理论的角度, 可以将事务分为以下几种类型.
扁平事务(Flat Transactions)是事务类型中最简单的一种, 在实际生产环境中是使用最频繁的事务. 在扁平事务中, 所有操作都处于同一层次, 其由
扁平事务的主要限制是不能提交或回滚事务的某一部分, 或分几个步骤提交.
带有保存点的扁平事务(Flat Transactions with Savepoints), 除了支持扁平事务支持的操作外, 允许在事务执行过程中回滚到同一事务中较早的一个状态.
保存点(Savepoint)用来通知系统应该记住事务当前的状态, 以便当之后发生错误时, 事务能回到保存点当时的状态.
对于扁平事务来说, 其隐式地设置了一个保存点, 就是事务开始时的状态点.
链事务(Chained Transactions)可视为保存点事务的一种变种.
嵌套事务(Nested Transactions)是一个层次结构框架. 由一个顶层事务(top-level transaction)控制着各个层次的事务. 顶层事务之下嵌套的事务被称为
可以通过保存点技术来模拟嵌套事务. 但是用保存点技术模拟的嵌套事务在锁的持有方面还是于嵌套查询有些区别.
分布式事务(Distributed Transactions)通常是一个在分布式环境下运行的扁平事务, 因此需要根据数据所在位置访问网络中的不同节点.
InnoDB 存储引擎支持扁平事务、带有保存点的事务、链事务、分布式事务。因此, 对有并行事务需求的用户来说, MySQL数据库或InnoDB 存储引擎就显得无能为力了. 虽然可以通过带有保存点的事务来模拟嵌套事务, 但本质上是串行的嵌套事务.
事务管理器的工作:
日志是日志记录构成的文件, 每个日志记录记载有关某个事务已做事情的某些情况.
不妨将日志看作一个按只允许附加的方式打开的文件. 当事务执行时, 日志管理器负责在日志中记录每个重要的事件.
如果日志记录出现在非易失性的存储器中, 那么在系统崩溃后, 我们就可以使用它们来将数据库恢复到一个一致性状态.
undo 日志通过撤销事务在系统崩溃前可能还没有完成的影响来修复数据库状态.
show variables like 'general_log_file';
mysql> show variables like '%general_log%'; +------------------+---------------------+ | Variable_name | Value | +------------------+---------------------+ | general_log | OFF | | general_log_file | LAPTOP-Q34L5TP8.log | +------------------+---------------------+ 2 rows in set, 1 warning (0.24 sec)
/var/log/mysql/mysql.log
SQL 定义了四种隔离层次(Isolation Levels), 所谓的
只有一种级别("可串行化(serializable)")=
每一种 DBMS 都有自己的方式对事务进行补充.
在一个事务中, 我们可以这样设置事务的隔离层次:
其中 $X=$
在
SET [GLOBAL | SESSION] TRANSACTION ISOLATION LEVEL { READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE }
如果想在 MySQL 数据库启动时就设置事务的默认隔离级别(比如希望设置为
[mysqld] transaction-isolation = READ-COMMITTED
查询当前会话的事务隔离级别, 可以查询系统变量
mysql> SELECT @@tx_isolation\G; *************************** 1. row *************************** @@tx_isolation: REPEATABLE-READ 1 row in set (0.00 sec)
查询全局的事务隔离级别, 可以使用
mysql> SELECT @@global.tx_isolation\G; *************************** 1. row *************************** @@global.tx_isolation: REPEATABLE-READ 1 row in set (0.00 sec)
如果忘了变量的名字, 可以使用下面的语句查询.
mysql> show variables like "%isolation%"; +-----------------------+----------------+ | Variable_name | Value | +-----------------------+----------------+ | transaction_isolation | READ-COMMITTED | +-----------------------+----------------+ 1 row in set, 1 warning (0.02 sec)
这里我们发现本地安装的 MySQL, 其隔离级别是
设置事务隔离级别为
mysql> SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; Query OK, 0 rows affected (0.00 sec) mysql> SELECT @@tx_isolation; +----------------+ | @@tx_isolation | +----------------+ | READ-COMMITTED | +----------------+ 1 row in set (0.00 sec)
InnoDB 存储引擎提供了对
分布式事务(
事务资源通常是关系型数据库系统, 但也可以是其他类型的资源.
全局事务要求在其中的所有参与的事务要么都提交, 要么都回滚. 这对于事务原有的 ACID 要求又提高了一层.
另外, 在使用分布式事务时, InnoDB 存储引擎的事务隔离级别必须设置为
分布式事务常见于银行系统的转账中.
XA 事务由一个或多个资源管理器(Resource Managers)、一个事务管理器(Transaction Manager)以及一个应用程序(Application Program)组成.
在 MySQL 数据库的分布式事务中, 资源管理器就是 MySQL 数据库, 事务管理器为连接 MySQL 服务器的客户端.
关于 XA 事务及分布式事务, 请参见相关参考文献. 推荐阅读姜承尧 著《MySQL技术内幕 InnoDB存储引擎》(第2版)。
SQL 中的
执行
MySQL 的 InnoDB 引擎默认自动提交. 注意每一次提交, MySQL 都要写一次重做日志(redo log). 因此, 在写存储过程时, 不要在循环中提交.
MySQL 事务的开启方式有显示开启和隐式开启. 开启的方式会影响自动提交的设置.
MySQL 可以使用
通过
mysql> show variables like "%autocommit%"; +---------------+-------+ | Variable_name | Value | +---------------+-------+ | autocommit | ON | +---------------+-------+ 1 row in set (0.00 sec) mysql> set autocommit=0; Query OK, 0 rows affected (0.00 sec) mysql> select @@autocommit; +--------------+ | @@autocommit | +--------------+ | 0 | +--------------+ 1 row in set (0.00 sec)
mysql> set autocommit=on; Query OK, 0 rows affected (0.00 sec)
下面通过例子说明自动提交可能会带来什么问题. 我们假设系统变量
以下例子来自于姜承尧 著《MySQL技术内幕 InnoDB存储引擎》(第2版).
CREATE PROCEDURE load1(count INT UNSIGNED) BEGIN DECLARE s INT UNSIGNED DEFAULT 1; DECLARE c CHAR(80) DEFAULT REPEAT('a',80); WHILE s <= count DO INSERT INTO t1 SELECT NULL,c; COMMIT; SET s = s+1; END WHILE; END;
下面的 load2 过程相比 load1 过程少了 COMMIT; 语句.
CREATE PROCEDURE load2(count INT UNSIGNED) BEGIN DECLARE s INT UNSIGNED DEFAULT 1; DECLARE c CHAR(80) DEFAULT REPEAT('a',80); WHILE s <= count DO INSERT INTO t1 SELECT NULL,c; SET s = s+1; END WHILE; END;
而 load3 过程将执行得比 load1 和 load2 更快, 因为它将整个 WHILE 循环部分放到一个事务中.
CREATE PROCEDURE load3(count INT UNSIGNED) BEGIN DECLARE s INT UNSIGNED DEFAULT 1; DECLARE c CHAR(80) DEFAULT REPEAT('a',80); START TRANSACTION; WHILE s <= count DO INSERT INTO t1 SELECT NULL,c; SET s = s+1; END WHILE; COMMIT; END;
首先建立表 t1.
mysql> use test Database changed mysql> create table t1( -> id int not null auto_increment primary key, -> str char(80) -> ); Query OK, 0 rows affected (0.04 sec)
建立三个存储过程 load1, load2, load3.
mysql> delimiter $$ mysql> CREATE PROCEDURE load1(count INT UNSIGNED) -> BEGIN -> DECLARE s INT UNSIGNED DEFAULT 1; -> DECLARE c CHAR(80) DEFAULT REPEAT('a',80); -> WHILE s <= count DO -> INSERT INTO t1 SELECT NULL,c; -> COMMIT; -> SET s = s+1; -> END WHILE; -> END; -> $$ Query OK, 0 rows affected (0.03 sec) mysql> CREATE PROCEDURE load2(count INT UNSIGNED) -> BEGIN -> DECLARE s INT UNSIGNED DEFAULT 1; -> DECLARE c CHAR(80) DEFAULT REPEAT('a',80); -> WHILE s <= count DO -> INSERT INTO t1 SELECT NULL,c; -> SET s = s+1; -> END WHILE; -> END; -> $$ Query OK, 0 rows affected (0.00 sec) mysql> CREATE PROCEDURE load3(count INT UNSIGNED) -> BEGIN -> DECLARE s INT UNSIGNED DEFAULT 1; -> DECLARE c CHAR(80) DEFAULT REPEAT('a',80); -> START TRANSACTION; -> WHILE s <= count DO -> INSERT INTO t1 SELECT NULL,c; -> SET s = s+1; -> END WHILE; -> COMMIT; -> END; -> $$ Query OK, 0 rows affected (0.00 sec) mysql> delimiter ;
测试这三个存储过程的执行时间.
mysql> CALL load1(10000); Query OK, 0 rows affected (39.07 sec) mysql> select * from t1; +-----+----------------------------------------------------------------------------------+ | id | str | +-----+----------------------------------------------------------------------------------+ | 1 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | | 2 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | | 3 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | | 4 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | | 5 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | | 6 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | | 7 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | | 8 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | | 9 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | | 10 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | ...... | 9994 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | | 9995 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | | 9996 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | | 9997 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | | 9998 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | | 9999 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | | 10000 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | +-------+----------------------------------------------------------------------------------+ 10000 rows in set (0.01 sec)
这个时间还是很长的. 使用
使用 load2() 稍微快一点.
mysql> call load2(10000); Query OK, 1 row affected (37.09 sec)
使用 load3() 是最快的.
mysql> call load3(10000); Query OK, 0 rows affected (0.18 sec) mysql> select count(*) from t1; +----------+ | count(*) | +----------+ | 30000 | +----------+ 1 row in set (0.01 sec)
可以对第二个存储过程 load2() 的调用进行调整, 调用之前显示开启事务, 则同样可以达到存储过程 load3() 的性能.
mysql> truncate t1; Query OK, 0 rows affected (0.01 sec) mysql> START TRANSACTION; Query OK, 0 rows affected (0.00 sec) mysql> call load2(10000); Query OK, 1 row affected (0.16 sec) mysql> commit; Query OK, 0 rows affected (0.00 sec)
我们再实验一下循环插入两万行数据.
mysql> CALL load1(20000); Query OK, 0 rows affected (1 min 11.95 sec) mysql> select count(*) from t1; +----------+ | count(*) | +----------+ | 20000 | +----------+ 1 row in set (0.01 sec) mysql> truncate t1; Query OK, 0 rows affected (0.01 sec) mysql> call load2(20000) -> ; Query OK, 1 row affected (1 min 11.51 sec) mysql> truncate t1; Query OK, 0 rows affected (0.01 sec) mysql> call load3(20000); Query OK, 0 rows affected (0.35 sec)
采用隐式提交的 load2() 还是比显示提交的 load1() 稍快. 而 load3() 是最快的. 因此, 不应该在一个循环中反复进行提交(commit)操作. 而且由于 MySQL 默认是自动提交, 因此最好将其设置为非自动提交.
对于不同语言的 API, 自动提交是不同的.
因此在选用不同的语言来编写数据库应用程序前, 应该对连接 MySQL 的 API 做好研究.
在编写应用程序时, 最好把事务的控制权限交给开发人员, 即在程序端进行事务的开始和结束.
SQL 中的
例如被 $0$ 除或某个违反约束的动作均会导致数据库的回滚, 即使程序员并没有作出回滚的请求.
InnoDB 存储引擎支持通过定义一个
首先建立一个表 b, 仅有一个属性 a, 且这个属性为 PRIMARY KEY.
mysql> create table b( -> a int not null default 0, -> primary key(a) -> ); Query OK, 0 rows affected (0.02 sec) mysql> delimiter // mysql> CREATE PROCEDURE sp_auto_rollback_demo() -> BEGIN -> DECLARE EXIT HANDLER FOR SQLEXCEPTION ROLLBACK; -> START TRANSACTION; -> INSERT INTO b SELECT 1; -> INSERT INTO b SELECT 2; -> INSERT INTO b SELECT 1; -> INSERT INTO b SELECT 3; -> COMMIT; -> END; -> // Query OK, 0 rows affected (0.00 sec)
调用此存储过程, 发现有 warning.
mysql> delimiter ; mysql> call sp_auto_rollback_demo(); Query OK, 0 rows affected, 1 warning (0.01 sec)
mysql> show warnings; +-------+------+---------------------------------------+ | Level | Code | Message | +-------+------+---------------------------------------+ | Error | 1062 | Duplicate entry '1' for key 'PRIMARY' | +-------+------+---------------------------------------+ 1 row in set (0.00 sec)
因此不建议在存储过程中执行事务的
例如, 可以这样来写.
mysql> delimiter ?? mysql> CREATE PROCEDURE sp_rollback_demo() -> BEGIN -> INSERT INTO b SELECT 1; -> INSERT INTO b SELECT 2; -> INSERT INTO b SELECT 1; -> INSERT INTO b SELECT 3; -> END; -> ?? Query OK, 0 rows affected (0.00 sec)
在 Windows 下, 可以用 where python 查找python.exe所在的路径.
#! C:\\Users\\haife\\AppData\\Local\\Programs\\Python\\Python36\\python.exe #encoding=utf-8 import MySQLdb try: conn=MySQLdb.connect(host="127.0.0.1", user="root", passwd="qwerty", db="test") cur = conn.cursor() cur.execute("SET autocommit=0") cur.execute("CALL sp_rollback_demo") cur.execute("COMMIT") #except Exception,e: # for Python2 except Exception as e: # for Python3 cur.execute("ROLLBACK") #print e # for Python2 print(e) # for Python3
在执行该python程序时, 可能会碰到下面的问题, 找不到名为
D:\work\cs\python\mysql_connect>python test_demo.py Traceback (most recent call last): File "test_demo.py", line 4, inimport MySQLdb ModuleNotFoundError: No module named 'MySQLdb'
则执行
D:\work\cs\python\mysql_connect>pip install mysqlclient Collecting mysqlclient Downloading https://files.pythonhosted.org/packages/96/da/6bc460ef7a6df3d417914f17232259a4c869e63a3809d138ae786d7cf639/mysqlclient-1.4.6-cp36-cp36m-win_amd64.whl (185kB) 93% |██████████████████████████████ | 174kB 37kB/s eta 0:00:01 99% |████████████████████████████████| 184kB 37kB/s eta 0:00: 100% |████████████████████████████████| 194kB 47kB/s Installing collected packages: mysqlclient Successfully installed mysqlclient-1.4.6 You are using pip version 19.0.3, however version 20.1b1 is available. You should consider upgrading via the 'python -m pip install --upgrade pip' command.
此时再运行 test_demo.py, 就会看到错误了.
D:\work\cs\python\mysql_connect>python test_demo.py (1062, "Duplicate entry '1' for key 'PRIMARY'")
长事务, 即执行时间较长的事务.
例如, 对于银行系统的数据库, 其中要对含有1亿用户的表 account, 进行加息计算.
UPDATE account SET account_total = account_total * (1 + interest_rate);
对于长事务的问题, 有时可以通过转化为小批量(mini batch)的事务来进行处理.
考虑关系
现在顾客
与此同时,
(max) SELECT MAX(price) FROM Sells WHERE bar='Joe''s Bar'; (min) SELECT MIN(price) FROM Sells WHERE bar='Joe''s Bar';
同时,
(del) DELETE FROM Sells WHERE bar='Joe''s Bar'; (ins) INSERT INTO Sells VALUES ('Joe''s Bar','Heineken',3.50);
尽管 (max) 必须在 (min) 之前, 以及 (del) 必须在 (ins) 之前, 这些语句的顺序上没有其他的约束, 除非我们将
假设这四个语句执行的次序是 (max)(del)(ins)(min).
Joe's Prices: | {2.50, 3.00} | {2.50, 3.00} | {3.50} | |
Statement: | (max) | (del) | (ins) | (min) |
Result: | 3.00 | {3.50} |
如果我们将
她在某个固定的时刻看到
假设
如果
如果
若
比如你的选择是运行
例如: 若
若
例如: 在
它的要求类似于
假设
运行于
例子: 若
MySQL 的事务操作模式默认是自动提交模式。除非显式地开始一个事务,否则每个查询都被当做一个单独的事务自动执行。我们可以通过设置autocommit的值改变是否是自动提交autocommit模式。 通过以下命令可以查看当前autocommit模式
mysql> SHOW VARIABLES LIKE 'autocommit'; +---------------+-------+ | Variable_name | Value | +---------------+-------+ | autocommit | ON | +---------------+-------+ 1 row in set (0.02 sec)
使用
mysql> SET autocommit=0; Query OK, 0 rows affected (0.04 sec) mysql> SHOW VARIABLES LIKE 'autocommit'; +---------------+-------+ | Variable_name | Value | +---------------+-------+ | autocommit | OFF | +---------------+-------+ 1 row in set (0.00 sec)
mysql> SELECT * FROM test1; Empty set (0.05 sec) mysql> INSERT INTO test1 VALUES(1); Query OK, 1 row affected (0.00 sec) mysql> SELECT * FROM test1; +------+ | id | +------+ | 1 | +------+ 1 row in set (0.00 sec)
这里 autocommit已经是OFF, 但是上面的
mysql> rollback; Query OK, 0 rows affected (0.05 sec) mysql> SELECT * FROM test1; Empty set (0.00 sec)
mysql> SET autocommit=ON; Query OK, 0 rows affected (0.04 sec) mysql> SHOW VARIABLES LIKE 'autocommit'; +---------------+-------+ | Variable_name | Value | +---------------+-------+ | autocommit | ON | +---------------+-------+ 1 row in set (0.00 sec) mysql> INSERT INTO test1 VALUES(10); Query OK, 1 row affected (0.01 sec) mysql> SELECT * FROM test1; +------+ | id | +------+ | 10 | +------+ 1 row in set (0.00 sec)
mysql> ROLLBACK; Query OK, 0 rows affected (0.00 sec) mysql> SELECT * FROM test1; +------+ | id | +------+ | 10 | +------+ 1 row in set (0.00 sec)
此时会发现, 无法“回滚”到上一个状态。因为此时默认自动 commit, 因此状态已经确定。换句话说,
References:
http://www.qttc.net/201208175.html
http://www.runoob.com/mysql/mysql-transaction.html
在 MySQL 命令行的默认设置下,事务都是自动提交的,即执行 SQL 语句后就会马上执行 COMMIT 操作。因此要显式地开启一个事务务须使用命令 BEGIN 或 START TRANSACTION,或者执行命令 SET AUTOCOMMIT=0,用来禁止使用当前会话的自动提交。
事物控制语句:
MYSQL 事务处理主要有两种方法:
1、用 BEGIN, ROLLBACK, COMMIT来实现 BEGIN 开始一个事务 ROLLBACK 事务回滚 COMMIT 事务确认
2、直接用 SET 来改变 MySQL 的自动提交模式: SET AUTOCOMMIT=0 禁止自动提交 SET AUTOCOMMIT=1 开启自动提交
mysql> use RUNOOB; Database changed mysql> CREATE TABLE runoob_transaction_test( id int(5)) engine=innodb; # 创建数据表 Query OK, 0 rows affected (0.04 sec)
mysql> select * from runoob_transaction_test; Empty set (0.01 sec)
mysql> begin; # 开始事务 Query OK, 0 rows affected (0.00 sec)
mysql> insert into runoob_transaction_test value(5); Query OK, 1 rows affected (0.01 sec)
mysql> insert into runoob_transaction_test value(6); Query OK, 1 rows affected (0.00 sec)
mysql> commit; # 提交事务 Query OK, 0 rows affected (0.01 sec)
mysql> select * from runoob_transaction_test; +------+ | id | +------+ | 5 | | 6 | +------+ 2 rows in set (0.01 sec)
mysql> begin; # 开始事务 Query OK, 0 rows affected (0.00 sec)
mysql> insert into runoob_transaction_test values(7); Query OK, 1 rows affected (0.00 sec)
mysql> rollback; # 回滚 Query OK, 0 rows affected (0.00 sec)
mysql> select * from runoob_transaction_test; # 因为回滚所以数据没有插入 +------+ | id | +------+ | 5 | | 6 | +------+ 2 rows in set (0.01 sec) mysql>
因此, 它和通常的表十分类似. 可以使用
视图分为两种:
视图方便用户操作, 可以集中显示所需要的数据. 并且可以保障数据库系统的安全.
视图一经定义便存储在数据库中, 与其相对应的数据并没有像表那样在数据库中再存储一份.
通过视图看到的数据只是存放在基本表中的数据, 它们按照视图的定义虚拟地组织到一起.
当对视图中的数据进行修改时, 相应的基本表中的数据也要发生变化; 同样, 若基本表中的数据变更, 则着这种变化会自动反映到视图中.
视图可帮助用户屏蔽基础表结构变化带来的影响.
声明方式:
<name> AS <query>;
默认是虚拟视图.
根据 MySQL 5.5 的手册,
视图的建立命令为
CREATE [OR REPLACE] [ALGORITHM={UNDEFINED | MERGE | TEMPTABLE}] VIEW view_name [(column_list)] AS <query> [WITH [CASCADED | LOCAL] CHECK OPTION]
CREATE [OR REPLACE] [ALGORITHM = {UNDEFINED | MERGE | TEMPTABLE}] [DEFINER = user] [SQL SECURITY { DEFINER | INVOKER }] VIEW view_name [(column_list)] AS select_statement [WITH [CASCADED | LOCAL] CHECK OPTION]
假定情报人员需要收集某些人喜欢的啤酒种类, 以了解被调查人员的喜好. 但是他只能跟踪此人是否进入了某个酒吧, 等该人离去后, 他再去酒吧了解酒吧卖什么啤酒.
于是可以创建一个名为
CREATE VIEW mayDrink AS SELECT drinker, beer FROM Frequents, Sells WHERE Frequents.bar=Sells.bar;
mysql> show tables; +--------------------+ | Tables_in_test_bar | +--------------------+ | Bars | | Beers | | Drinkers | | Frequents | | Likes | | PotBuddies | | Sells | +--------------------+ 7 rows in set (0.00 sec)
现在建立一个名为
mysql> CREATE VIEW mayDrink AS -> SELECT drinker, beer -> FROM Frequents, Sells -> WHERE Frequents.bar=Sells.bar; Query OK, 0 rows affected (0.06 sec)
mysql> show tables; +--------------------+ | Tables_in_test_bar | +--------------------+ | Bars | | Beers | | mayDrink | | Drinkers | | Frequents | | Likes | | PotBuddies | | Sells | +--------------------+ 8 rows in set (0.00 sec)
注意不是
mysql> show create view mayDrink\G; *************************** 1. row *************************** View: mayDrink Create View: CREATE ALGORITHM=UNDEFINED DEFINER=`haifeng`@`localhost` SQL SECURITY DEFINER VIEW `mayDrink` AS select `Frequents`.`drinker` AS `drinker`,`Sells`.`beer` AS `beer` from (`Frequents` join `Sells`) where (`Frequents`.`bar` = `Sells`.`bar`) character_set_client: utf8 collation_connection: utf8_general_ci 1 row in set (0.00 sec)
使用
改变 Frequents 或 Sells 表的结构. 看是否对视图有影响.
Frequents表只有两个属性
mysql> desc Frequents; +---------+--------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +---------+--------------+------+-----+---------+-------+ | drinker | char(20) | NO | PRI | NULL | | | bar | varchar(255) | NO | PRI | NULL | | +---------+--------------+------+-----+---------+-------+ 2 rows in set (0.00 sec)
现在增加一个属性
mysql> ALTER TABLE Frequents -> ADD time Datetime after bar; Query OK, 0 rows affected (0.08 sec) Enregistrements: 0 Doublons: 0 Avertissements: 0再次查询
mysql> desc mayDrink; +---------+-------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +---------+-------------+------+-----+---------+-------+ | drinker | char(20) | NO | | NULL | | | beer | varchar(20) | NO | | NULL | | +---------+-------------+------+-----+---------+-------+ 2 rows in set (0.04 sec)
如果删除
mysql> ALTER TABLE Frequents -> DROP drinker; Query OK, 0 rows affected (0.04 sec) Enregistrements: 0 Doublons: 0 Avertissements: 0
mysql> desc mayDrink; ERROR 1356 (HY000): View 'test_bar.maydrink' references invalid table(s) or column(s) or function(s) or definer/invoker of view lack rights to use them mysql> select * from mayDrink; ERROR 1356 (HY000): View 'test_bar.maydrink' references invalid table(s) or column(s) or function(s) or definer/invoker of view lack rights to use them
不过
mysql> SHOW CREATE VIEW mayDrink\G *************************** 1. row *************************** View: maydrink Create View: CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `maydrink` AS select `frequents`.`drinker` AS `drinker`,`sells`.`beer` AS `beer` from (`frequents` join `sells`) where (`frequents`.`bar` = `sells`.`bar`) character_set_client: gbk collation_connection: gbk_chinese_ci 1 row in set, 1 warning (0.00 sec)
恢复 Frequents 表的 drinker 属性.
mysql> show create table Frequents\G *************************** 1. row *************************** Table: Frequents Create Table: CREATE TABLE `frequents` ( `bar` varchar(255) NOT NULL, `time` datetime DEFAULT NULL, PRIMARY KEY (`bar`) ) ENGINE=MyISAM DEFAULT CHARSET=latin1 1 row in set (0.00 sec)
mysql> ALTER TABLE Frequents -> ADD drinker char(20) AFTER bar; Query OK, 0 rows affected (0.06 sec) Enregistrements: 0 Doublons: 0 Avertissements: 0 mysql> desc Frequents; +---------+--------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +---------+--------------+------+-----+---------+-------+ | bar | varchar(255) | NO | PRI | NULL | | | drinker | char(20) | YES | | NULL | | | time | datetime | YES | | NULL | | +---------+--------------+------+-----+---------+-------+ 3 rows in set (0.00 sec)
mysql> ALTER TABLE Frequents -> MODIFY bar varchar(255) after drinker; Query OK, 0 rows affected (0.02 sec) Enregistrements: 0 Doublons: 0 Avertissements: 0
mysql> desc Frequents; +---------+--------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +---------+--------------+------+-----+---------+-------+ | drinker | char(20) | YES | | NULL | | | bar | varchar(255) | NO | PRI | NULL | | | time | datetime | YES | | NULL | | +---------+--------------+------+-----+---------+-------+ 3 rows in set (0.01 sec)
mysql> ALTER TABLE Frequents DROP PRIMARY KEY; Query OK, 0 rows affected (0.03 sec) Enregistrements: 0 Doublons: 0 Avertissements: 0 mysql> ALTER TABLE Frequents -> ADD PRIMARY KEY(drinker,bar); Query OK, 0 rows affected (0.04 sec) Enregistrements: 0 Doublons: 0 Avertissements: 0
可以把视图当作一个基本表(即存储的表)来查询.
查询的例子:
SELECT beer FROM CanDrink WHERE drinker='Sally';
mysql> select beer from CanDrink -> where drinker='John von Neumann'; +-----------------+ | beer | +-----------------+ | Stella Artois | | 健力士黑啤 | | 喜力 | | 安贝夫 | | 百威 | | 科罗娜 | | 米勒 | | 纯种苦啤酒 | | 贝克 | | 麒麟 | +-----------------+ 10 rows in set (0.00 sec)
一般的, 是不可能修改一个虚拟视图的, 因为它并不存在.
但是名为
例子: 视图
CREATE VIEW Synergy AS SELECT Likes.drinker, Likes.beer, Sells.bar FROM Likes, Sells, Frequents WHERE Likes.drinker=Frequents.drinker AND Likes.beer=Sells.beer AND Sells.bar=Frequents.bar;
mysql> CREATE VIEW Synergy AS -> SELECT Likes.drinker, Likes.beer, Sells.bar -> FROM Likes, Sells, Frequents -> WHERE Likes.drinker=Frequents.drinker -> AND Likes.beer=Sells.beer -> AND Sells.bar=Frequents.bar; Query OK, 0 rows affected (0.01 sec)
mysql> SHOW CREATE VIEW Synergy\G *************************** 1. row *************************** View: synergy Create View: CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `synergy` AS select `likes`.`drinker` AS `drinker`,`likes`.`beer` AS `beer`,`sells`.`bar` AS `bar` from ((`likes` join `sells`) join `frequents`) where ((`likes`.`drinker` = `frequents`.`drinker`) and (`likes`.`beer` = `sells`.`beer`) and (`sells`.`bar` = `frequents`.`bar`)) character_set_client: gbk collation_connection: gbk_chinese_ci 1 row in set (0.00 sec)
删除视图使用
mysql> DROP TABLE Synergy; ERROR 1051 (42S02): Table 'test_bar.synergy' inconnue mysql> DROP VIEW Synergy; Query OK, 0 rows affected (0.01 sec)
我们不能插入数据到
但是我们可以使用
CREATE TRIGGER ViewTrig INSTEAD OF INSERT ON Synergy REFERENCING NEW ROW AS n FOR EACH ROW BEGIN INSERT INTO LIKES VALUES(n.drinker, n.beer); INSERT INTO SELLS(bar, beer) VALUES(n.bar, n.beer); INSERT INTO FREQUENTS VALUES(n.drinker, n.bar); END;
MySQL 8.4.2 不支持
delimiter // CREATE TRIGGER ViewTrig INSTEAD OF INSERT ON Synergy FOR EACH ROW BEGIN INSERT INTO LIKES VALUES(NEW.drinker, NEW.beer); INSERT INTO SELLS(bar, beer) VALUES(NEW.bar, NEW.beer); INSERT INTO FREQUENTS VALUES(NEW.drinker, NEW.bar); END//
mysql> delimiter // mysql> CREATE TRIGGER ViewTrig -> INSTEAD OF INSERT ON Synergy -> FOR EACH ROW -> BEGIN -> INSERT INTO LIKES VALUES(NEW.drinker, NEW.beer); -> INSERT INTO SELLS(bar, beer) VALUES(NEW.bar, NEW.beer); -> INSERT INTO FREQUENTS VALUES(NEW.drinker, NEW.bar); -> END// 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 'INSTEAD OF INSERT ON Synergy FOR EACH ROW BEGIN INSERT INTO LIKES VALUES' at line 2
问题: 每次基本表改变后, 物化视图也要跟着改变.
解决方案: 周期性重建物化视图, 否则会过时.
在 MySQL 下, 检查 CanDrink 中的内容是否会因其基本表中的内容改变而改变. 首先注意到 CanDrink 中有这样一条记录 atzjg | 南非啤酒
mysql> select * from CanDrink where drinker="atzjg"; +---------+-----------------+ | drinker | beer | +---------+-----------------+ | atzjg | 健力士黑啤 | | atzjg | 南非啤酒 | | atzjg | 朝日 | | atzjg | 科罗娜 | | atzjg | 纯种苦啤酒 | | atzjg | 健力士黑啤 | | atzjg | 南非啤酒 | | atzjg | 喜力 | | atzjg | 朝日 | | atzjg | 科罗娜 | | atzjg | 纯种苦啤酒 | +---------+-----------------+ 11 rows in set (0.00 sec)
我们希望更改 atzjg 或者南非啤酒. 注意 CanDrink 的基本表是 Frequents 和 Sells. 不过 Sells 中有外键约束, 因此要更改南非啤酒, 必须通过更改 Beers 表才可以. 另一种方式是更改 atzjg. 这可以在 Frequents 中完成.
刚才讲到视图的插入本质上是对基本表的插入. 视图的更新也是对于基本表进行更新.
假设我们已经创建了
mysql> desc mayDrink; +---------+-------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +---------+-------------+------+-----+---------+-------+ | drinker | char(20) | NO | | | | | beer | varchar(20) | NO | | | | +---------+-------------+------+-----+---------+-------+ 2 rows in set (0.03 sec)
视图中属性来源于基本表, 因此如果要更改属性类型, 这个只能通过对基本表进行修改来实现. 比如现在将
mysql> alter table frequents modify drinker varchar(30); Query OK, 0 rows affected (0.04 sec) Records: 0 Duplicates: 0 Warnings: 0 mysql> desc maydrink; +---------+-------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +---------+-------------+------+-----+---------+-------+ | drinker | varchar(30) | NO | | | | | beer | char(20) | YES | | NULL | | +---------+-------------+------+-----+---------+-------+ 2 rows in set (0.04 sec)
使用
mysql> show create view maydrink\G; *************************** 1. row *************************** View: maydrink Create View: CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `maydrink` AS select `frequents`.`drinker` AS `drinker`,`sells`.`beer` AS `beer` from (`frequents` join `sells`) where (`frequents`.`bar` = `sells`.`bar`) character_set_client: gbk collation_connection: gbk_chinese_ci 1 row in set (0.00 sec)
我们将其稍作修改:
mysql> CREATE or REPLACE VIEW mayDrink AS -> SELECT drinker AS person, Frequents.bar, beer -> FROM Frequents, Sells -> WHERE Frequents.bar=Sells.bar; Query OK, 0 rows affected (0.02 sec) mysql> desc mayDrink; +--------+--------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +--------+--------------+------+-----+---------+-------+ | person | varchar(30) | NO | | | | | bar | varchar(255) | NO | | | | | beer | char(20) | YES | | NULL | | +--------+--------------+------+-----+---------+-------+ 3 rows in set (0.03 sec)
mysql> DROP VIEW mayDrink; Query OK, 0 rows affected (0.00 sec)
The class mailing list cs145-aut0708- students is in effect a materialized view of the class enrollment in Axess.
Actually updated four times/day. You can enroll and miss an email sent out after you enroll.
沃尔玛(Wal-Mart)将每个店的销售情况存储在数据库中.
晚上, 一天的销售被用于更新
数据仓库是分析师用来预测趋势并将货物运送到卖得最好的地方.
也可以是一个 hash 表, 但在 DBMS 中, it is always a balanced search tree with giant nodes (a full disk page) called a B-tree.
若不使用索引, 则 MySQL 必须从第一条记录开始读完整个表, 直到找到相关的行. 表越大, 查询数据所花费的时间越多.
如果表中查询的列有一个索引, MySQL 就能快速到达一个位置去搜寻数据文件, 而不必查看所有数据.
例如: 数据库中有 2 万条记录, 现在要执行这样一个查询:
SELECT * FROM myTable WHERE num=10000;
如果没有索引, 必须遍历整个表, 直到
索引是在存储引擎中实现的, 因此, 每种存储引擎的索引都不一定完全相同, 并且每种存储引擎也不一定支持所有索引类型.
根据存储引擎定义每个表的最大索引数和最大索引长度.
所有存储引擎支持每个表至少 16 个索引, 总索引长度至少为 256 字节.
MySQL 中索引的存储类型有两种:
MySQL 中的索引可以分为以下几类:
没有标准!
CREATE INDEX BeerInd ON Beers (manf); CREATE INDEX SellInd ON Sells (bar, beer);
CREATE TABLE `Beers2` ( `name` varchar(20) DEFAULT NULL, `manf` varchar(50) DEFAULT NULL, UNIQUE KEY `name` (`name`), INDEX(name) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
使用
mysql> show create table Beers2\G; *************************** 1. row *************************** Table: Beers2 Create Table: CREATE TABLE `Beers2` ( `name` varchar(20) DEFAULT NULL, `manf` varchar(50) DEFAULT NULL, UNIQUE KEY `name` (`name`), KEY `name_2` (`name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 1 row in set (0.00 sec)
使用
mysql> explain select * from Beers2 where name="abc"\G; *************************** 1. row *************************** id: 1 select_type: SIMPLE table: NULL type: NULL possible_keys: NULL key: NULL key_len: NULL ref: NULL rows: NULL Extra: Impossible WHERE noticed after reading const tables 1 row in set (0.00 sec) ERROR: No query specified
别忘了我们还没有添加数据
mysql> insert into Beers2 values("abc","AB"); Query OK, 1 row affected (0.42 sec)
mysql> explain select * from Beers2 where name="abc"\G; *************************** 1. row *************************** id: 1 select_type: SIMPLE table: Beers2 type: const possible_keys: name,name_2 key: name key_len: 63 ref: const rows: 1 Extra: 1 row in set (0.00 sec)
在已经存在的表中创建索引, 可以使用
ALTER TABLE table_name ADD [UNIQUE | FULLTEXT | SPATIAL] [INDEX | KEY] [index_name](col_name[length],...[ASC | DESC]
mysql> ALTER TABLE Beers ADD INDEX BeerNameIdx(manf(20)); Query OK, 0 rows affected (0.49 sec) Records: 0 Duplicates: 0 Warnings: 0
可以使用
mysql> show index from Beers\G; *************************** 1. row *************************** Table: Beers Non_unique: 0 Key_name: name Seq_in_index: 1 Column_name: name Collation: A Cardinality: 14 Sub_part: NULL Packed: NULL Null: YES Index_type: BTREE Comment: Index_comment: *************************** 2. row *************************** Table: Beers Non_unique: 1 Key_name: BeerNameIdx Seq_in_index: 1 Column_name: manf Collation: A Cardinality: 14 Sub_part: 20 Packed: NULL Null: YES Index_type: BTREE Comment: Index_comment: 2 rows in set (0.00 sec)
创建惟一索引的主要原因是减少查询索引列操作的执行时间, 尤其是对比较大的数据表.
CREATE TABLE `users` ( `id` varchar(20) DEFAULT NULL, `name` varchar(50) DEFAULT NULL, UNIQUE INDEX UniqIdx(id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
mysql> show create table users\G; *************************** 1. row *************************** Table: users Create Table: CREATE TABLE `users` ( `id` varchar(20) DEFAULT NULL, `name` varchar(50) DEFAULT NULL, UNIQUE KEY `UniqIdx` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 1 row in set (0.00 sec)
先将刚才建立的
DROP TABLE users;
一个表中可以创建多个单列索引.
CREATE TABLE `users` ( `id` varchar(20) DEFAULT NULL, `name` varchar(50) DEFAULT NULL, `mobile` varchar(15) DEFAULT NULL, INDEX id_Idx(id), INDEX mobile_Idx(mobile) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
mysql> show create table users\G; *************************** 1. row *************************** Table: users Create Table: CREATE TABLE `users` ( `id` varchar(20) DEFAULT NULL, `name` varchar(50) DEFAULT NULL, `mobile` varchar(15) DEFAULT NULL, KEY `id_Idx` (`id`), KEY `mobile_Idx` (`mobile`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 1 row in set (0.00 sec)
先将刚才建立的
DROP TABLE users;
组合索引是指在多个属性上创建一个索引.
CREATE TABLE `users` ( `id` varchar(20) DEFAULT NULL, `name` varchar(50) DEFAULT NULL, `mobile` varchar(15) DEFAULT NULL, INDEX multi_Idx(id,mobile) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
mysql> show create table users\G;*************************** 1. row *************************** Table: users Create Table: CREATE TABLE `users` ( `id` varchar(20) DEFAULT NULL, `name` varchar(50) DEFAULT NULL, `mobile` varchar(15) DEFAULT NULL, KEY `multi_Idx` (`id`,`mobile`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 1 row in set (0.00 sec)
先将刚才建立的
DROP TABLE users;
全文索引可以用于全文搜索, 只有
CREATE TABLE `users` ( `id` varchar(20) DEFAULT NULL, `name` varchar(50) DEFAULT NULL, `mobile` varchar(15) DEFAULT NULL, `info` TEXT DEFAULT NULL, FULLTEXT INDEX info_Idx(info) ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
MySQL5.5 中默认引擎为
下面建立的索引既是全文索引又是组合索引, 即全文组合索引.
CREATE TABLE `users` ( `id` varchar(20) DEFAULT NULL, `name` varchar(50) DEFAULT NULL, `mobile` varchar(15) DEFAULT NULL, `info` TEXT DEFAULT NULL, FULLTEXT INDEX info_Idx(mobile,info) ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
空间索引也必须在
在空间类型为
CREATE TABLE `users` ( `id` varchar(20) DEFAULT NULL, `name` varchar(50) DEFAULT NULL, `mobile` varchar(15) DEFAULT NULL, `info` TEXT DEFAULT NULL, `g` GEOMETRY NOT NULL, SPATIAL INDEX spatIdx(g) ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
mysql> show index from users\G; *************************** 1. row *************************** Table: users Non_unique: 1 Key_name: spatIdx Seq_in_index: 1 Column_name: g Collation: A Cardinality: NULL Sub_part: 32 Packed: NULL Null: Index_type: SPATIAL Comment: Index_comment: 1 row in set (0.00 sec)
mysql> drop table users; Query OK, 0 rows affected (0.00 sec) mysql> CREATE TABLE `users` ( -> `id` varchar(20) DEFAULT NULL, -> `name` varchar(50) DEFAULT NULL, -> `mobile` varchar(15) DEFAULT NULL, -> `info` TEXT DEFAULT NULL, -> `g` GEOMETRY, -> SPATIAL INDEX spatIdx(g) -> ) ENGINE=MyISAM DEFAULT CHARSET=utf8; ERROR 1252 (42000): All parts of a SPATIAL index must be NOT NULL mysql>
可以使用
mysql> alter table users drop index spatIdx; Query OK, 0 rows affected (0.61 sec) Records: 0 Duplicates: 0 Warnings: 0
mysql> show index from users\G; Empty set (0.00 sec)
mysql> alter table users add spatial index spatIdx(g); Query OK, 0 rows affected (0.40 sec) Records: 0 Duplicates: 0 Warnings: 0
mysql> drop index spatIdx on users; Query OK, 0 rows affected (0.38 sec) Records: 0 Duplicates: 0 Warnings: 0
给定值 $v$, 索引带我们到那些在索引中具有值 $v$ 的那些元组.
例子: 利用索引
SELECT price FROM Beers, Sells WHERE manf='Pete''s' AND Beers.name=Sells.beer AND bar='Joe''s Bar';
使数据库跑得快的一个主要问题是决定要建立哪个索引.
正面: 索引会加快查询的速度.
反面: 索引也会降低其所属关系的所有更新速度, 因为关系的更新要求其索引也必须更新.
假设对于啤酒数据库我们要做的事情仅是下面两种:
则基于
是主要的研究推力.
An advisor gets a query load, e.g.:
The advisor generates candidate indexes and evaluates each on the workload.