1 简介
Hive 作为 Hadoop 家族历史最悠久的组件之一,一直以其优秀的兼容性支持和稳定性而著称,越来越多的企业将业务数据从传统数据库迁移至 Hadoop 平台,并通过 Hive 来进行数据分析。但是我们在迁移的过程中难免会碰到如何将传统数据库的功能也迁移到 Hadoop 的问题,比如说事务。事务作为传统数据库很重要的一个功能,在 Hive 中是如何实现的呢?Hive 的实现有什么不一样的地方呢?我们将传统数据库的应用迁移到 Hive 如果有事务相关的场景我们该如何去转换并要注意什么问题呢?
本文会通过很多真实测试案例来比较 Hive 与传统数据库事务的区别,并在文末给出一些在 Hive 平台上使用事务相关的功能时的指导和建议。
2 ACID 与实现原理
为了方便解释和说明后面的一些问题,这里重提传统数据库事务相关的概念,以下内容来源于网络。
2.1 ACID 说明
何为事务?就是一组单元化操作,这些操作要么都执行,要么都不执行,是一个不可分割的工作单位。
事务(transaction)所应该具有的四个要素:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)。这四个基本要素通常称为 ACID 特性。
- 原子性(Atomicity)
- 一个事务是一个不可再分割的工作单位,事务中的所有操作要么都发生,要么都不发生。
- 一致性(Consistency)
- 事务开始之前和事务结束以后,数据库的完整性约束没有被破坏。这是说数据库事务不能破坏关系数据的完整性以及业务逻辑上的一致性。
- 隔离性(Isolation)
- 多个事务并发访问,事务之间是隔离的,一个事务不影响其它事务运行效果。这指的是在并发环境中,当不同的事务同时操作相同的数据时,每个事务都有各自完整的数据空间。事务查看数据更新时,数据所处的状态要么是另一事务修改它之前的状态,要么是另一事务修改后的状态,事务不会查看到中间状态的数据。
- 事务之间的相应影响,分别为:脏读、不可重复读、幻读、丢失更新。
- 持久性(Durability)
- 意味着在事务完成以后,该事务锁对数据库所作的更改便持久的保存在数据库之中,并不会被回滚。
2.2 ACID 的实现原理
事务可以保证 ACID 原则的操作,那么事务是如何保证这些原则的?解决 ACID 问题的两大技术点是:
- 预写日志(Write-ahead logging)保证原子性和持久性
- 锁(locking)保证隔离性
这里并没有提到一致性,是因为一致性是应用相关的话题,它的定义一个由业务系统来定义,什么样的状态才是一致?而实现一致性的代码通常在业务逻辑的代码中得以体现。
注:锁是指在并发环境中通过读写锁来保证操作的互斥性。根据隔离程度不同,锁的运用也不同。
3 测试环境
操作系统
CentOS 6.5
JDK
jdk1.7.0_67
CDH
5.9
Hadoop
2.6.0
Hive
1.1.0
4 Hive 的 ACID 测试
4.1 Hive 中的锁(不开启事务)
Hive 中定义了两种锁的模式:共享锁(S)和排它锁(X),顾名思义,多个共享锁 (S) 可以同时获取,但是排它锁 (X) 会阻塞其它所有锁。在本次测试中,CDH5.9 的 Concurrency 参数是默认开启的(hive.support.concurrency=true),以下分别对开启 Concurrency 和关闭进行相关测试。
首先在测试之前,创建一个普通的 hive 表:
create table test_notransaction(user_id Int,name String);
向 test_transaction 表中插入测试数据:
insert into test_notransaction values(1,'peach1'),(2,'peach2'),(3, 'peach3'),(4, 'peach4');
查看插入的数据:
(点击放大图像)
4.1.1 开启 Concurrency
1. 对 catalog_sales 表进行并发 select 操作
执行的 sql 语句: select count(*) from catalog_sales;
执行单条 sql 查询时,获取一个共享锁(S),sql 语句正常执行
(点击放大图像)
同时执行两条sql 查询是,获取两个共享锁,并且sql 语句均正常执行
(点击放大图像)
分析:由此对比可得出hive 在执行sql 查询时获取Share 锁,在并发的情况下可获取多个共享锁。
2. 对 test 表进行并发 Insert 操作
创建表:
create table test(name string, id int);
执行 sql 语句:
insert into test values('test11aaa1',1252); insert into test values('test1',52);
执行单条 insert 语句时,获取一个 X 锁,sql 语句正常执行
(点击放大图像)
同时执行两条insert 语句时,只能获取一个test 表X 锁,第一条insert 语句正常执行,第二条insert 语句处于等待状态,在第一条insert 语句释放test 表的X 锁,第二条sql 语句正常执行.
分析:由此对比可得出hive 在执行insert 操作时,只能获取一个X 锁且锁不能共享,只能在sql 执行完成释放锁后,后续sql 方可继续执行。
3. 对 test 表执行 select 的同时执行 insert 操作
执行 sql 语句:
select count(*) from test; insert into test values("test123",123);
步骤:
1) 执行 select 语句,在 select 未运行完时,在新的窗口同时执行 insert 语句观察两条 sql 执行情况,select 语句正常执行,insert 语句处于等待状态。
2) 此时查看 test 表锁状态
(点击放大图像)
在步骤1 的执行过程中,获取到test 表的锁为共享锁(S)
3) 在 select 语句执行完成后,观察 insert 语句开始正常执行,此时获取 test 表锁为排它锁(X)。注意:在 select 语句执行完成后,大概过 40s 左右 insert 语句才正常执行,这是由 hive.lock.sleep.between.retries 参数控制,默认 60
(点击放大图像)
分析: 由上述操作可得出,hive 中一个表只能有一个排它锁(X) 且锁不能共享,在获取排它锁时,表上不能有其它锁包括共享锁(S),只有在表上所有的锁都释放后,insert 操作才能继续,否则处于等待状态。
对注意部分进行参数调整,将hive.lock.sleep.between.retries 设置为10s,再次进行测试发现,在select 语句执行完成后,大概过6s 左右insert 语句开始执行, 通过两次测试发现,等待时间均在10s 以内,由此可以得出此参数影响sql 操作获取锁的间隔(在未获取到锁的情况下),如果此时未到获取锁触发周期,执行其它sql 则,该sql 会优于等待的sql 执行。
4. 对 test 表执行 insert 的同时执行 select 操作
执行 sql 语句:
insert into test values("test123",123); <a name="OLE_LINK5"></a>select count(*) from test;
操作步骤:
1) 在命令窗口执行 insert 语句,在 insert 操作未执行完成时,在新的命令窗口执行 select 语句,观察两个窗口的 sql 执行情况,insert 语句正常执行,select 语句处于等待状态。
2) 此时查看 test 表锁状态,只有 insert 操作获取的排它锁(X)
(点击放大图像)
3) 在 insert 语句执行完成后,观察 select 语句开始正常执行,此时查看 test 表锁状态为共享锁(S),之前的 insert 操作获取的排它锁(X)已被释放
(点击放大图像)
分析:在test 表锁状态为排它锁(X) 时,所有的操作均被阻塞处于等待状态,只有在排它锁(X) 释放其它操作可继续进行。
5. 测试 update 和 delete 修改 test 表数据
sql 语句:
update test set name='aaaa' where id=1252; delete test set name='bbbb' where id=123;
1) 表中数据,更新前
(点击放大图像)
2) 在 beeline 窗口执行 update 操作
(点击放大图像)
执行update 操作报错,异常提示“Attempt to do update or delete using transaction manager that does not support these operations”,在非事务模式下不支持update 和 delete。
4.1.2 关闭 Concurrency
1. 执行 insert 操作的同时执行 select 操作
sql 语句:
insert into test_notransaction values(1,'peach1'),(2,'peach2'),(3, 'peach3'),(4, 'peach4'); select count(*) from test_notransaction;
操作 sql 前,查看表数据
(点击放大图像)
查看test_notransaction 表获取情况,show locks;
(点击放大图像)
hive 在未开启 concurrency 的情况下,show locks 不能正常获取表的锁,同时对同一张表执行 insert 和 select 操作时并发执行,获取数据取决于 sql 执行速度,因此在 select 的时候未获取到插入数据。
2. 执行 select 操作的同时执行 insert 操作
sql 语句:
select count(*) from test_notransaction; insert into test_notransaction values(1,'peach1'),(2,'peach2'),(3, 'peach3'),(4, 'peach4');
在执行 select 的同时执行 insert 操作,操作可以同时并行操作,未产生阻塞等待的过程。
3. 同时执行多条 insert 操作
sql 语句:
insert into test_notransaction values(1,'peach1'),(2,'peach2'),(3, 'peach3'),(4, 'peach4'); insert into test_notransaction values(1,'peach1'),(2,'peach2'),(3, 'peach3'),(4, 'peach4');
同时执行 insert 操作时,可同时执行未产生阻塞等待的过程。
4. 执行 update 操作,将表中 user_id 为 2 的用户名修改为 peach22
sql 语句:
update test_notransaction set name='peach22' where user_id=2;
执行 update 操作,执行结果如下:
(点击放大图像)
在未配置hive 的Transaction 和ACID 时,不支持update 操作。
5. 执行 delete 操作,将表中 user_id 为 1 信息删除
sql 语句:
delete from test_notransaction where user_id=1;
执行 delete 操作,执行结果如下:
(点击放大图像)
hive 未配置 Transaction 和 ACID,不支持 delete 操作。
6. 查看表获取锁类型
show locks;
无法正常执行;
4.2 Hive 的事务
4.2.1 Hive 的事务配置
Hive 从 0.13 开始加入了事务支持,在行级别提供完整的 ACID 特性,Hive 在 0.14 时加入了对 INSERT…VALUES,UPDATE,and DELETE 的支持。对于在 Hive 中使用 ACID 和 Transactions,主要有以下限制:
- 不支持 BEGIN,COMMIT 和 ROLLBACK
- 只支持 ORC 文件格式
- 表必须分桶
- 不允许从一个非 ACID 连接写入 / 读取 ACID 表
为了使 Hive 支持事务操作,需将以下参数加入到 hive-site.xml 文件中。
<property> <name>hive.support.concurrency</name> <value>true</value> </property> <property> <name>hive.enforce.bucketing</name> <value>true</value> </property> <property> <name>hive.exec.dynamic.partition.mode</name> <value>nonstrict</value> </property> <property> <name>hive.txn.manager</name> <value>org.apache.hadoop.hive.ql.lockmgr.DbTxnManager</value> </property> <property> <name>hive.compactor.initiator.on</name> <value>true</value> </property> <property> <name>hive.compactor.worker.threads </name> <value>1</value> </property>
可以在 Cloudera Manager 进行以下配置:
(点击放大图像)
为了让beeline 支持还需要配置:
(点击放大图像)
4.2.2 Hive 事务测试
环境准备
1、创建一个支持 ACID 的表
建表语句:
create table test_trancaction (user_id Int,name String) clustered by (user_id) into 3 buckets stored as orc TBLPROPERTIES ('transactional'='true');
将表名修改为 test_transaction
alter table test_trancaction rename to test_transaction;
2、准备测试数据,向数据库中插入数据
insert into test_transaction values(1,'peach'),(2,'peach2'),(3,'peach3'),(4,'peach4'),(5,'peach5');
用例测试
1. 执行 update 操作,将 user_id 的 name 修改为 peach_update
sql 语句:
update test_transaction set name='peach_update' where user_id=1;
执行修改操作,查看表获取锁类型
(点击放大图像)
数据修改成功,且不影响其它数据。
(点击放大图像)
2. 同时修改同一条数据,将 user_id 为 1 的用户名字修改为 peach,另一条 sql 将名字修改为 peach_
sql 语句:
update test_transaction set name='peach' where user_id=1; update test_transaction set name='peach_' where user_id=1;
sql 执行顺序为 peach,其次为 peach_
此时查看表获取到的锁
(点击放大图像)
通过获取到锁分析,在同时修改同一条数据时,优先执行的sql 获取到了SHARED_WRITE,而后执行的sql 获取锁的状态为WAITING 状态,表示还未获取到SHARED_WRITE 锁,等待第一条sql 执行结束后方可获取到锁对数据进行操作。
通过上不执行操作分析,数据user_id 为1 的用户名字应被修改为peach_
(点击放大图像)
3. 同时修改不同数据,修改 id 为 2 的 name 为 peachtest,修改 id 为 3 的 name 为 peach_test
sql 语句:
update test_transaction set name='peachtest' where user_id=2; update test_transaction set name='peach_test' where user_id=3;
sql 执行顺序为 peachtest,其次为 peach_test
此时查看表获取到的锁
(点击放大图像)
通过sql 操作获取锁分析,在同时修改不同数据时,优先执行的sql 获取到了SHARED_WRITE,而后执行的sql 获取锁的状态为WAITING 状态,表示还未获取到SHARED_WRITE 锁,等待第一条sql 执行结束后方可获取到锁对数据进行操作。
4. 执行 select 操作的同时执行 insert 操作
sql 语句:
select count(*) from test_transaction; insert into test_transaction values(3,'peach3');
步骤:
先执行 select 操作,再执行 insert 操作,执行完成后查看表获取到的锁
(点击放大图像)
由于select 和insert 操作均获取的是SHARED_READ 锁,读锁为并行,所以select 查询和insert 同时执行,互不影响。
5.update 同一条数据的同时 select 该条数据
sql 语句:
update test_transaction set name='peach_update' where user_id=1; select * from test_transaction where user_id=1;
步骤:
先执行 update 操作,再执行 select 操作,获取此时表获取到的锁
(点击放大图像)
通过获取锁的情况分析, 在update 操作时,获取到SHARED_WRITE 锁,执行select 操作时获取到SHARED_READ 锁,在进行修改数据时未阻塞select 查询操作,update 未执行完成时,select 查询到的数据为未修改的数据。
6. 执行 delete 操作,将 user_id 为 3 的数据删除
sql 语句:
delete from test_transaction where user_id=3;
步骤:
执行 delete 操作,获取此时表获取到的锁
(点击放大图像)
删除操作获取到的是SHARED_WRITE 锁
执行成功后数据
(点击放大图像)
7. 同时 delete 同一条数据
sql 语句:
delete from test_transaction where user_id=3; delete from test_transaction where user_id=3;
步骤:
按顺序执行两条 delete 操作,查看此时表获取到的锁:
(点击放大图像)
通过查看delete 操作获取到的锁,优先执行的操作获取到SHARED_WRITE 锁,后执行的delete 操作未获取到SHARED_WRITE 锁,处于WAITING 状态。
执行删除后结果
(点击放大图像)
8. 同时 delete 两条不同的数据
sql 语句:
delete from test_transaction where user_id=1; delete from test_transaction where user_id=5;
步骤:
按顺序执行两条 delete 操作,查看此时表获取到的锁:
(点击放大图像)
通过查看delete 操作获取到的锁,优先执行的操作获取到SHARED_WRITE 锁,后执行的delete 操作未获取到SHARED_WRITE 锁,处于WAITING 状态。
执行删除后结果
(点击放大图像)
9. 执行 delete 的同时对删除的数据进行 update 操作
sql 语句:
delete from test_transaction where user_id=3; update test_transaction set name='test' where user_id=3;
步骤:
按顺序执行两条 sql,查看此时获取到表的锁:
(点击放大图像)
通过查看delete 和update 操作获取到的锁,优先执行的操作获取到SHARED_WRITE 锁,后执行的操作未获取到SHARED_WRITE 锁,处于WAITING 状态。
执行delete 和update 后结果
(点击放大图像)
注意:此处在delete 优先于update 执行,但执行结果为update 的结果,执行异常。
10. 执行 delete 的同时对不同的数据进行 update 操作
sql 语句:
delete from test_transaction where user_id=2; update test_transaction set name='test' where user_id=4;
步骤:
按顺序执行上面两条 sql,查看表锁获取情况
(点击放大图像)
通过查看delete 和update 操作获取到的锁,优先执行的操作获取到SHARED_WRITE 锁,后执行的操作未获取到SHARED_WRITE 锁,处于WAITING 状态。
执行delete 和update 后结果, 执行结果正常
(点击放大图像)
11. 执行 delete 的同时执行 select 操作
sql 语句:
delete from test_transaction where user_id=4; select count(*) from test_transaction;
步骤:
按顺序执行上面两条 sql,查看表锁获取情况
(点击放大图像)
在操作delete 的同时执行select 操作,两个操作均同时获取到SHARED_RED 和SHARED_WRITE 锁,操作并行进行未出现阻塞。
5 总结对比
并行执行 =C 串行执行 =S 不支持 =N
操作
Hive
MySQL
关闭 Concurrency
开启 Concurrency
开启 Transaction
并发执行 select 操作
C
C
C
C
并发执行 insert 操作
C
S
S
S
执行 insert 操作的同时执行 select 操作
C
S
C
C
执行 select 操作的同时执行 insert 操作
C
S
C
C
执行 delete 操作
N
N
S
C
执行 update 操作
N
N
S
C
同时 delete 同一条数据
N
N
S
C
同时 delete 两条不同的数据
N
N
S
C
同时 update 同一条数据
N
N
S
C
同时 update 不同的数据
N
N
S
C
update 同一条数据的同时 select 该条数据
N
N
C
C
执行 delete 的同时对删除数据进行 update 操作
N
N
S
C
执行 delete 的同时对不同的数据进行 update 操作
N
N
S
C
执行 delete 的同时执行 select 操作
N
N
C
C
6 Hive 事务使用建议
- 传统数据库中有三种模型隐式事务、显示事务和自动事务。在目前 Hive 对事务仅支持自动事务,因此 Hive 无法通过显示事务的方式对一个操作序列进行事务控制。
- 传统数据库事务在遇到异常情况可自动进行回滚,目前 Hive 无法支持 ROLLBACK。
- 传统数据库中支持事务并发,而 Hive 对事务无法做到完全并发控制, 多个操作均需要获取 WRITE 的时候则这些操作为串行模式执行(在测试用例中"delete 同一条数据的同时 update 该数据",操作是串行的且操作完成后数据未被删除且数据被修改)未保证数据一致性。
- Hive 的事务功能尚属于实验室功能,并不建议用户直接上生产系统,因为目前它还有诸多的限制,如只支持 ORC 文件格式,建表必须分桶等,使用起来没有那么方便,另外该功能的稳定性还有待进一步验证。
- CDH 默认开启了 Hive 的 Concurrency 功能,主要是对并发读写的的时候通过锁进行了控制。所以为了防止用户在使用 Hive 的时候,报错提示该表已经被 lock,对于用户来说不友好,建议在业务侧控制一下写入和读取,比如写入同一个 table 或者 partition 的时候保证是单任务写入,其他写入需控制写完第一个任务了,后面才继续写,并且控制在写的时候不让用户进行查询。另外需要控制在查询的时候不要允许有写入操作。
- 如果对于数据一致性不在乎,可以完全关闭 Hive 的 Concurrency 功能关闭,即设置 hive.support.concurrency 为 false,这样 Hive 的并发读写将没有任何限制。
7 附录
参考文档:
- https://cwiki.apache.org/confluence/display/Hive/Locking
- https://cwiki.apache.org/confluence/display/Hive/LanguageManual+DDL#LanguageManualDDL- ShowLocks
- https://cwiki.apache.org/confluence/display/Hive/Configuration+Properties#ConfigurationProperties- Locking
- 谈谈数据库的 ACID
- Hive Transaction 事务性小试
- MySQL 详解--锁
测试代码获取地址:
评论