mysql事务
事务的四大特性(ACID)
- 原子性(Atomicity)
- 一致性(Consistency)
- 隔离性(Isolation)
- 持久性(Durability)
原子性:事务内执行的sql操作当作一个整体,要么同时成功,要么同时失败回滚
一致性:强调的是数据的状态,指的是数据库从一个一致性状态到另外一个一致性状态,打个比方,A有200,B有100,A转100给B,那么最后的一致性状态是A有100,B有200,如果状态是A有100,B还是原来的100,那这就不符合数据的一致性
隔离性:一个事务所做的修改在最终提交以前,对其他事务是不可见的,也就是说,A有200,B有100,A转100,B加100,还没有提交的时候,又开启了一个事物,此时看到的是A有200,B有100,也就是第一个事物没有commit之前的状态;还有一点就是说事物对某 行的某个字段进行加操作,那么在没有提交之前,其他事物是不能修改这一行的这个字段的,因为mysql此时的行锁被第一个事物拿到,commit之后才会释放
持久性:事物一旦进行了提交,那么数据落盘,所做的修改会永久保存到数据库
事务的隔离级别
事务的隔离级别是为了解决mysql并发问题的几种控制手段。
事务的并发问题
1、脏读:事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据
2、不可重复读:事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果 不一致。
3、幻读:系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是系统管理员B就在这个时候插入了一条具体分数的记录,当系统管理员A改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。
小结:不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表
单条修改数据的命令会自动的触发事务,包括insert、update、delete。
而在SQL语句中有手动开启事务的原因是:可以进行多次数据的修改,如果成功一起成功,否则一起会滚到之前的数据.
4种隔离级别
Read Uncommitted(读取未提交内容)
在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少。读取未提交的数据,也被称之为脏读(Dirty Read)。
Read Committed(读取提交内容)
这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。,因为同一事务的其他实例在该实例处理其间可能会有新的commit,所以同一select可能返回不同结果,这就是不可重复读
Repeatable Read(可重读)
这是MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。不过理论上,这会导致另一个棘手的问题:幻读 (Phantom Read)。简单的说,幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影” 行。InnoDB和Falcon存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)机制解决了该问题。
Serializable(可串行化)
这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。
在测试的时候,把mysql事务级别设置成 Repeatable Read,一个事务往里插数据,另一个事务是看不到其他事务插入的数据的,即读的不是最新数据,我们把这种现象也看作是幻读
查看和设置隔离级别
SELECT @@tx_isolation
设置mysql的隔离级别:set session transaction isolation level 设置事务隔离级别
pymysql
对于pymysql有几个点需要注意
默认支持事务
pymysql默认开启事务,从每次执行执行增删改都需要最终commit就能知道。
通过pymysql增加数据但未提交时候,发现表的AUTO_INCREMENT
是一直递增的(说明数据库已经收到插入指令了,就等着拍板呢),这是为了解决并发的考量,并且如果回滚AUTO_INCREMENT
并不会减少,而是一直递增下去,这样才不会出错。
cursor对象存放查询的数据
import pymysql
conn = pymysql.connect(host='localhost', user='root', password="123456",database='test', port=3306, charset='utf8')
cur = conn.cursor()
cur.execute('select * from department')
Out[5]: 4
cur.fetchone()
Out[6]: (200, '技术')
cur.execute('select * from employee')
Out[7]: 18
cur.fetchone()
Out[8]:
(1,
'egon',
'male',
18,
datetime.date(2017, 3, 1),
'办事处外交大使',
None,
7300.33,
401,
1)
通过上面我们可以发现,当我们查询的时候,取数据没有取完就执行另一条查询语句,那么先前的查询结果会被覆盖。也就是说执行了一条查询语句,那么就应该立即取出结果,否则拿着这个cursor去执行任何增删改查操作,原来存储的查询数据都会被清掉
pymysql模块的threadsafety = 1
如果在多个线程中的cursor共用一个conn连接,那么就会报类似pymysql.err.InternalError: Packet sequence number wrong - got 45 expected 0
的错误
- 每个线程拥有自己的连接
- 所有线程共用一个连接池,需要考虑线程总数和连接池连接数上限的问题
pymysql遇到的幻读
之前我开了两个线程,一个线程取数据,一个线程往表插入数据,最终的结果是每次取得的数据都是一样的,但是数据表确确实实是在不停地增加数据。这是因为数据库的隔离级别造成的.解决办法有两个:
- 修改隔离级别
- 建立conn的时候加上
autocommit=True
参数