包含postgresql死锁的词条
SQL基础教程的目录
1.1 运行SQL程序
创新互联坚持“要么做到,要么别承诺”的工作理念,服务领域包括:网站设计、网站制作、企业官网、英文网站、手机端网站、网站推广等服务,满足客户于互联网时代的西固网站设计、移动媒体设计的需求,帮助企业找到有效的互联网解决方案。努力成为您成熟可靠的网络建设合作伙伴!
1.2 Microsoft Access
1.3 Microsoft SQL Server
1.3.1 SQL Server 2000
1.3.2 SQL Server 2005/2008
1.4 Oracle
1.5 IBM DB2
1.6 MySQL
1.7 PostgreSQL 2.1 表、列和行
2.1.1 表
2.1.2 列
2.1.3 行
2.2 主键
2.3 外键
2.4 联系
2.4.1 一对一
2.4.2 一对多
2.4.3 多对多
2.5 规范化
2.5.1 第一范式
2.5.2 第二范式
2.5.3 第三范式
2.5.4 其他范式
2.6 示例数据库
2.6.1 表authors
2.6.2 表publishers
2.6.3 表titles
2.6.4 表titles_authors
2.6.5 表royalties
2.7 创建示例数据库 3.1 SQL语法
3.2 SQL标准和一致性
3.3 标识符
3.4 数据类型
3.5 字符串类型
3.6 二进制大型对象类型
3.7 精确数字类型
3.8 近似数字类型
3.9 布尔类型
3.10 日期和时间类型
3.11 时间间隔类型
3.12 唯一标识符
3.13 其他数据类型
3.14 空值 4.1 使用SELECT和FROM检索列
4.2 使用AS创建列的别名
4.3 使用DISTINCT消除重复的行
4.4 使用ORDER BY排序行
4.5 使用WHERE筛选行
4.6 使用AND、OR和NOT组合及求反条件
4.6.1 AND操作符
4.6.2 OR操作符
4.6.3 NOT操作符
4.6.4 AND、OR和NOT一起使用
4.7 使用LIKE匹配模式
4.8 使用BETWEEN进行范围筛选
4.9 使用IN进行列表筛选
4.10 使用IS NULL测试空值 5.1 创建派生列
5.2 执行算术运算
5.3 确定计算的顺序
5.4 使用||连接串
5.5 使用SUBSTRING()提取子串
5.6 使用UPPER()和LOWER()更改串的大小写
5.7 使用TRIM()修整字符
5.8 使用CHARACTER_LENGTH()得到串长度
5.9 使用POSITION()查找子串
5.10 执行日期及时间间隔运算
5.11 获得当前日期和时间
5.12 获得用户信息
5.13 使用CAST()转换数据类型
5.14 使用CASE计算条件值
5.15 使用COALESCE()检查空值
5.16 使用NULLIF()比较表达式 6.1 使用聚合函数
6.2 创建聚合表达式
6.3 使用MIN()查找最小值
6.4 使用MAX()查找最大值
6.5 使用SUM()计算总和
6.6 使用AVG()计算平均值
6.7 使用COUNT()统计行数
6.8 使用DISTINCT聚合不重复的值
6.9 使用GROUP BY分组行
6.10 使用HAVING筛选分组 7.1 限定列名
7.2 使用AS创建表的别名
7.3 使用联结
7.4 使用JOIN或WHERE创建联结
7.5 使用CROSS JOIN创建交叉联结
7.6 使用NATURAL JOIN创建自然联结
7.7 使用INNER JOIN创建内联结
7.8 使用OUTER JOIN创建外联结
7.9 创建自联结 8.1 理解子查询
8.2 子查询语法
8.3 子查询和联结
8.4 简单子查询和相关子查询
8.4.1 简单子查询
8.4.2 相关子查询
8.5 在子查询中限定列名
8.6 子查询中的空值
8.7 使用子查询作为列表达式
8.8 使用比较操作符比较子查询的值
8.9 使用IN测试集合成员资格
8.10 使用ALL比较所有子查询的值
8.11 使用ANY比较某些子查询的值
8.12 使用EXISTS检测存在性
8.13 比较等价查询 9.1 使用UNION合并行
9.2 使用INTERSECT查找相同行
9.3 使用EXCEPT查找不同行 10.1 显示表结构
10.2 使用INSERT插入行
10.3 使用UPDATE更新行
10.4 使用DELETE删除行 11.1 创建表
11.2 理解约束
11.3 使用CREATE TABLE创建新表
11.4 使用NOT NULL禁止空值
11.5 使用DEFAULT确定默认值
11.6 使用PRIMARY KEY指定主键
11.7 使用FOREIGN KEY指定外键
11.8 使用UNIQUE确保值唯一
11.9 使用CHECK创建检查约束
11.10 使用CREATE TEMPORARY TABLE创建临时表
11.11 使用CREATE TABLE AS利用已存在表创建新表
11.12 使用ALTER TABLE修改表
11.13 使用DROP TABLE删除表 12.1 使用CREATE INDEX创建索引
12.2 使用DROP INDEX删除索引 13.1 使用CREATE VIEW创建视图
13.2 通过视图检索数据
13.3 通过视图修改数据
13.3.1 通过视图插入行
13.3.2 通过视图更新行
13.3.3 通过视图删除行
13.4 使用DROP VIEW删除视图 15.1 动态统计
15.2 产生序列
15.3 发现等差数列、递增数列和等值数列
15.4 限定返回行的数量
15.4.1 Microsoft Access
15.4.2 Microsoft SQL Server
15.4.3 Oracle
15.4.4 IBM DB2
15.4.5 MySQL
15.4.6 PostgreSQL
15.5 分配排名
15.6 计算修整均值
15.7 随机选取行
15.8 处理重复值
15.9 创建电话列表
15.10 检索元数据
15.10.1 Microsoft Access
15.10.2 Microsoft SQL Server
15.10.3 Oracle
15.10.4 IBM DB2
15.10.5 MySQL
15.10.6 PostgreSQL
15.11 处理日期
15.11.1 Microsoft Access
15.11.2 Microsoft SQL Server
15.11.3 Oracle
15.11.4 IBM DB2
15.11.5 MySQL
15.11.6 PostgreSQL
15.12 计算中值
15.13 查询极值
15.14 改变动态统计的中流
15.15 旋转结果
15.16 处理层次结构
索引
分库分表 VS newsql数据库
最近与同行 科技 交流,经常被问到分库分表与分布式数据库如何选择,网上也有很多关于中间件+传统关系数据库(分库分表)与NewSQL分布式数据库的文章,但有些观点与判断是我觉得是偏激的,脱离环境去评价方案好坏其实有失公允。
本文通过对两种模式关键特性实现原理对比,希望可以尽可能客观、中立的阐明各自真实的优缺点以及适用场景。
首先关于“中间件+关系数据库分库分表”算不算NewSQL分布式数据库问题,国外有篇论文pavlo-newsql-sigmodrec,如果根据该文中的分类,Spanner、TiDB、OB算是第一种新架构型,Sharding-Sphere、Mycat、DRDS等中间件方案算是第二种(文中还有第三种云数据库,本文暂不详细介绍)。
基于中间件(包括SDK和Proxy两种形式)+传统关系数据库(分库分表)模式是不是分布式架构?我觉得是的,因为存储确实也分布式了,也能实现横向扩展。但是不是"伪"分布式数据库?从架构先进性来看,这么说也有一定道理。"伪"主要体现在中间件层与底层DB重复的SQL解析与执行计划生成、存储引擎基于B+Tree等,这在分布式数据库架构中实际上冗余低效的。为了避免引起真伪分布式数据库的口水战,本文中NewSQL数据库特指这种新架构NewSQL数据库。
NewSQL数据库相比中间件+分库分表的先进在哪儿?画一个简单的架构对比图:
这些大多也是NewSQL数据库产品主要宣传的点,不过这些看起来很美好的功能是否真的如此?接下来针对以上几点分别阐述下的我的理解。
这是把双刃剑。
CAP限制
想想更早些出现的NoSQL数据库为何不支持分布式事务(最新版的mongoDB等也开始支持了),是缺乏理论与实践支撑吗?并不是,原因是CAP定理依然是分布式数据库头上的颈箍咒,在保证强一致的同时必然会牺牲可用性A或分区容忍性P。为什么大部分NoSQL不提供分布式事务?
那么NewSQL数据库突破CAP定理限制了吗?并没有。NewSQL数据库的鼻主Google Spanner(目前绝大部分分布式数据库都是按照Spanner架构设计的)提供了一致性和大于5个9的可用性,宣称是一个“实际上是CA”的,其真正的含义是 系统处于 CA 状态的概率非常高,由于网络分区导致的服务停用的概率非常小 ,究其真正原因是其打造私有全球网保证了不会出现网络中断引发的网络分区,另外就是其高效的运维队伍,这也是cloud spanner的卖点。详细可见CAP提出者Eric Brewer写的《Spanner, TrueTime 和CAP理论》。
完备性 :
两阶段提交协议是否严格支持ACID,各种异常场景是不是都可以覆盖?
2PC在commit阶段发送异常,其实跟最大努力一阶段提交类似也会有部分可见问题,严格讲一段时间内并不能保证A原子性和C一致性(待故障恢复后recovery机制可以保证最终的A和C)。完备的分布式事务支持并不是一件简单的事情,需要可以应对网络以及各种硬件包括网卡、磁盘、CPU、内存、电源等各类异常,通过严格的测试。之前跟某友商交流,他们甚至说目前已知的NewSQL在分布式事务支持上都是不完整的,他们都有案例跑不过,圈内人士这么笃定,也说明了 分布式事务的支持完整程度其实是层次不齐的。
但分布式事务又是这些NewSQL数据库的一个非常重要的底层机制,跨资源的DML、DDL等都依赖其实现,如果这块的性能、完备性打折扣,上层跨分片SQL执行的正确性会受到很大影响。
性能
传统关系数据库也支持分布式事务XA,但为何很少有高并发场景下用呢? 因为XA的基础两阶段提交协议存在网络开销大,阻塞时间长、死锁等问题,这也导致了其实际上很少大规模用在基于传统关系数据库的OLTP系统中。
NewSQL数据库的分布式事务实现也仍然多基于两阶段提交协议,例如google percolator分布式事务模型,
采用原子钟+MVCC+ Snapshot Isolation(SI),这种方式通过TSO(Timestamp Oracle)保证了全局一致性,通过MVCC避免了锁,另外通过primary lock和secondary lock将提交的一部分转为异步,相比XA确实提高了分布式事务的性能。
但不管如何优化,相比于1PC,2PC多出来的GID获取、网络开销、prepare日志持久化还是会带来很大的性能损失,尤其是跨节点的数量比较多时会更加显著,例如在银行场景做个批量扣款,一个文件可能上W个账户,这样的场景无论怎么做还是吞吐都不会很高。
虽然NewSQL分布式数据库产品都宣传完备支持分布式事务,但这并不是说应用可以完全不用关心数据拆分,这些数据库的最佳实践中仍然会写到,应用的大部分场景尽可能避免分布式事务。
既然强一致事务付出的性能代价太大,我们可以反思下是否真的需要这种强一致的分布式事务?尤其是在做微服务拆分后,很多系统也不太可能放在一个统一的数据库中。尝试将一致性要求弱化,便是柔性事务,放弃ACID(Atomicity,Consistency, Isolation, Durability),转投BASE(Basically Available,Soft state,Eventually consistent),例如Saga、TCC、可靠消息保证最终一致等模型,对于大规模高并发OLTP场景,我个人更建议使用柔性事务而非强一致的分布式事务。关于柔性事务,笔者之前也写过一个技术组件,最近几年也涌现出了一些新的模型与框架(例如阿里刚开源的Fescar),限于篇幅不再赘述,有空再单独写篇文章。
HA与异地多活
主从模式并不是最优的方式,就算是半同步复制,在极端情况下(半同步转异步)也存在丢数问题,目前业界公认更好的方案是基于paxos分布式一致性协议或者其它类paxos如raft方式,Google Spanner、TiDB、cockcoachDB、OB都采用了这种方式,基于Paxos协议的多副本存储,遵循过半写原则,支持自动选主,解决了数据的高可靠,缩短了failover时间,提高了可用性,特别是减少了运维的工作量,这种方案技术上已经很成熟,也是NewSQL数据库底层的标配。
当然这种方式其实也可以用在传统关系数据库,阿里、微信团队等也有将MySQL存储改造支持paxos多副本的,MySQL也推出了官方版MySQL Group Cluster,预计不远的未来主从模式可能就成为 历史 了。
需要注意的是很多NewSQL数据库厂商宣传基于paxos或raft协议可以实现【异地多活】,这个实际上是有前提的,那就是异地之间网络延迟不能太高 。以银行“两地三中心”为例,异地之间多相隔数千里,延时达到数十毫秒,如果要多活,那便需异地副本也参与数据库日志过半确认,这样高的延时几乎没有OLTP系统可以接受的。
数据库层面做异地多活是个美好的愿景,但距离导致的延时目前并没有好的方案。 之前跟蚂蚁团队交流,蚂蚁异地多活的方案是在应用层通过MQ同步双写交易信息,异地DC将交易信息保存在分布式缓存中,一旦发生异地切换,数据库同步中间件会告之数据延迟时间,应用从缓存中读取交易信息,将这段时间内涉及到的业务对象例如用户、账户进行黑名单管理,等数据同步追上之后再将这些业务对象从黑名单中剔除。由于双写的不是所有数据库操作日志而只是交易信息,数据延迟只影响一段时间内数据,这是目前我觉得比较靠谱的异地度多活方案。
另外有些系统进行了单元化改造,这在paxos选主时也要结合考虑进去,这也是目前很多NewSQL数据库欠缺的功能。
Scale横向扩展与分片机制
paxos算法解决了高可用、高可靠问题,并没有解决Scale横向扩展的问题,所以分片是必须支持的。NewSQL数据库都是天生内置分片机制的,而且会根据每个分片的数据负载(磁盘使用率、写入速度等)自动识别热点,然后进行分片的分裂、数据迁移、合并,这些过程应用是无感知的,这省去了DBA的很多运维工作量。以TiDB为例,它将数据切成region,如果region到64M时,数据自动进行迁移。
分库分表模式下需要应用设计之初就要明确各表的拆分键、拆分方式(range、取模、一致性哈希或者自定义路由表)、路由规则、拆分库表数量、扩容方式等。相比NewSQL数据库,这种模式给应用带来了很大侵入和复杂度,这对大多数系统来说也是一大挑战。
这里有个问题是NewSQL数据库统一的内置分片策略(例如tidb基于range)可能并不是最高效的,因为与领域模型中的划分要素并不一致,这导致的后果是很多交易会产生分布式事务。 举个例子,银行核心业务系统是以客户为维度,也就是说客户表、该客户的账户表、流水表在绝大部分场景下是一起写的,但如果按照各表主键range进行分片,这个交易并不能在一个分片上完成,这在高频OLTP系统中会带来性能问题。
分布式SQL支持
常见的单分片SQL,这两者都能很好支持。NewSQL数据库由于定位与目标是一个通用的数据库,所以支持的SQL会更完整,包括跨分片的join、聚合等复杂SQL。中间件模式多面向应用需求设计,不过大部分也支持带拆分键SQL、库表遍历、单库join、聚合、排序、分页等。但对跨库的join以及聚合支持就不够了。
NewSQL数据库一般并不支持存储过程、视图、外键等功能,而中间件模式底层就是传统关系数据库,这些功能如果只是涉及单库是比较容易支持的。
NewSQL数据库往往选择兼容MySQL或者PostgreSQL协议,所以SQL支持仅局限于这两种,中间件例如驱动模式往往只需做简单的SQL解析、计算路由、SQL重写,所以可以支持更多种类的数据库SQL。
SQL支持的差异主要在于分布式SQL执行计划生成器,由于NewSQL数据库具有底层数据的分布、统计信息,因此可以做CBO,生成的执行计划效率更高,而中间件模式下没有这些信息,往往只能基于规则RBO(Rule-Based-Opimization),这也是为什么中间件模式一般并不支持跨库join,因为实现了效率也往往并不高,还不如交给应用去做。
存储引擎
传统关系数据库的存储引擎设计都是面向磁盘的,大多都基于B+树。B+树通过降低树的高度减少随机读、进而减少磁盘寻道次数,提高读的性能,但大量的随机写会导致树的分裂,从而带来随机写,导致写性能下降。NewSQL的底层存储引擎则多采用LSM,相比B+树LSM将对磁盘的随机写变成顺序写,大大提高了写的性能。不过LSM的的读由于需要合并数据性能比B+树差,一般来说LSM更适合应在写大于读的场景。当然这只是单纯数据结构角度的对比,在数据库实际实现时还会通过SSD、缓冲、bloom filter等方式优化读写性能,所以读性能基本不会下降太多。NewSQL数据由于多副本、分布式事务等开销,相比单机关系数据库SQL的响应时间并不占优,但由于集群的弹性扩展,整体QPS提升还是很明显的,这也是NewSQL数据库厂商说分布式数据库更看重的是吞吐,而不是单笔SQL响应时间的原因。
成熟度与生态
分布式数据库是个新型通用底层软件,准确的衡量与评价需要一个多维度的测试模型,需包括发展现状、使用情况、社区生态、监控运维、周边配套工具、功能满足度、DBA人才、SQL兼容性、性能测试、高可用测试、在线扩容、分布式事务、隔离级别、在线DDL等等,虽然NewSQL数据库发展经过了一定时间检验,但多集中在互联网以及传统企业非核心交易系统中,目前还处于快速迭代、规模使用不断优化完善的阶段。
相比而言,传统关系数据库则经过了多年的发展,通过完整的评测,在成熟度、功能、性能、周边生态、风险把控、相关人才积累等多方面都具有明显优势,同时对已建系统的兼容性也更好。
对于互联网公司,数据量的增长压力以及追求新技术的基因会更倾向于尝试NewSQL数据库,不用再考虑库表拆分、应用改造、扩容、事务一致性等问题怎么看都是非常吸引人的方案。
对于传统企业例如银行这种风险意识较高的行业来说,NewSQL数据库则可能在未来一段时间内仍处于 探索 、审慎试点的阶段。基于中间件+分库分表模式架构简单,技术门槛更低,虽然没有NewSQL数据库功能全面,但大部分场景最核心的诉求也就是拆分后SQL的正确路由,而此功能中间件模式应对还是绰绰有余的,可以说在大多数OLTP场景是够用的。
限于篇幅,其它特性例如在线DDL、数据迁移、运维工具等特性就不在本文展开对比。
总结
如果看完以上内容,您还不知道选哪种模式,那么结合以下几个问题,先思考下NewSQL数据库解决的点对于自身是不是真正的痛点:
如果以上有2到3个是肯定的,那么你可以考虑用NewSQL数据库了,虽然前期可能需要一定的学习成本,但它是数据库的发展方向,未来收益也会更高,尤其是互联网行业,随着数据量的突飞猛进,分库分表带来的痛苦会与日俱增。当然选择NewSQL数据库你也要做好承担一定风险的准备。
如果你还未做出抉择,不妨再想想下面几个问题:
如果这些问题有多数是肯定的,那还是分库分表吧。在软件领域很少有完美的解决方案,NewSQL数据库也不是数据分布式架构的银弹。相比而言分库分表是一个代价更低、风险更小的方案,它最大程度复用传统关系数据库生态,通过中间件也可以满足分库分表后的绝大多数功能,定制化能力更强。 在当前NewSQL数据库还未完全成熟的阶段,分库分表可以说是一个上限低但下限高的方案,尤其传统行业的核心系统,如果你仍然打算把数据库当做一个黑盒产品来用,踏踏实实用好分库分表会被认为是个稳妥的选择。
很多时候软件选型取决于领域特征以及架构师风格,限于笔者知识与所属行业特点所限,以上仅为个人粗浅的一些观点,欢迎讨论。
PostgreSQL中怎么关闭死锁的进程
今天在进行测试的时候,发现一个数据表无法进行更新。一旦 运行更新sql,就进行等待。无法执行下去。感觉数据库进行了死锁。由于使用的PostgreSQL数据库,没有资料。只好进行谷歌。最终在一个英文论坛中发现了解决方法。如下:1.检索出死锁进程的ID。SELECT * FROM pg_stat_activity WHERE datname='死锁的数据库ID ';检索出来的字段中,【wating 】字段,数据为t的那条,就是死锁的进程。找到对应的【procpid 】列的值。2.将进程杀掉。
数据库为什么要分库分表
1 基本思想之什么是分库分表?
从字面上简单理解,就是把原本存储于一个库的数据分块存储到多个库上,把原本存储于一个表的数据分块存储到多个表上。
2 基本思想之为什么要分库分表?
数
据库中的数据量不一定是可控的,在未进行分库分表的情况下,随着时间和业务的发展,库中的表会越来越多,表中的数据量也会越来越大,相应地,数据操作,增
删改查的开销也会越来越大;另外,由于无法进行分布式式部署,而一台服务器的资源(CPU、磁盘、内存、IO等)是有限的,最终数据库所能承载的数据量、
数据处理能力都将遭遇瓶颈。
3 分库分表的实施策略。
分库分表有垂直切分和水平切分两种。
3.1
何谓垂直切分,即将表按照功能模块、关系密切程度划分出来,部署到不同的库上。例如,我们会建立定义数据库workDB、商品数据库payDB、用户数据
库userDB、日志数据库logDB等,分别用于存储项目数据定义表、商品定义表、用户数据表、日志数据表等。
3.2
何谓水平切分,当一个表中的数据量过大时,我们可以把该表的数据按照某种规则,例如userID散列,进行划分,然后存储到多个结构相同的表,和不同的库
上。例如,我们的userDB中的用户数据表中,每一个表的数据量都很大,就可以把userDB切分为结构相同的多个userDB:part0DB、
part1DB等,再将userDB上的用户数据表userTable,切分为很多userTable:userTable0、userTable1等,
然后将这些表按照一定的规则存储到多个userDB上。
3.3 应该使用哪一种方式来实施数据库分库分表,这要看数据库中数据量的瓶颈所在,并综合项目的业务类型进行考虑。
如果数据库是因为表太多而造成海量数据,并且项目的各项业务逻辑划分清晰、低耦合,那么规则简单明了、容易实施的垂直切分必是首选。
而
如果数据库中的表并不多,但单表的数据量很大、或数据热度很高,这种情况之下就应该选择水平切分,水平切分比垂直切分要复杂一些,它将原本逻辑上属于一体
的数据进行了物理分割,除了在分割时要对分割的粒度做好评估,考虑数据平均和负载平均,后期也将对项目人员及应用程序产生额外的数据管理负担。
在现实项目中,往往是这两种情况兼而有之,这就需要做出权衡,甚至既需要垂直切分,又需要水平切分。我们的游戏项目便综合使用了垂直与水平切分,我们首先对数据库进行垂直切分,然后,再针对一部分表,通常是用户数据表,进行水平切分。
4 分库分表存在的问题。
4.1 事务问题。
在执行分库分表之后,由于数据存储到了不同的库上,数据库事务管理出现了困难。如果依赖数据库本身的分布式事务管理功能去执行事务,将付出高昂的性能代价;如果由应用程序去协助控制,形成程序逻辑上的事务,又会造成编程方面的负担。
4.2 跨库跨表的join问题。
在执行了分库分表之后,难以避免会将原本逻辑关联性很强的数据划分到不同的表、不同的库上,这时,表的关联操作将受到限制,我们无法join位于不同分库的表,也无法join分表粒度不同的表,结果原本一次查询能够完成的业务,可能需要多次查询才能完成。
4.3 额外的数据管理负担和数据运算压力。
额
外的数据管理负担,最显而易见的就是数据的定位问题和数据的增删改查的重复执行问题,这些都可以通过应用程序解决,但必然引起额外的逻辑运算,例如,对于
一个记录用户成绩的用户数据表userTable,业务要求查出成绩最好的100位,在进行分表之前,只需一个order
by语句就可以搞定,但是在进行分表之后,将需要n个order
by语句,分别查出每一个分表的前100名用户数据,然后再对这些数据进行合并计算,才能得出结果。
如何解决postgresql 错误:08p01:无效的信息格式
Appendix A. PostgreSQL错误代码
PostgreSQL服务器发出的所有消息都赋予 了五个字符的错误代码,这些代码遵循 SQL 的"SQLSTATE" 代码的习惯。需要知道发生了什么错误条件的应用通常应该测试错误代码, 而不是查看文本错误信息。这些错误代码轻易不会随着PostgreSQL 的版本更新而修改,并且一般也不会随着错误信息的本地化而发生修改。 请注意有些(但不是全部)PostgreSQL生成的错误代码是 由 SQL 标准定义的;有些标准没有定义的错误条件是发明的或者是从其它数据库借来的。
根据标准,错误代码的头两个字符表示错误类别, 而后三个字符表示在该类别内特定的条件。因此, 那些不能识别特定错误代码的应用仍然可以从错误类别中推断要做什么。
Table A-1里面列出了PostgreSQL 8.2.3 定义的所有错误代码(有些实际上目前并没有使用, 但是 SQL 标准定义了)。错误类别也列出在此。对于每个错误类别都有个 "标准"的错误代码,它的最后三个字符是000。 这个代码只用于那些落在该类别内,但是没有赋予任何更准确的代码的错误条件。
PL/pgSQL用于每个错误代码的条件名和表中显示的措辞相同, 只是用下划线代替了空白。比如,代码22012, DIVISION BY ZERO 的条件名是DIVISION_BY_ZERO。 条件名大小写无关。(请注意PL/pgSQL并不识别警告,这一点和错误、条件名正相反;那些是 00, 01, 02 类别。)
Table A-1. PostgreSQL 错误代码
错误代码
含义
常量名
Class 00 — 成功完成
00000 成功完成 successful_completion
Class 01 — 警告
01000 警告 warning
0100C 返回了动态结果 dynamic_result_sets_returned
01008 警告,隐含补齐了零比特位 implicit_zero_bit_padding
01003 在集合函数里消除null null_value_eliminated_in_set_function
01007 没有赋予权限 privilege_not_granted
01006 没有撤销权限 privilege_not_revoked
01004 字符串数据在右端截断 string_data_right_truncation
01P01 废弃的特性 deprecated_feature
Class 02 — 没有数据(按照 SQL 标准的要求,这也是警告类)
02000 没有数据 no_data
02001 返回了没有附加动态结果集 no_additional_dynamic_result_sets_returned
Class 03 — SQL语句尚未结束
03000 SQL语句尚未结束 sql_statement_not_yet_complete
Class 08 — 连接异常
08000 连接异常 connection_exception
08003 连接不存在 connection_does_not_exist
08006 连接失败 connection_failure
08001 SQL 客户端不能建立 SQL 连接 sqlclient_unable_to_establish_sqlconnection
08004 SQL 服务器拒绝建立 SQL 连接 sqlserver_rejected_establishment_of_sqlconnection
08007 未知的事务解析 transaction_resolution_unknown
08P01 违反协议 protocol_violation
Class 09 — Triggered Action Exception触发器动作异常
09000 触发器动作异常 triggered_action_exception
Class 0A — 不支持特性
0A000 不支持此特性 feature_not_supported
Class 0B — 非法事务初始化
0B000 非法事务初始化 invalid_transaction_initiation
Class 0F — 定位器异常
0F000 定位器异常 locator_exception
0F001 非法的定位器声明 invalid_locator_specification
Class 0L — 非法赋权者
0L000 非法赋权者 invalid_grantor
0LP01 非法赋权操作 invalid_grant_operation
Class 0P — 非法角色声明
0P000 非法角色声明 invalid_role_specification
Class 20 — 未发现情况
20000 未发现情况 case_not_found
Class 21 — 势违例
21000 势违例 cardinality_violation
Class 22 — 数据异常
22000 数据异常 data_exception
2202E 数组下标错误 array_subscript_error
22021 字符不在规定范围内 character_not_in_repertoire
22008 日期时间字段溢出 datetime_field_overflow
22012 被零除 division_by_zero
22005 赋值中出错 error_in_assignment
2200B 逃逸字符冲突 escape_character_conflict
22022 INDICATOR OVERFLOW指示器溢出 indicator_overflow
22015 内部字段溢出 interval_field_overflow
2201E 对数运算的非法参数 invalid_argument_for_logarithm
22014 NTILE函数的无效参数 invalid_argument_for_ntile_function
22016 N倍函数的无效参数 invalid_argument_for_nth_value_function
2201F 指数函数的无效参数 invalid_argument_for_power_function
2201G BUCKET函数的非法参数 invalid_argument_for_width_bucket_function
22018 类型转换时非法的字符值 invalid_character_value_for_cast
22007 非法日期时间格式 invalid_datetime_format
22019 非法的逃逸字符 invalid_escape_character
2200D 非法的逃逸字节 invalid_escape_octet
22025 非法逃逸序列 invalid_escape_sequence
22P06 非标准使用逃逸字符 nonstandard_use_of_escape_character
22010 非法指示器参数值 invalid_indicator_parameter_value
22023 非法参数值 invalid_parameter_value
2201B 非法正则表达式 invalid_regular_expression
2201W 无效的行数限制 invalid_row_count_in_limit_clause
2201X 在结果抵消子句中无效的行数 invalid_row_count_in_result_offset_clause
22009 非法时区显示值 invalid_time_zone_displacement_value
2200C 逃逸字符的非法使用 invalid_use_of_escape_character
2200G 最相关类型不匹配 most_specific_type_mismatch
22004 不允许 NULL 值 null_value_not_allowed
22002 NULL 值不能做指示器参数 null_value_no_indicator_parameter
22003 数字值超出范围 numeric_value_out_of_range
22026 字符串数据长度不匹配 string_data_length_mismatch
22001 字符串数据右边被截断 string_data_right_truncation
22011 抽取子字符串错误 substring_error
22027 截断错误 trim_error
22024 未结束的 C 字符串 unterminated_c_string
2200F 零长度的字符串 zero_length_character_string
22P01 浮点异常 floating_point_exception
22P02 非法文本表现形式 invalid_text_representation
22P03 非法二进制表现形式 invalid_binary_representation
22P04 错误的COPY文件格式 bad_copy_file_format
22P05 不可翻译字符 untranslatable_character
2200L 不是一个XML文档 not_an_xml_document
2200M 无效的XML文档 invalid_xml_document
2200N 无效的XML内容 invalid_xml_content
2200S 无效的XML评论 invalid_xml_comment
2200T 无效的XML处理指令 invalid_xml_processing_instruction
Class 23 — 违反完整性约束
23000 违反完整性约束 integrity_constraint_violation
23001 约束限制 restrict_violation
23502 NOT NULL VIOLATION违反非空 not_null_violation
23503 违反外键约束 foreign_key_violation
23505 违反唯一约束 unique_violation
23514 违反检查 check_violation
23P01 违反排除 exclusion_violation
Class 24 — 非法游标状态
24000 非法游标状态 invalid_cursor_state
Class 25 — 非法事务状态
25000 非法事务状态 invalid_transaction_state
25001 活跃的SQL状态 active_sql_transaction
25002 分支事务已经激活 branch_transaction_already_active
25008 持有的指针要求同样的隔离级别 held_cursor_requires_same_isolation_level
25003 对分支事务的不恰当的访问方式 inappropriate_access_mode_for_branch_transaction
25004 对分支事务的不恰当的隔离级别 inappropriate_isolation_level_for_branch_transaction
25005 分支事务没有活跃的SQL事务 no_active_sql_transaction_for_branch_transaction
25006 只读的SQL事务 read_only_sql_transaction
25007 不支持混和的模式和数据语句 schema_and_data_statement_mixing_not_supported
25P01 没有活跃的SQL事务 no_active_sql_transaction
25P02 在失败的SQL事务中 in_failed_sql_transaction
Class 26 — 非法SQL语句名
26000 非法SQL语句名 invalid_sql_statement_name
Class 27 — 触发数据更改违规
27000 触发数据更改违规 triggered_data_change_violation
Class 28 — 非法授权声明
28000 非法授权声明 invalid_authorization_specification
28P01 非法密码 invalid_password
Class 2B — 依然存在依赖的优先级描述符
2B000 依然存在依赖的优先级描述符 dependent_privilege_descriptors_still_exist
2BP01 依赖性对象仍然存在 dependent_objects_still_exist
Class 2D — 非法的事务终止
2D000 非法的事务终止 invalid_transaction_termination
Class 2F — SQL过程异常
2F000 SQL过程异常 sql_routine_exception
2F005 执行的函数没有返回语句 function_executed_no_return_statement
2F002 不允许修改SQL数据 modifying_sql_data_not_permitted
2F003 企图使用禁止的SQL语句 prohibited_sql_statement_attempted
2F004 不允许读取SQL数据 reading_sql_data_not_permitted
Class 34 — 非法指针名
34000 非法指针名 invalid_cursor_name
Class 38 — 外部过程异常
38000 外部过程异常 external_routine_exception
38001 不允许包含的SQL containing_sql_not_permitted
38002 不允许修改SQL数据 modifying_sql_data_not_permitted
38003 企图使用禁止的SQL语句 prohibited_sql_statement_attempted
38004 不允许读取SQL数据 reading_sql_data_not_permitted
Class 39 — 外部过程调用异常
39000 外部过程调用异常 external_routine_invocation_exception
39001 返回了非法的SQL状态 invalid_sqlstate_returned
39004 不允许使用NULL null_value_not_allowed
39P01 违反触发器协议 trigger_protocol_violated
39P02 违反 SRF 协议 srf_protocol_violated
Class 3B — 保存点异常
3B000 保存点异常 savepoint_exception
3B001 无效的保存点声明 invalid_savepoint_specification
Class 3D — 非法目录名
3D000 非法目录名 invalid_catalog_name
Class 3F — 非法模式名
3F000 非法模式名 invalid_schema_name
Class 40 — 事务回滚
40000 事务回滚 transaction_rollback
40002 违反事务完整性约束 transaction_integrity_constraint_violation
40001 串行化失败 serialization_failure
40003 不知道语句是否结束 statement_completion_unknown
40P01 侦测到死锁 deadlock_detected
Class 42 — 语法错误或者违反访问规则
42000 语法错误或者违反访问规则 syntax_error_or_access_rule_violation
42601 语法错误 syntax_error
42501 权限不够 insufficient_privilege
42846 无法进行类型转换 cannot_coerce
42803 分组错误 grouping_error
42P20 开窗口错误 windowing_error
42P19 非法递归 invalid_recursion
42830 非法的外键 invalid_foreign_key
42602 非法名称 invalid_name
42622 名称过长 name_too_long
42939 保留名称 reserved_name
42804 数据类型不匹配 datatype_mismatch
42P18 模糊数据类型 indeterminate_datatype
42809 错误的对象类型 wrong_object_type
42703 未定义的字段 undefined_column
42883 未定义的函数 undefined_function
42P01 未定义的表 undefined_table
42P02 未定义的参数 undefined_parameter
42704 未定义对象 undefined_object
42701 重复的字段 duplicate_column
42P03 重复的游标 duplicate_cursor
42P04 重复的数据库 duplicate_database
42723 重复的函数 duplicate_function
42P05 重复的预备语句 duplicate_prepared_statement
42P06 重复的模式 duplicate_schema
42P07 重复的表 duplicate_table
42712 重复的别名 duplicate_alias
42710 重复的对象 duplicate_object
42702 模糊的字段 ambiguous_column
42725 模糊的函数 ambiguous_function
42P08 模糊的参数 ambiguous_parameter
42P09 模糊的别名 ambiguous_alias
42P10 非法字段引用 invalid_column_reference
42611 非法字段定义 invalid_column_definition
42P11 非法游标定义 invalid_cursor_definition
42P12 非法数据库定义 invalid_database_definition
42P13 非法函数定义 invalid_function_definition
42P14 非法预备语句定义 invalid_prepared_statement_definition
42P15 非法模式定义 invalid_schema_definition
42P16 非法表定义 invalid_table_definition
42P17 非法对象定义 invalid_object_definition
Class 44 — 违反 WITH CHECK 选项
44000 违反 WITH CHECK 选项 with_check_option_violation
事务的隔离级别 全部都是共享锁吗
前言:我们都知道事务的几种性质,数据库为了维护这些性质,尤其是一致性和隔离性,一般使用加锁这种方式。同时数据库又是个高并发的应用,同一时间会有大量的并发访问,如果加锁过度,会极大的降低并发处理能力。所以对于加锁的处理,可以说就是数据库对于事务处理的精髓所在。这里通过分析MySQL中InnoDB引擎的加锁机制,来抛砖引玉,让读者更好的理解,在事务处理中数据库到底做了什么。一次封锁or两段锁?因为有大量的并发访问,为了预防死锁,一般应用中推荐使用一次封锁法,就是在方法的开始阶段,已经预先知道会用到哪些数据,然后全部锁住,在方法运行之后,再全部解锁。这种方式可以有效的避免循环死锁,但在数据库中却不适用,因为在事务开始阶段,数据库并不知道会用到哪些数据。数据库遵循的是两段锁协议,将事务分成两个阶段,加锁阶段和解锁阶段(所以叫两段锁)加锁阶段:在该阶段可以进行加锁操作。在对任何数据进行读操作之前要申请并获得S锁(共享锁,其它事务可以继续加共享锁,但不能加排它锁),在进行写操作之前要申请并获得X锁(排它锁,其它事务不能再获得任何锁)。加锁不成功,则事务进入等待状态,直到加锁成功才继续执行。解锁阶段:当事务释放了一个封锁以后,事务进入解锁阶段,在该阶段只能进行解锁操作不能再进行加锁操作。事务 加锁/解锁处理begin; insert into test ..... 加insert对应的锁update test set... 加update对应的锁delete from test .... 加delete对应的锁commit; 事务提交时,同时释放insert、update、delete对应的锁这种方式虽然无法避免死锁,但是两段锁协议可以保证事务的并发调度是串行化(串行化很重要,尤其是在数据恢复和备份的时候)的。事务中的加锁方式事务的四种隔离级别在数据库操作中,为了有效保证并发读取数据的正确性,提出的事务隔离级别。我们的数据库锁,也是为了构建这些隔离级别存在的。隔离级别 脏读(Dirty Read) 不可重复读(NonRepeatable Read) 幻读(Phantom Read)未提交读(Read uncommitted) 可能 可能 可能 已提交读(Read committed) 不可能 可能 可能 可重复读(Repeatable read) 不可能 不可能 可能 可串行化(Serializable ) 不可能 不可能 不可能 未提交读(Read Uncommitted):允许脏读,也就是可能读取到其他会话中未提交事务修改的数据提交读(Read Committed):只能读取到已经提交的数据。Oracle等多数数据库默认都是该级别 (不重复读)可重复读(Repeated Read):可重复读。在同一个事务内的查询都是事务开始时刻一致的,InnoDB默认级别。在SQL标准中,该隔离级别消除了不可重复读,但是还存在幻象读串行读(Serializable):完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻塞Read Uncommitted这种级别,数据库一般都不会用,而且任何操作都不会加锁,这里就不讨论了。MySQL中锁的种类MySQL中锁的种类很多,有常见的表锁和行锁,也有新加入的Metadata Lock等等,表锁是对一整张表加锁,虽然可分为读锁和写锁,但毕竟是锁住整张表,会导致并发能力下降,一般是做ddl处理时使用。行锁则是锁住数据行,这种加锁方法比较复杂,但是由于只锁住有限的数据,对于其它数据不加限制,所以并发能力强,MySQL一般都是用行锁来处理并发事务。这里主要讨论的也就是行锁。Read Committed(读取提交内容)在RC级别中,数据的读取都是不加锁的,但是数据的写入、修改和删除是需要加锁的。效果如下MySQL show create table class_teacher \G\ Table: class_teacher Create Table: CREATE TABLE `class_teacher` ( `id` int(11) NOT NULL AUTO_INCREMENT, `class_name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL, `teacher_id` int(11) NOT NULL, PRIMARY KEY (`id`), KEY `idx_teacher_id` (`teacher_id`) ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci 1 row in set (0.02 sec) MySQL select * from class_teacher; +----+--------------+------------+ id class_name teacher_id +----+--------------+------------+ 1 初三一班 1 3 初二一班 2 4 初二二班 2 +----+--------------+------------+ 由于MySQL的InnoDB默认是使用的RR级别,所以我们先要将该session开启成RC级别,并且设置binlog的模式SET session transaction isolation level read committed; SET SESSION binlog_format = 'ROW'; (或者是MIXED)事务A 事务Bbegin; begin;update class_teacher set class_name='初三二班' where teacher_id=1; update class_teacher set class_name='初三三班' where teacher_id=1; ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction commit; 为了防止并发过程中的修改冲突,事务A中MySQL给teacher_id=1的数据行加锁,并一直不commit(释放锁),那么事务B也就一直拿不到该行锁,wait直到超时。这时我们要注意到,teacher_id是有索引的,如果是没有索引的class_name呢?update class_teacher set teacher_id=3 where class_name = '初三一班';那么MySQL会给整张表的所有数据行的加行锁。这里听起来有点不可思议,但是当sql运行的过程中,MySQL并不知道哪些数据行是 class_name = '初三一班'的(没有索引嘛),如果一个条件无法通过索引快速过滤,存储引擎层面就会将所有记录加锁后返回,再由MySQL Server层进行过滤。但在实际使用过程当中,MySQL做了一些改进,在MySQL Server过滤条件,发现不满足后,会调用unlock_row方法,把不满足条件的记录释放锁 (违背了二段锁协议的约束)。这样做,保证了最后只会持有满足条件记录上的锁,但是每条记录的加锁操作还是不能省略的。可见即使是MySQL,为了效率也是会违反规范的。(参见《高性能MySQL》中文第三版p181)这种情况同样适用于MySQL的默认隔离级别RR。所以对一个数据量很大的表做批量修改的时候,如果无法使用相应的索引,MySQL Server过滤数据的的时候特别慢,就会出现虽然没有修改某些行的数据,但是它们还是被锁住了的现象。Repeatable Read(可重读)这是MySQL中InnoDB默认的隔离级别。我们姑且分“读”和“写”两个模块来讲解。读读就是可重读,可重读这个概念是一事务的多个实例在并发读取数据时,会看到同样的数据行,有点抽象,我们来看一下效果。RC(不可重读)模式下的展现事务A 事务Bbegin; begin; select id,class_name,teacher_id from class_teacher where teacher_id=1; id class_name teacher_id 1 初三二班 1 2 初三一班 1 update class_teacher set class_name='初三三班' where id=1; commit; select id,class_name,teacher_id from class_teacher where teacher_id=1; id class_name teacher_id 1 初三三班 1 2 初三一班 1 读到了事务B修改的数据,和第一次查询的结果不一样,是不可重读的。commit; 事务B修改id=1的数据提交之后,事务A同样的查询,后一次和前一次的结果不一样,这就是不可重读(重新读取产生的结果不一样)。这就很可能带来一些问题,那么我们来看看在RR级别中MySQL的表现:事务A 事务B 事务Cbegin; begin; begin; select id,class_name,teacher_id from class_teacher where teacher_id=1; id class_name teacher_id 1 初三二班 1 2 初三一班 1 update class_teacher set class_name='初三三班' where id=1; commit; insert into class_teacher values (null,'初三三班',1); commit; select id,class_name,teacher_id from class_teacher where teacher_id=1; id class_name teacher_id 1 初三二班 1 2 初三一班 1 没有读到事务B修改的数据,和第一次sql读取的一样,是可重复读的。没有读到事务C新添加的数据。commit; 我们注意到,当teacher_id=1时,事务A先做了一次读取,事务B中间修改了id=1的数据,并commit之后,事务A第二次读到的数据和第一次完全相同。所以说它是可重读的。那么MySQL是怎么做到的呢?这里姑且卖个关子,我们往下看。不可重复读和幻读的区别很多人容易搞混不可重复读和幻读,确实这两者有些相似。但不可重复读重点在于update和delete,而幻读的重点在于insert。如果使用锁机制来实现这两种隔离级别,在可重复读中,该sql第一次读取到数据后,就将这些数据加锁,其它事务无法修改这些数据,就可以实现可重复读了。但这种方法却无法锁住insert的数据,所以当事务A先前读取了数据,或者修改了全部数据,事务B还是可以insert数据提交,这时事务A就会发现莫名其妙多了一条之前没有的数据,这就是幻读,不能通过行锁来避免。需要Serializable隔离级别 ,读用读锁,写用写锁,读锁和写锁互斥,这么做可以有效的避免幻读、不可重复读、脏读等问题,但会极大的降低数据库的并发能力。所以说不可重复读和幻读最大的区别,就在于如何通过锁机制来解决他们产生的问题。上文说的,是使用悲观锁机制来处理这两种问题,但是MySQL、ORACLE、PostgreSQL等成熟的数据库,出于性能考虑,都是使用了以乐观锁为理论基础的MVCC(多版本并发控制)来避免这两种问题。悲观锁和乐观锁悲观锁正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。在悲观锁的情况下,为了保证事务的隔离性,就需要一致性锁定读。读取数据时给加锁,其它事务无法修改这些数据。修改删除数据时也要加锁,其它事务无法读取这些数据。乐观锁相对悲观锁而言,乐观锁机制采取了更加宽松的加锁机制。悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。而乐观锁机制在一定程度上解决了这个问题。乐观锁,大多是基于数据版本( Version )记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个 “version” 字段来实现。读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。要说明的是,MVCC的实现没有固定的规范,每个数据库都会有不同的实现方式,这里讨论的是InnoDB的MVCC。MVCC在MySQL的InnoDB中的实现在InnoDB中,会在每行数据后添加两个额外的隐藏的值来实现MVCC,这两个值一个记录这行数据何时被创建,另外一个记录这行数据何时过期(或者被删除)。 在实际操作中,存储的并不是时间,而是事务的版本号,每开启一个新事务,事务的版本号就会递增。 在可重读Repeatable reads事务隔离级别下:SELECT时,读取创建版本号=当前事务版本号,删除版本号为空或当前事务版本号。 INSERT时,保存当前事务版本号为行的创建版本号 DELETE时,保存当前事务版本号为行的删除版本号 UPDATE时,插入一条新纪录,保存当前事务版本号为行创建版本号,同时保存当前事务版本号到原来删除的行 通过MVCC,虽然每行记录都需要额外的存储空间,更多的行检查工作以及一些额外的维护工作,但可以减少锁的使用,大多数读操作都不用加锁,读数据操作很简单,性能很好,并且也能保证只会读取到符合标准的行,也只锁住必要行。 我们不管从数据库方面的教课书中学到,还是从网络上看到,大都是上文中事务的四种隔离级别这一模块列出的意思,RR级别是可重复读的,但无法解决幻读,而只有在Serializable级别才能解决幻读。于是我就加了一个事务C来展示效果。在事务C中添加了一条teacher_id=1的数据commit,RR级别中应该会有幻读现象,事务A在查询teacher_id=1的数据时会读到事务C新加的数据。但是测试后发现,在MySQL中是不存在这种情况的,在事务C提交后,事务A还是不会读到这条数据。可见在MySQL的RR级别中,是解决了幻读的读问题的。参见下图读问题解决了,根据MVCC的定义,并发提交数据时会出现冲突,那么冲突时如何解决呢?我们再来看看InnoDB中RR级别对于写数据的处理。“读”与“读”的区别可能有读者会疑惑,事务的隔离级别其实都是对于读数据的定义,但到了这里,就被拆成了读和写两个模块来讲解。这主要是因为MySQL中的读,和事务隔离级别中的读,是不一样的。我们且看,在RR级别中,通过MVCC机制,虽然让数据变得可重复读,但我们读到的数据可能是历史数据,是不及时的数据,不是数据库当前的数据!这在一些对于数据的时效特别敏感的业务中,就很可能出问题。对于这种读取历史数据的方式,我们叫它快照读 (snapshot read),而读取数据库当前版本数据的方式,叫当前读 (current read)。很显然,在MVCC中:快照读:就是selectselect * from table ....; 当前读:特殊的读操作,插入/更新/删除操作,属于当前读,处理的都是当前的数据,需要加锁。select * from table where ? lock in share mode; select * from table where ? for update; insert; update ; delete; 事务的隔离级别实际上都是定义了当前读的级别,MySQL为了减少锁处理(包括等待其它锁)的时间,提升并发能力,引入了快照读的概念,使得select不用加锁。而update、insert这些“当前读”,就需要另外的模块来解决了。写("当前读")事务的隔离级别中虽然只定义了读数据的要求,实际上这也可以说是写数据的要求。上文的“读”,实际是讲的快照读;而这里说的“写”就是当前读了。为了解决当前读中的幻读问题,MySQL事务使用了Next-Key锁。Next-Key锁Next-Key锁是行锁和GAP(间隙锁)的合并,行锁上文已经介绍了,接下来说下GAP间隙锁。行锁可以防止不同事务版本的数据修改提交时造成数据冲突的情况。但如何避免别的事务插入数据就成了问题。我们可以看看RR级别和RC级别的对比RC级别:事务A 事务Bbegin; begin; select id,class_name,teacher_id from class_teacher where teacher_id=30; id class_name teacher_id 2 初三二班 30 update class_teacher set class_name='初三四班' where teacher_id=30; insert into class_teacher values (null,'初三二班',30); commit; select id,class_name,teacher_id from class_teacher where teacher_id=30; id class_name teacher_id 2 初三四班 30 10 初三二班 30 RR级别: 事务A 事务B begin; begin; select id,class_name,teacher_id from class_teacher where teacher_id=30; id class_name teacher_id 2 初三二班 30 update class_teacher set class_name='初三四班' where teacher_id=30; insert into class_teacher values (null,'初三二班',30); waiting.... select id,class_name,teacher_id from class_teacher where teacher_id=30; id class_name teacher_id 2 初三四班 30 commit; 事务Acommit后,事务B的insert执行。通过对比我们可以发现,在RC级别中,事务A修改了所有teacher_id=30的数据,但是当事务Binsert进新数据后,事务A发现莫名其妙多了一行teacher_id=30的数据,而且没有被之前的update语句所修改,这就是“当前读”的幻读。RR级别中,事务A在update后加锁,事务B无法插入新数据,这样事务A在update前后读的数据保持一致,避免了幻读。这个锁,就是Gap锁。MySQL是这么实现的:在class_teacher这张表中,teacher_id是个索引,那么它就会维护一套B+树的数据关系,为了简化,我们用链表结构来表达(实际上是个树形结构,但原理相同)如图所示,InnoDB使用的是聚集索引,teacher_id身为二级索引,就要维护一个索引字段和主键id的树状结构(这里用链表形式表现),并保持顺序排列。Innodb将这段数据分成几个个区间(negative infinity, 5], (5,30], (30,positive infinity); update class_teacher set class_name='初三四班' where teacher_id=30; 不仅用行锁,锁住了相应的数据行;同时也在两边的区间,(5,30]和(30,positive infinity),都加入了gap锁。这样事务B就无法在这个两个区间insert进新数据。受限于这种实现方式,Innodb很多时候会锁住不需要锁的区间。如下所示:事务A 事务B 事务Cbegin; begin; begin; select id,class_name,teacher_id from class_teacher; id class_name teacher_id 1 初三一班 5 2 初三二班 30 update class_teacher set class_name='初一一班' where teacher_id=20; insert into class_teacher values (null,'初三五班',10); waiting ..... insert into class_teacher values (null,'初三五班',40); commit; 事务A commit之后,这条语句才插入成功 commit; commit; update的teacher_id=20是在(5,30]区间,即使没有修改任何数据,Innodb也会在这个区间加gap锁,而其它区间不会影响,事务C正常插入。如果使用的是没有索引的字段,比如update class_teacher set teacher_id=7 where class_name='初三八班(即使没有匹配到任何数据)',那么会给全表加入gap锁。同时,它不能像上文中行锁一样经过MySQL Server过滤自动解除不满足条件的锁,因为没有索引,则这些字段也就没有排序,也就没有区间。除非该事务提交,否则其它事务无法插入任何数据。行锁防止别的事务修改或删除,GAP锁防止别的事务新增,行锁和GAP锁结合形成的的Next-Key锁共同解决了RR级别在写数据时的幻读问题。Serializable这个级别很简单,读加共享锁,写加排他锁,读写互斥。使用的悲观锁的理论,实现简单,数据更加安全,但是并发能力非常差。如果你的业务并发的特别少或者没有并发,同时又要求数据及时可靠的话,可以使用这种模式。这里要吐槽一句,不要看到select就说不会加锁了,在Serializable这个级别,还是会加锁的!
分享名称:包含postgresql死锁的词条
分享地址:http://scyanting.com/article/dsdpjip.html