diff --git a/include/mhash.h b/include/mhash.h
index 722a9dffc9e973c038a056336a9524595200e07d..e6bfc1943896cc35fffa1589125b72cda194803f 100644
--- a/include/mhash.h
+++ b/include/mhash.h
@@ -170,6 +170,32 @@ _mh(next_slot)(mh_int_t slot, mh_int_t inc, mh_int_t size)
 	return slot >= size ? slot - size : slot;
 }
 
+#if defined(mh_hash_key) && defined(mh_eq_key)
+/**
+ * If it is necessary to search by something different
+ * than a hash node, define mh_hash_key and mh_eq_key
+ * and use mh_find().
+ */
+static inline mh_int_t
+_mh(find)(struct _mh(t) *h, mh_key_t key, mh_arg_t arg)
+{
+	(void) arg;
+
+	mh_int_t k = mh_hash_key(key, arg);
+	mh_int_t i = k % h->n_buckets;
+	mh_int_t inc = 1 + k % (h->n_buckets - 1);
+	for (;;) {
+		if ((mh_exist(h, i) && mh_eq_key(key, mh_node(h, i), arg)))
+			return i;
+
+		if (!mh_dirty(h, i))
+			return h->n_buckets;
+
+		i = _mh(next_slot)(i, inc, h->n_buckets);
+	}
+}
+#endif
+
 static inline mh_int_t
 _mh(get)(struct _mh(t) *h, const mh_node_t *node,
 	 mh_arg_t arg)
@@ -504,9 +530,12 @@ _mh(dump)(struct _mh(t) *h)
 #undef mh_int_t
 #undef mh_node_t
 #undef mh_arg_t
+#undef mh_key_t
 #undef mh_name
 #undef mh_hash
+#undef mh_hash_key
 #undef mh_eq
+#undef mh_eq_key
 #undef mh_node
 #undef mh_dirty
 #undef mh_place
diff --git a/src/box/hash_index.cc b/src/box/hash_index.cc
index bb88876104dabd550e556dc97310c359412fdd51..4b726caf25bef92cb292909768e972de1ca73bba 100644
--- a/src/box/hash_index.cc
+++ b/src/box/hash_index.cc
@@ -32,75 +32,125 @@
 #include "pickle.h"
 #include "exception.h"
 #include "space.h"
-#include "assoc.h"
 #include "errinj.h"
 
-/* {{{ HashIndex Iterators ****************************************/
-
-struct hash_i32_iterator {
-	struct iterator base; /* Must be the first member. */
-	struct mh_i32ptr_t *hash;
-	mh_int_t h_pos;
-};
-
-struct hash_i64_iterator {
-	struct iterator base;
-	struct mh_i64ptr_t *hash;
-	mh_int_t h_pos;
-};
+#include "third_party/PMurHash.h"
 
-struct hash_lstr_iterator {
-	struct iterator base;
-	struct mh_lstrptr_t *hash;
-	mh_int_t h_pos;
+enum {
+	HASH_SEED = 13U
 };
 
-void
-hash_iterator_free(struct iterator *iterator)
+static inline bool
+mh_index_eq(struct tuple *const *tuple_a, struct tuple *const *tuple_b,
+	    const struct key_def *key_def)
 {
-	assert(iterator->free == hash_iterator_free);
-	free(iterator);
+	return tuple_compare(*tuple_a, *tuple_b, key_def) == 0;
 }
 
-struct tuple *
-hash_iterator_i32_ge(struct iterator *ptr)
+static inline bool
+mh_index_eq_key(const char *key, struct tuple *const *tuple,
+		const struct key_def *key_def)
 {
-	assert(ptr->free == hash_iterator_free);
-	struct hash_i32_iterator *it = (struct hash_i32_iterator *) ptr;
+	return tuple_compare_with_key(*tuple, key, key_def->part_count,
+				      key_def) == 0;
+}
 
-	while (it->h_pos < mh_end(it->hash)) {
-		if (mh_exist(it->hash, it->h_pos))
-			return (struct tuple *) mh_i32ptr_node(it->hash, it->h_pos++)->val;
-		it->h_pos++;
+
+static inline uint32_t
+mh_index_hash(struct tuple *const *tuple, const struct key_def *key_def)
+{
+	struct key_part *part = key_def->parts;
+	uint32_t size;
+	/*
+	 * Speed up the simplest case when we have a
+	 * single-part hash over an integer field.
+	 */
+	if (key_def->part_count == 1 && part->type == NUM)
+		return *(uint32_t *) tuple_field(*tuple, part->fieldno, &size);
+
+	uint32_t h = HASH_SEED;
+	uint32_t carry = 0;
+	uint32_t total_size = 0;
+
+	for ( ; part < key_def->parts + key_def->part_count; part++) {
+		const char *field = tuple_field(*tuple, part->fieldno, &size);
+		assert(size < INT32_MAX);
+		PMurHash32_Process(&h, &carry, field, size);
+		total_size += size;
 	}
-	return NULL;
+
+	return PMurHash32_Result(h, carry, total_size);
 }
 
-struct tuple *
-hash_iterator_i64_ge(struct iterator *ptr)
+static inline uint32_t
+mh_index_hash_key(const char *key, const struct key_def *key_def)
 {
-	assert(ptr->free == hash_iterator_free);
-	struct hash_i64_iterator *it = (struct hash_i64_iterator *) ptr;
+	struct key_part *part = key_def->parts;
 
-	while (it->h_pos < mh_end(it->hash)) {
-		if (mh_exist(it->hash, it->h_pos))
-			return (struct tuple *) mh_i64ptr_node(
-						it->hash,it->h_pos++)->val;
-		it->h_pos++;
+	if (key_def->part_count == 1 && part->type == NUM) {
+		(void) load_varint32(&key);
+		return *(uint32_t *) key;
 	}
-	return NULL;
+	uint32_t h = HASH_SEED;
+	uint32_t carry = 0;
+	uint32_t total_size = 0;
+
+	for ( ; part < key_def->parts + key_def->part_count; part++) {
+		uint32_t size = load_varint32(&key);
+		if (part->type == NUM64 && size == sizeof(uint32_t)) {
+			/* Allow search in NUM64 indexes using NUM keys. */
+			uint64_t u64 = *(uint32_t *) key;
+			PMurHash32_Process(&h, &carry, &u64, sizeof(uint64_t));
+			total_size += sizeof(uint64_t);
+		} else {
+			assert(size < INT32_MAX);
+			PMurHash32_Process(&h, &carry, key, size);
+			total_size += size;
+		}
+		key += size;
+	}
+
+	return PMurHash32_Result(h, carry, total_size);
+}
+
+#define mh_int_t uint32_t
+#define mh_arg_t const struct key_def *
+
+#define mh_hash(a, arg) mh_index_hash(a, arg)
+#define mh_hash_key(a, arg) mh_index_hash_key(a, arg)
+#define mh_eq(a, b, arg) mh_index_eq(a, b, arg)
+#define mh_eq_key(a, b, arg) mh_index_eq_key(a, b, arg)
+
+#define mh_key_t const char *
+typedef struct tuple * mh_node_t;
+#define mh_name _index
+#define MH_SOURCE 1
+#include <mhash.h>
+
+/* {{{ HashIndex Iterators ****************************************/
+
+struct hash_iterator {
+	struct iterator base; /* Must be the first member. */
+	struct mh_index_t *hash;
+	uint32_t h_pos;
+};
+
+void
+hash_iterator_free(struct iterator *iterator)
+{
+	assert(iterator->free == hash_iterator_free);
+	free(iterator);
 }
 
 struct tuple *
-hash_iterator_lstr_ge(struct iterator *ptr)
+hash_iterator_ge(struct iterator *ptr)
 {
 	assert(ptr->free == hash_iterator_free);
-	struct hash_lstr_iterator *it = (struct hash_lstr_iterator *) ptr;
+	struct hash_iterator *it = (struct hash_iterator *) ptr;
 
 	while (it->h_pos < mh_end(it->hash)) {
 		if (mh_exist(it->hash, it->h_pos))
-			return (struct tuple *) mh_lstrptr_node(
-						it->hash,it->h_pos++)->val;
+			return *mh_index_node(it->hash, it->h_pos++);
 		it->h_pos++;
 	}
 	return NULL;
@@ -113,147 +163,29 @@ hash_iterator_eq_next(struct iterator *it __attribute__((unused)))
 }
 
 static struct tuple *
-hash_iterator_i32_eq(struct iterator *it)
-{
-	it->next = hash_iterator_eq_next;
-	return hash_iterator_i32_ge(it);
-}
-
-static struct tuple *
-hash_iterator_i64_eq(struct iterator *it)
-{
-	it->next = hash_iterator_eq_next;
-	return hash_iterator_i64_ge(it);
-}
-
-static struct tuple *
-hash_iterator_lstr_eq(struct iterator *it)
+hash_iterator_eq(struct iterator *it)
 {
 	it->next = hash_iterator_eq_next;
-	return hash_iterator_lstr_ge(it);
+	return hash_iterator_ge(it);
 }
 
 /* }}} */
 
 /* {{{ HashIndex -- base class for all hashes. ********************/
 
-class Hash32Index: public HashIndex {
-public:
-	Hash32Index(struct key_def *key_def, struct space *space);
-	~Hash32Index();
-
-	virtual size_t
-	size() const;
-
-	virtual struct tuple *
-	random(u32 rnd) const;
-
-	virtual struct tuple *
-	findByKey(const char *key, u32 part_count) const;
-
-	virtual struct tuple *
-	replace(struct tuple *old_tuple, struct tuple *new_tuple,
-		enum dup_replace_mode mode);
-
-	struct iterator *
-	allocIterator() const;
-
-	void
-	initIterator(struct iterator *iterator, enum iterator_type type,
-		     const char *key, u32 part_count) const;
-
-	virtual void
-	reserve(u32 n_tuples);
-private:
-	struct mh_i32ptr_t *int_hash;
-};
-
-class Hash64Index: public HashIndex {
-public:
-	Hash64Index(struct key_def *key_def, struct space *space);
-	~Hash64Index();
-
-	virtual size_t
-	size() const;
-
-	virtual struct tuple *
-	random(u32 rnd) const;
-
-	virtual struct tuple *
-	findByKey(const char *key, u32 part_count) const;
-
-	virtual struct tuple *
-	replace(struct tuple *old_tuple, struct tuple *new_tuple,
-		enum dup_replace_mode mode);
-
-	struct iterator *
-	allocIterator() const;
-
-	void
-	initIterator(struct iterator *iterator, enum iterator_type type,
-		     const char *key, u32 part_count) const;
-
-	virtual void
-	reserve(u32 n_tuples);
-private:
-	struct mh_i64ptr_t *int64_hash;
-};
-
-class HashStrIndex: public HashIndex {
-public:
-	HashStrIndex(struct key_def *key_def, struct space *space);
-	~HashStrIndex();
-
-	virtual size_t
-	size() const;
-
-	virtual struct tuple *
-	random(u32 rnd) const;
-
-	virtual struct tuple *
-	findByKey(const char *key, u32 part_count) const;
-
-	virtual struct tuple *
-	replace(struct tuple *old_tuple, struct tuple *new_tuple,
-		enum dup_replace_mode mode);
-
-	struct iterator *
-	allocIterator() const;
-
-	void
-	initIterator(struct iterator *iterator, enum iterator_type type,
-		     const char *key, u32 part_count) const;
-
-	virtual void
-	reserve(u32 n_tuples);
-private:
-	struct mh_lstrptr_t *str_hash;
-};
-
-HashIndex *
-HashIndex::factory(struct key_def *key_def, struct space *space)
+HashIndex::HashIndex(struct key_def *key_def, struct space *space)
+	: Index(key_def, space)
 {
-	/*
-	 * Hash index always has a single-field key.
-	 */
-	switch (key_def->parts[0].type) {
-	case NUM:
-		return new Hash32Index(key_def, space);  /* 32-bit integer hash */
-	case NUM64:
-		return new Hash64Index(key_def, space);  /* 64-bit integer hash */
-	case STRING:
-		return new HashStrIndex(key_def, space); /* string hash */
-	default:
-		assert(false);
+	hash = mh_index_new();
+	if (hash == NULL) {
+		tnt_raise(ClientError, ER_MEMORY_ISSUE, sizeof(hash),
+			  "HashIndex", "hash");
 	}
-
-	return NULL;
 }
 
-HashIndex::HashIndex(struct key_def *key_def, struct space *space)
-	: Index(key_def, space)
+HashIndex::~HashIndex()
 {
-	/* Nothing */
+	mh_index_delete(hash);
 }
 
 void
@@ -293,299 +225,84 @@ HashIndex::build(Index *pk)
 	      replace(NULL, tuple, DUP_INSERT);
 }
 
-struct tuple *
-HashIndex::min() const
-{
-	tnt_raise(ClientError, ER_UNSUPPORTED, "Hash index", "min()");
-	return NULL;
-}
-
-struct tuple *
-HashIndex::max() const
-{
-	tnt_raise(ClientError, ER_UNSUPPORTED, "Hash index", "max()");
-	return NULL;
-}
-
-
-
-/* }}} */
-
-/* {{{ Hash32Index ************************************************/
-
-static inline struct mh_i32ptr_node_t
-int32_key_to_node(const char *key)
-{
-	u32 key_size = load_varint32(&key);
-	if (key_size != 4)
-		tnt_raise(ClientError, ER_KEY_FIELD_TYPE, "u32");
-	struct mh_i32ptr_node_t node = { *(u32 *) key, NULL };
-	return node;
-}
-
-static inline struct mh_i32ptr_node_t
-int32_tuple_to_node(struct tuple *tuple, struct key_def *key_def)
-{
-	const char *field = tuple_field_old(tuple, key_def->parts[0].fieldno);
-	struct mh_i32ptr_node_t node = int32_key_to_node(field);
-	node.val = tuple;
-	return node;
-}
-
-
-Hash32Index::Hash32Index(struct key_def *key_def, struct space *space)
-	: HashIndex(key_def, space)
-{
-	int_hash = mh_i32ptr_new();
-}
-
-Hash32Index::~Hash32Index()
-{
-	mh_i32ptr_delete(int_hash);
-}
-
 void
-Hash32Index::reserve(u32 n_tuples)
+HashIndex::reserve(u32 n_tuples)
 {
-	mh_i32ptr_reserve(int_hash, n_tuples, NULL);
+	mh_index_reserve(hash, n_tuples, key_def);
 }
 
 size_t
-Hash32Index::size() const
+HashIndex::size() const
 {
-	return mh_size(int_hash);
+	return mh_size(hash);
 }
 
 
 struct tuple *
-Hash32Index::random(u32 rnd) const
+HashIndex::min() const
 {
-	mh_int_t k = mh_i32ptr_random(int_hash, rnd);
-	if (k != mh_end(int_hash))
-		return (struct tuple *) mh_i32ptr_node(int_hash, k)->val;
+	tnt_raise(ClientError, ER_UNSUPPORTED, "Hash index", "min()");
 	return NULL;
 }
 
 struct tuple *
-Hash32Index::findByKey(const char *key, u32 part_count) const
-{
-	assert(key_def->is_unique && part_count == key_def->part_count);
-	struct tuple *ret = NULL;
-	struct mh_i32ptr_node_t node = int32_key_to_node(key);
-	mh_int_t k = mh_i32ptr_get(int_hash, &node, NULL);
-	if (k != mh_end(int_hash))
-		ret = (struct tuple *) mh_i32ptr_node(int_hash, k)->val;
-#ifdef DEBUG
-	say_debug("Hash32Index find(self:%p, key:%i) = %p", self, node.key, ret);
-#endif
-	return ret;
-}
-
-struct tuple *
-Hash32Index::replace(struct tuple *old_tuple, struct tuple *new_tuple,
-		     enum dup_replace_mode mode)
-{
-	struct mh_i32ptr_node_t new_node, old_node;
-	uint32_t errcode;
-
-	if (new_tuple) {
-		struct mh_i32ptr_node_t *dup_node = &old_node;
-		new_node = int32_tuple_to_node(new_tuple, key_def);
-		mh_int_t pos = mh_i32ptr_put(int_hash, &new_node,
-					     &dup_node, NULL);
-
-		ERROR_INJECT(ERRINJ_INDEX_ALLOC,
-		{
-			mh_i32ptr_del(int_hash, pos, NULL);
-			pos = mh_end(int_hash);
-		});
-
-		if (pos == mh_end(int_hash)) {
-			tnt_raise(LoggedError, ER_MEMORY_ISSUE, (ssize_t) pos,
-				  "int hash", "key");
-		}
-		struct tuple *dup_tuple = (dup_node ?
-					   (struct tuple *) dup_node->val
-					   : NULL);
-		errcode = replace_check_dup(old_tuple, dup_tuple, mode);
-
-		if (errcode) {
-			mh_i32ptr_remove(int_hash, &new_node, NULL);
-			if (dup_node) {
-				pos = mh_i32ptr_put(int_hash, dup_node,
-						    NULL, NULL);
-				if (pos == mh_end(int_hash)) {
-					panic("Failed to allocate memory in "
-					      "recover of int hash");
-				}
-			}
-			tnt_raise(ClientError, errcode, index_n(this));
-		}
-		if (dup_tuple)
-			return dup_tuple;
-	}
-	if (old_tuple) {
-		old_node = int32_tuple_to_node(old_tuple, key_def);
-		mh_i32ptr_remove(int_hash, &old_node, NULL);
-	}
-	return old_tuple;
-}
-
-
-struct iterator *
-Hash32Index::allocIterator() const
-{
-	struct hash_i32_iterator *it = (struct hash_i32_iterator *)
-			malloc(sizeof(struct hash_i32_iterator));
-	if (it) {
-		memset(it, 0, sizeof(*it));
-		it->base.next = hash_iterator_i32_ge;
-		it->base.free = hash_iterator_free;
-	}
-	return (struct iterator *) it;
-}
-
-void
-Hash32Index::initIterator(struct iterator *ptr, enum iterator_type type,
-			  const char *key, u32 part_count) const
-{
-	assert ((part_count == 1 && key != NULL) || part_count == 0);
-	(void) part_count;
-	assert(ptr->free == hash_iterator_free);
-
-	struct hash_i32_iterator *it = (struct hash_i32_iterator *) ptr;
-	struct mh_i32ptr_node_t node;
-
-	switch (type) {
-	case ITER_GE:
-		if (key != NULL) {
-			node = int32_key_to_node(key);
-			it->h_pos = mh_i32ptr_get(int_hash, &node, NULL);
-			it->base.next = hash_iterator_i32_ge;
-			break;
-		}
-		/* Fall through. */
-	case ITER_ALL:
-		it->h_pos = mh_begin(int_hash);
-		it->base.next = hash_iterator_i32_ge;
-		break;
-	case ITER_EQ:
-		node = int32_key_to_node(key);
-		it->h_pos = mh_i32ptr_get(int_hash, &node, NULL);
-		it->base.next = hash_iterator_i32_eq;
-		break;
-	default:
-		tnt_raise(ClientError, ER_UNSUPPORTED,
-			  "Hash index", "requested iterator type");
-	}
-	it->hash = int_hash;
-}
-
-/* }}} */
-
-/* {{{ Hash64Index ************************************************/
-
-static inline struct mh_i64ptr_node_t
-int64_key_to_node(const char *key)
-{
-	u32 key_size = load_varint32(&key);
-	if (key_size != 8)
-		tnt_raise(ClientError, ER_KEY_FIELD_TYPE, "u64");
-	struct mh_i64ptr_node_t node = { *(u64 *) key, NULL };
-	return node;
-}
-
-static inline struct mh_i64ptr_node_t
-int64_tuple_to_node(struct tuple *tuple, struct key_def *key_def)
-{
-	const char *field = tuple_field_old(tuple, key_def->parts[0].fieldno);
-	struct mh_i64ptr_node_t node = int64_key_to_node(field);
-	node.val = tuple;
-	return node;
-}
-
-Hash64Index::Hash64Index(struct key_def *key_def, struct space *space)
-	: HashIndex(key_def, space)
-{
-	int64_hash = mh_i64ptr_new();
-}
-
-Hash64Index::~Hash64Index()
-{
-	mh_i64ptr_delete(int64_hash);
-}
-
-void
-Hash64Index::reserve(u32 n_tuples)
-{
-	mh_i64ptr_reserve(int64_hash, n_tuples, NULL);
-}
-
-size_t
-Hash64Index::size() const
+HashIndex::max() const
 {
-	return mh_size(int64_hash);
+	tnt_raise(ClientError, ER_UNSUPPORTED, "Hash index", "max()");
+	return NULL;
 }
 
 struct tuple *
-Hash64Index::random(u32 rnd) const
+HashIndex::random(u32 rnd) const
 {
-	mh_int_t k = mh_i64ptr_random(int64_hash, rnd);
-	if (k != mh_end(int64_hash))
-		return (struct tuple *) mh_i64ptr_node(int64_hash, k)->val;
+	uint32_t k = mh_index_random(hash, rnd);
+	if (k != mh_end(hash))
+		return *mh_index_node(hash, k);
 	return NULL;
 }
 
 struct tuple *
-Hash64Index::findByKey(const char *key, u32 part_count) const
+HashIndex::findByKey(const char *key, u32 part_count) const
 {
 	assert(key_def->is_unique && part_count == key_def->part_count);
-	(void) part_count;
 
 	struct tuple *ret = NULL;
-	struct mh_i64ptr_node_t node = int64_key_to_node(key);
-	mh_int_t k = mh_i64ptr_get(int64_hash, &node, NULL);
-	if (k != mh_end(int64_hash))
-		ret = (struct tuple *) mh_i64ptr_node(int64_hash, k)->val;
-#ifdef DEBUG
-	say_debug("Hash64Index find(self:%p, key:%i) = %p", self, node.key, ret);
-#endif
+	uint32_t k = mh_index_find(hash, key, key_def);
+	if (k != mh_end(hash))
+		ret = *mh_index_node(hash, k);
 	return ret;
 }
 
 struct tuple *
-Hash64Index::replace(struct tuple *old_tuple, struct tuple *new_tuple,
-		     enum dup_replace_mode mode)
+HashIndex::replace(struct tuple *old_tuple, struct tuple *new_tuple,
+		   enum dup_replace_mode mode)
 {
-	struct mh_i64ptr_node_t new_node, old_node;
 	uint32_t errcode;
 
 	if (new_tuple) {
-		struct mh_i64ptr_node_t *dup_node = &old_node;
-		new_node = int64_tuple_to_node(new_tuple, key_def);
-		mh_int_t pos = mh_i64ptr_put(int64_hash, &new_node,
-					     &dup_node, NULL);
+		struct tuple *dup_tuple = NULL;
+		struct tuple **dup_node = &dup_tuple;
+		uint32_t pos = mh_index_put(hash, &new_tuple,
+					    &dup_node, key_def);
 
 		ERROR_INJECT(ERRINJ_INDEX_ALLOC,
 		{
-			mh_i64ptr_del(int64_hash, pos, NULL);
-			pos = mh_end(int64_hash);
+			mh_index_del(hash, pos, key_def);
+			pos = mh_end(hash);
 		});
 
-		if (pos == mh_end(int64_hash)) {
+		if (pos == mh_end(hash)) {
 			tnt_raise(LoggedError, ER_MEMORY_ISSUE, (ssize_t) pos,
-				  "int hash", "key");
+				  "hash", "key");
 		}
-		struct tuple *dup_tuple = (dup_node ?
-					   (struct tuple *) dup_node->val :
-					   NULL);
 		errcode = replace_check_dup(old_tuple, dup_tuple, mode);
 
 		if (errcode) {
-			mh_i64ptr_remove(int64_hash, &new_node, NULL);
-			if (dup_node) {
-				pos = mh_i64ptr_put(int64_hash, dup_node, NULL, NULL);
-				if (pos == mh_end(int64_hash)) {
+			mh_index_remove(hash, &new_tuple, key_def);
+			if (dup_tuple) {
+				pos = mh_index_put(hash, &dup_tuple, NULL,
+						   key_def);
+				if (pos == mh_end(hash)) {
 					panic("Failed to allocate memory in "
 					      "recover of int hash");
 				}
@@ -598,229 +315,57 @@ Hash64Index::replace(struct tuple *old_tuple, struct tuple *new_tuple,
 	}
 
 	if (old_tuple) {
-		old_node = int64_tuple_to_node(old_tuple, key_def);
-		mh_i64ptr_remove(int64_hash, &old_node, NULL);
+		mh_index_remove(hash, &old_tuple, key_def);
 	}
-
 	return old_tuple;
 }
 
-
 struct iterator *
-Hash64Index::allocIterator() const
-{
-	struct hash_i64_iterator *it = (struct hash_i64_iterator *)
-			malloc(sizeof(struct hash_i64_iterator));
-	if (it) {
-		memset(it, 0, sizeof(*it));
-		it->base.next = hash_iterator_i64_ge;
-		it->base.free = hash_iterator_free;
+HashIndex::allocIterator() const
+{
+	struct hash_iterator *it = (struct hash_iterator *)
+			calloc(1, sizeof(*it));
+	if (it == NULL) {
+		tnt_raise(ClientError, ER_MEMORY_ISSUE,
+			  sizeof(struct hash_iterator),
+			  "HashIndex", "iterator");
 	}
 
+	it->base.next = hash_iterator_ge;
+	it->base.free = hash_iterator_free;
 	return (struct iterator *) it;
 }
 
 void
-Hash64Index::initIterator(struct iterator *ptr, enum iterator_type type,
-			  const char *key, u32 part_count) const
+HashIndex::initIterator(struct iterator *ptr, enum iterator_type type,
+			const char *key, u32 part_count) const
 {
-	assert ((part_count == 1 && key != NULL) || part_count == 0);
+	assert (key != NULL || part_count == 0);
 	(void) part_count;
 	assert(ptr->free == hash_iterator_free);
-	struct hash_i64_iterator *it = (struct hash_i64_iterator *) ptr;
-	struct mh_i64ptr_node_t node;
 
-	switch (type) {
-	case ITER_GE:
-		if (key != NULL) {
-			node = int64_key_to_node(key);
-			it->h_pos = mh_i64ptr_get(int64_hash, &node, NULL);
-			it->base.next = hash_iterator_i64_ge;
-			break;
-		}
-		/* Fall through. */
-	case ITER_ALL:
-		it->h_pos = mh_begin(int64_hash);
-		it->base.next = hash_iterator_i64_ge;
-		break;
-	case ITER_EQ:
-		node = int64_key_to_node(key);
-		it->h_pos = mh_i64ptr_get(int64_hash, &node, NULL);
-		it->base.next = hash_iterator_i64_eq;
-		break;
-	default:
-		tnt_raise(ClientError, ER_UNSUPPORTED,
-			  "Hash index", "requested iterator type");
-	}
-	it->hash = int64_hash;
-}
-
-/* }}} */
-
-/* {{{ HashStrIndex ***********************************************/
-
-static inline struct mh_lstrptr_node_t
-lstrptr_tuple_to_node(struct tuple *tuple, struct key_def *key_def)
-{
-	const char *field = tuple_field_old(tuple, key_def->parts[0].fieldno);
-	if (field == NULL)
-		tnt_raise(ClientError, ER_NO_SUCH_FIELD,
-			  key_def->parts[0].fieldno);
-
-	struct mh_lstrptr_node_t node = { field, tuple };
-	return node;
-}
-
-HashStrIndex::HashStrIndex(struct key_def *key_def, struct space *space)
-	: HashIndex(key_def, space)
-{
-	str_hash = mh_lstrptr_new();
-}
-
-HashStrIndex::~HashStrIndex()
-{
-	mh_lstrptr_delete(str_hash);
-}
-
-void
-HashStrIndex::reserve(u32 n_tuples)
-{
-	mh_lstrptr_reserve(str_hash, n_tuples, NULL);
-}
-
-
-size_t
-HashStrIndex::size() const
-{
-	return mh_size(str_hash);
-}
-
-struct tuple *
-HashStrIndex::random(u32 rnd) const
-{
-	mh_int_t k = mh_lstrptr_random(str_hash, rnd);
-	if (k != mh_end(str_hash))
-		return (struct tuple *) mh_lstrptr_node(str_hash, k)->val;
-	return NULL;
-}
-
-struct tuple *
-HashStrIndex::findByKey(const char *key, u32 part_count) const
-{
-	assert(key_def->is_unique && part_count == key_def->part_count);
-	(void) part_count;
-
-	struct tuple *ret = NULL;
-	const struct mh_lstrptr_node_t node = { key, NULL };
-	mh_int_t k = mh_lstrptr_get(str_hash, &node, NULL);
-	if (k != mh_end(str_hash))
-		ret = (struct tuple *) mh_lstrptr_node(str_hash, k)->val;
-#ifdef DEBUG
-	u32 key_size = load_varint32(&key);
-	say_debug("HashStrIndex find(self:%p, key:(%i)'%.*s') = %p",
-		  self, key_size, key_size, key, ret);
-#endif
-	return ret;
-}
-
-struct tuple *
-HashStrIndex::replace(struct tuple *old_tuple, struct tuple *new_tuple,
-	enum dup_replace_mode mode)
-{
-	struct mh_lstrptr_node_t new_node, old_node;
-	uint32_t errcode;
-
-	if (new_tuple) {
-		struct mh_lstrptr_node_t *dup_node = &old_node;
-		new_node = lstrptr_tuple_to_node(new_tuple, key_def);
-		mh_int_t pos = mh_lstrptr_put(str_hash, &new_node,
-					      &dup_node, NULL);
-
-		ERROR_INJECT(ERRINJ_INDEX_ALLOC,
-		{
-			mh_lstrptr_del(str_hash, pos, NULL);
-			pos = mh_end(str_hash);
-		});
-
-		if (pos == mh_end(str_hash)) {
-			tnt_raise(LoggedError, ER_MEMORY_ISSUE, (ssize_t) pos,
-				  "str hash", "key");
-		}
-		struct tuple *dup_tuple = dup_node
-				? (struct tuple *) dup_node->val
-				: NULL;
-		errcode = replace_check_dup(old_tuple, dup_tuple, mode);
-
-		if (errcode) {
-			mh_lstrptr_remove(str_hash, &new_node, NULL);
-			if (dup_node) {
-				pos = mh_lstrptr_put(str_hash, dup_node,
-						     NULL, NULL);
-				if (pos == mh_end(str_hash)) {
-					panic("Failed to allocate memory in "
-					      "recover of str hash");
-				}
-			}
-			tnt_raise(ClientError, errcode, index_n(this));
-		}
-		if (dup_tuple)
-			return dup_tuple;
-	}
-	if (old_tuple) {
-		old_node = lstrptr_tuple_to_node(old_tuple, key_def);
-		mh_lstrptr_remove(str_hash, &old_node, NULL);
-	}
-	return old_tuple;
-}
-
-struct iterator *
-HashStrIndex::allocIterator() const
-{
-	struct hash_lstr_iterator *it = (struct hash_lstr_iterator *)
-			malloc(sizeof(struct hash_lstr_iterator));
-	if (it) {
-		memset(it, 0, sizeof(*it));
-		it->base.next = hash_iterator_lstr_ge;
-		it->base.free = hash_iterator_free;
-	}
-	return (struct iterator *) it;
-}
-
-void
-HashStrIndex::initIterator(struct iterator *ptr, enum iterator_type type,
-			   const char *key, u32 part_count) const
-{
-	assert ((part_count == 1 && key != NULL) || part_count == 0);
-	(void) part_count;
-
-	assert(ptr->free == hash_iterator_free);
-	struct hash_lstr_iterator *it = (struct hash_lstr_iterator *) ptr;
-	struct mh_lstrptr_node_t node;
+	struct hash_iterator *it = (struct hash_iterator *) ptr;
 
 	switch (type) {
 	case ITER_GE:
 		if (key != NULL) {
-			node.key = key;
-			it->h_pos = mh_lstrptr_get(str_hash, &node, NULL);
-			it->base.next = hash_iterator_lstr_ge;
+			it->h_pos = mh_index_find(hash, key, key_def);
+			it->base.next = hash_iterator_ge;
 			break;
 		}
 		/* Fall through. */
 	case ITER_ALL:
-		it->base.next = hash_iterator_lstr_ge;
-		it->h_pos = mh_begin(str_hash);
+		it->h_pos = mh_begin(hash);
+		it->base.next = hash_iterator_ge;
 		break;
 	case ITER_EQ:
-		node.key = key;
-		it->h_pos = mh_lstrptr_get(str_hash, &node, NULL);
-		it->base.next = hash_iterator_lstr_eq;
+		it->h_pos = mh_index_find(hash, key, key_def);
+		it->base.next = hash_iterator_eq;
 		break;
 	default:
 		tnt_raise(ClientError, ER_UNSUPPORTED,
 			  "Hash index", "requested iterator type");
 	}
-	it->hash = str_hash;
+	it->hash = hash;
 }
-
 /* }}} */
-
diff --git a/src/box/hash_index.h b/src/box/hash_index.h
index 409d171cb2cf08321d209dec7870447c47d5f1e6..b315e72ae9057e235b3e3e9b572ca6109324a837 100644
--- a/src/box/hash_index.h
+++ b/src/box/hash_index.h
@@ -31,33 +31,35 @@
 
 #include "index.h"
 
+struct mh_index_t;
 
 class HashIndex: public Index {
 public:
-	static HashIndex *
-	factory(struct key_def *key_def, struct space *space);
-
 	HashIndex(struct key_def *key_def, struct space *space);
+	~HashIndex();
 
 	virtual void beginBuild();
 	virtual void buildNext(struct tuple *tuple);
 	virtual void endBuild();
 	virtual void build(Index *pk);
-	virtual size_t size() const  = 0;
+	virtual size_t size() const;
 	virtual struct tuple *min() const;
 	virtual struct tuple *max() const;
-	virtual struct tuple *random(u32 rnd) const = 0;
-	virtual struct tuple *findByKey(const char *key, u32 part_count) const  = 0;
+	virtual struct tuple *random(u32 rnd) const;
+	virtual struct tuple *findByKey(const char *key, u32 part_count) const;
 	virtual struct tuple *replace(struct tuple *old_tuple,
 				      struct tuple *new_tuple,
-				      enum dup_replace_mode mode)  = 0;
+				      enum dup_replace_mode mode);
 
-	virtual struct iterator *allocIterator() const = 0;
+	virtual struct iterator *allocIterator() const;
 	virtual void initIterator(struct iterator *iterator,
 				  enum iterator_type type,
-				  const char *key, u32 part_count) const  = 0;
+				  const char *key, u32 part_count) const;
+
+	virtual void reserve(u32 n_tuples);
 
-	virtual void reserve(u32 n_tuples) = 0;
+protected:
+	struct mh_index_t *hash;
 };
 
 #endif /* TARANTOOL_BOX_HASH_INDEX_H_INCLUDED */
diff --git a/src/box/index.cc b/src/box/index.cc
index b5e07081fdeaee9acb36d7601fd5c804b7bb70a1..9a7c212c5c6a254df8237dc12ce37e803ce5f976 100644
--- a/src/box/index.cc
+++ b/src/box/index.cc
@@ -143,7 +143,7 @@ Index::factory(enum index_type type, struct key_def *key_def, struct space *spac
 {
 	switch (type) {
 	case HASH:
-		return HashIndex::factory(key_def, space);
+		return new HashIndex(key_def, space);
 	case TREE:
 		return new TreeIndex(key_def, space);
 	case BITSET:
diff --git a/src/box/space.cc b/src/box/space.cc
index dc9eb92017f1d9a74ecb3514e291fd0f9e264f9d..90b6e3f3eb00b082b646b443dbf278fd8be52c3c 100644
--- a/src/box/space.cc
+++ b/src/box/space.cc
@@ -586,12 +586,6 @@ check_spaces(struct tarantool_cfg *conf)
 			switch (index_type) {
 			case HASH:
 				/* check hash index */
-				/* hash index must has single-field key */
-				if (key_part_count != 1) {
-					out_warning(CNF_OK, "(space = %zu index = %zu) "
-						    "hash index must has a single-field key", i, j);
-					return -1;
-				}
 				/* hash index must be unique */
 				if (!index->unique) {
 					out_warning(CNF_OK, "(space = %zu index = %zu) "
diff --git a/test/big/hash.result b/test/big/hash.result
index 6099e94a9c3c51624d3f20c588b009316310a63f..1734cc3de78fc9df1f894d1266f36c53b15695d5 100644
--- a/test/big/hash.result
+++ b/test/big/hash.result
@@ -156,25 +156,41 @@ error: 'Invalid key part count in an exact match (expected 1, got 2)'
 
 # Insert valid fieds
 
-lua box.space[11]:insert('00000000', 'value1 v1.0', 'value2 v1.0')
+lua box.space[11]:insert(0ULL, 'value1 v1.0', 'value2 v1.0')
 ---
- - 3472328296227680304: {'value1 v1.0', 'value2 v1.0'}
+ - 0: {'value1 v1.0', 'value2 v1.0'}
 ...
-lua box.space[11]:insert('00000001', 'value1 v1.0', 'value2 v1.0')
+lua box.space[11]:insert(1ULL, 'value1 v1.0', 'value2 v1.0')
 ---
- - 3544385890265608240: {'value1 v1.0', 'value2 v1.0'}
+ - 1: {'value1 v1.0', 'value2 v1.0'}
 ...
-lua box.space[11]:insert('00000002', 'value1 v1.0', 'value2 v1.0')
+lua box.space[11]:insert(2ULL, 'value1 v1.0', 'value2 v1.0')
 ---
- - 3616443484303536176: {'value1 v1.0', 'value2 v1.0'}
+ - 2: {'value1 v1.0', 'value2 v1.0'}
 ...
-lua box.space[11]:insert('00000003', 'value1 v1.0', 'value2 v1.0')
+lua box.space[11]:insert(3ULL, 'value1 v1.0', 'value2 v1.0')
 ---
- - 3688501078341464112: {'value1 v1.0', 'value2 v1.0'}
+ - 3: {'value1 v1.0', 'value2 v1.0'}
 ...
 
 # Insert invalid fields
 
+lua box.space[11]:insert(100, 'value1 v1.0', 'value2 v1.0')
+---
+error: 'Supplied key field type does not match index type: expected u64'
+...
+lua box.space[11]:insert(101, 'value1 v1.0', 'value2 v1.0')
+---
+error: 'Supplied key field type does not match index type: expected u64'
+...
+lua box.space[11]:insert(102, 'value1 v1.0', 'value2 v1.0')
+---
+error: 'Supplied key field type does not match index type: expected u64'
+...
+lua box.space[11]:insert(103, 'value1 v1.0', 'value2 v1.0')
+---
+error: 'Supplied key field type does not match index type: expected u64'
+...
 lua box.space[11]:insert('invalid key', 'value1 v1.0', 'value2 v1.0')
 ---
 error: 'Supplied key field type does not match index type: expected u64'
@@ -187,24 +203,36 @@ error: 'Supplied key field type does not match index type: expected u64'
 
 # Replace valid fieds
 
-lua box.space[11]:replace('00000003', 'value1 v1.31', 'value2 1.12')
+lua box.space[11]:replace(3ULL, 'value1 v1.31', 'value2 1.12')
 ---
- - 3688501078341464112: {'value1 v1.31', 'value2 1.12'}
+ - 3: {'value1 v1.31', 'value2 1.12'}
 ...
-lua box.space[11]:replace('00000001', 'value1 v1.32', 'value2 1.72')
+lua box.space[11]:replace(1ULL, 'value1 v1.32', 'value2 1.72')
 ---
- - 3544385890265608240: {'value1 v1.32', 'value2 1.72'}
+ - 1: {'value1 v1.32', 'value2 1.72'}
 ...
-lua box.space[11]:replace('00000002', 'value1 v1.43', 'value2 1.92')
+lua box.space[11]:replace(2ULL, 'value1 v1.43', 'value2 1.92')
 ---
- - 3616443484303536176: {'value1 v1.43', 'value2 1.92'}
+ - 2: {'value1 v1.43', 'value2 1.92'}
 ...
 
 # Replace invalid fields
 
-lua box.space[10]:replace('invalid key', 'value1 v1.0', 'value2 v1.0')
+lua box.space[11]:replace(3, 'value1 v1.31', 'value2 1.12')
 ---
-error: 'Supplied key field type does not match index type: expected u32'
+error: 'Supplied key field type does not match index type: expected u64'
+...
+lua box.space[11]:replace(1, 'value1 v1.32', 'value2 1.72')
+---
+error: 'Supplied key field type does not match index type: expected u64'
+...
+lua box.space[11]:replace(2, 'value1 v1.43', 'value2 1.92')
+---
+error: 'Supplied key field type does not match index type: expected u64'
+...
+lua box.space[11]:replace('invalid key', 'value1 v1.0', 'value2 v1.0')
+---
+error: 'Supplied key field type does not match index type: expected u64'
 ...
 
 #-----------------------------------------------------------------------------#
@@ -214,26 +242,51 @@ error: 'Supplied key field type does not match index type: expected u32'
 
 # select by valid keys
 
-lua box.space[11]:select(0, '00000000')
+lua box.space[11]:select(0, 0ULL)
 ---
- - 3472328296227680304: {'value1 v1.0', 'value2 v1.0'}
+ - 0: {'value1 v1.0', 'value2 v1.0'}
 ...
-lua box.space[11]:select(0, '00000001')
+lua box.space[11]:select(0, 1ULL)
 ---
- - 3544385890265608240: {'value1 v1.32', 'value2 1.72'}
+ - 1: {'value1 v1.32', 'value2 1.72'}
 ...
-lua box.space[11]:select(0, '00000002')
+lua box.space[11]:select(0, 2ULL)
 ---
- - 3616443484303536176: {'value1 v1.43', 'value2 1.92'}
+ - 2: {'value1 v1.43', 'value2 1.92'}
 ...
-lua box.space[11]:select(0, '00000003')
+lua box.space[11]:select(0, 3ULL)
 ---
- - 3688501078341464112: {'value1 v1.31', 'value2 1.12'}
+ - 3: {'value1 v1.31', 'value2 1.12'}
 ...
-lua box.space[11]:select(0, '00000004')
+lua box.space[11]:select(0, 4ULL)
 ---
 ...
-lua box.space[11]:select(0, '00000005')
+lua box.space[11]:select(0, 5ULL)
+---
+...
+
+# select by valid NUM keys
+
+lua box.space[11]:select(0, 0)
+---
+ - 0: {'value1 v1.0', 'value2 v1.0'}
+...
+lua box.space[11]:select(0, 1)
+---
+ - 1: {'value1 v1.32', 'value2 1.72'}
+...
+lua box.space[11]:select(0, 2)
+---
+ - 2: {'value1 v1.43', 'value2 1.92'}
+...
+lua box.space[11]:select(0, 3)
+---
+ - 3: {'value1 v1.31', 'value2 1.12'}
+...
+lua box.space[11]:select(0, 4)
+---
+...
+lua box.space[11]:select(0, 5)
 ---
 ...
 
@@ -255,26 +308,67 @@ error: 'Invalid key part count (expected [0..1], got 2)'
 
 # delete by valid keys
 
-lua box.space[11]:delete('00000000')
+lua box.space[11]:delete(0ULL)
+---
+ - 0: {'value1 v1.0', 'value2 v1.0'}
+...
+lua box.space[11]:delete(1ULL)
+---
+ - 1: {'value1 v1.32', 'value2 1.72'}
+...
+lua box.space[11]:delete(2ULL)
 ---
- - 3472328296227680304: {'value1 v1.0', 'value2 v1.0'}
+ - 2: {'value1 v1.43', 'value2 1.92'}
 ...
-lua box.space[11]:delete('00000001')
+lua box.space[11]:delete(3ULL)
 ---
- - 3544385890265608240: {'value1 v1.32', 'value2 1.72'}
+ - 3: {'value1 v1.31', 'value2 1.12'}
 ...
-lua box.space[11]:delete('00000002')
+lua box.space[11]:delete(4ULL)
 ---
- - 3616443484303536176: {'value1 v1.43', 'value2 1.92'}
 ...
-lua box.space[11]:delete('00000003')
+lua box.space[11]:delete(5ULL)
+---
+...
+lua box.space[11]:insert(0ULL, 'value1 v1.0', 'value2 v1.0')
+---
+ - 0: {'value1 v1.0', 'value2 v1.0'}
+...
+lua box.space[11]:insert(1ULL, 'value1 v1.0', 'value2 v1.0')
 ---
- - 3688501078341464112: {'value1 v1.31', 'value2 1.12'}
+ - 1: {'value1 v1.0', 'value2 v1.0'}
+...
+lua box.space[11]:insert(2ULL, 'value1 v1.0', 'value2 v1.0')
+---
+ - 2: {'value1 v1.0', 'value2 v1.0'}
+...
+lua box.space[11]:insert(3ULL, 'value1 v1.0', 'value2 v1.0')
+---
+ - 3: {'value1 v1.0', 'value2 v1.0'}
+...
+
+# delete by valid NUM keys
+
+lua box.space[11]:delete(0)
+---
+ - 0: {'value1 v1.0', 'value2 v1.0'}
+...
+lua box.space[11]:delete(1)
+---
+ - 1: {'value1 v1.0', 'value2 v1.0'}
+...
+lua box.space[11]:delete(2)
+---
+ - 2: {'value1 v1.0', 'value2 v1.0'}
+...
+lua box.space[11]:delete(3)
+---
+ - 3: {'value1 v1.0', 'value2 v1.0'}
 ...
-lua box.space[11]:delete('00000004')
+lua box.space[11]:delete(4)
 ---
 ...
-lua box.space[11]:delete('00000005')
+lua box.space[11]:delete(5)
 ---
 ...
 
diff --git a/test/big/hash.test b/test/big/hash.test
index 51c46574092d540d1b34acf510427594c2ee57da..79b510266fb3c45aa865205e710497c77712f985 100644
--- a/test/big/hash.test
+++ b/test/big/hash.test
@@ -107,17 +107,20 @@ print """
 print """
 # Insert valid fieds
 """
-exec admin "lua box.space[11]:insert('00000000', 'value1 v1.0', 'value2 v1.0')"
-exec admin "lua box.space[11]:insert('00000001', 'value1 v1.0', 'value2 v1.0')"
-exec admin "lua box.space[11]:insert('00000002', 'value1 v1.0', 'value2 v1.0')"
-exec admin "lua box.space[11]:insert('00000003', 'value1 v1.0', 'value2 v1.0')"
+exec admin "lua box.space[11]:insert(0ULL, 'value1 v1.0', 'value2 v1.0')"
+exec admin "lua box.space[11]:insert(1ULL, 'value1 v1.0', 'value2 v1.0')"
+exec admin "lua box.space[11]:insert(2ULL, 'value1 v1.0', 'value2 v1.0')"
+exec admin "lua box.space[11]:insert(3ULL, 'value1 v1.0', 'value2 v1.0')"
 
 print """
 # Insert invalid fields
 """
+exec admin "lua box.space[11]:insert(100, 'value1 v1.0', 'value2 v1.0')"
+exec admin "lua box.space[11]:insert(101, 'value1 v1.0', 'value2 v1.0')"
+exec admin "lua box.space[11]:insert(102, 'value1 v1.0', 'value2 v1.0')"
+exec admin "lua box.space[11]:insert(103, 'value1 v1.0', 'value2 v1.0')"
 exec admin "lua box.space[11]:insert('invalid key', 'value1 v1.0', 'value2 v1.0')"
 
-
 print """
 #-----------------------------------------------------------------------------#
 # 64-bit hash replace fields tests
@@ -127,15 +130,17 @@ print """
 print """
 # Replace valid fieds
 """
-exec admin "lua box.space[11]:replace('00000003', 'value1 v1.31', 'value2 1.12')"
-exec admin "lua box.space[11]:replace('00000001', 'value1 v1.32', 'value2 1.72')"
-exec admin "lua box.space[11]:replace('00000002', 'value1 v1.43', 'value2 1.92')"
+exec admin "lua box.space[11]:replace(3ULL, 'value1 v1.31', 'value2 1.12')"
+exec admin "lua box.space[11]:replace(1ULL, 'value1 v1.32', 'value2 1.72')"
+exec admin "lua box.space[11]:replace(2ULL, 'value1 v1.43', 'value2 1.92')"
 
 print """
 # Replace invalid fields
 """
-exec admin "lua box.space[10]:replace('invalid key', 'value1 v1.0', 'value2 v1.0')"
-
+exec admin "lua box.space[11]:replace(3, 'value1 v1.31', 'value2 1.12')"
+exec admin "lua box.space[11]:replace(1, 'value1 v1.32', 'value2 1.72')"
+exec admin "lua box.space[11]:replace(2, 'value1 v1.43', 'value2 1.92')"
+exec admin "lua box.space[11]:replace('invalid key', 'value1 v1.0', 'value2 v1.0')"
 
 print """
 #-----------------------------------------------------------------------------#
@@ -146,12 +151,22 @@ print """
 print """
 # select by valid keys
 """
-exec admin "lua box.space[11]:select(0, '00000000')"
-exec admin "lua box.space[11]:select(0, '00000001')"
-exec admin "lua box.space[11]:select(0, '00000002')"
-exec admin "lua box.space[11]:select(0, '00000003')"
-exec admin "lua box.space[11]:select(0, '00000004')"
-exec admin "lua box.space[11]:select(0, '00000005')"
+exec admin "lua box.space[11]:select(0, 0ULL)"
+exec admin "lua box.space[11]:select(0, 1ULL)"
+exec admin "lua box.space[11]:select(0, 2ULL)"
+exec admin "lua box.space[11]:select(0, 3ULL)"
+exec admin "lua box.space[11]:select(0, 4ULL)"
+exec admin "lua box.space[11]:select(0, 5ULL)"
+
+print """
+# select by valid NUM keys
+"""
+exec admin "lua box.space[11]:select(0, 0)"
+exec admin "lua box.space[11]:select(0, 1)"
+exec admin "lua box.space[11]:select(0, 2)"
+exec admin "lua box.space[11]:select(0, 3)"
+exec admin "lua box.space[11]:select(0, 4)"
+exec admin "lua box.space[11]:select(0, 5)"
 
 print """
 # select by invalid keys
@@ -159,7 +174,6 @@ print """
 exec admin "lua box.space[11]:select(0, 'invalid key')"
 exec admin "lua box.space[11]:select(0, '00000001', '00000002')"
 
-
 print """
 #-----------------------------------------------------------------------------#
 # 64-bit hash delete fields test
@@ -169,12 +183,27 @@ print """
 print """
 # delete by valid keys
 """
-exec admin "lua box.space[11]:delete('00000000')"
-exec admin "lua box.space[11]:delete('00000001')"
-exec admin "lua box.space[11]:delete('00000002')"
-exec admin "lua box.space[11]:delete('00000003')"
-exec admin "lua box.space[11]:delete('00000004')"
-exec admin "lua box.space[11]:delete('00000005')"
+exec admin "lua box.space[11]:delete(0ULL)"
+exec admin "lua box.space[11]:delete(1ULL)"
+exec admin "lua box.space[11]:delete(2ULL)"
+exec admin "lua box.space[11]:delete(3ULL)"
+exec admin "lua box.space[11]:delete(4ULL)"
+exec admin "lua box.space[11]:delete(5ULL)"
+
+exec admin "lua box.space[11]:insert(0ULL, 'value1 v1.0', 'value2 v1.0')"
+exec admin "lua box.space[11]:insert(1ULL, 'value1 v1.0', 'value2 v1.0')"
+exec admin "lua box.space[11]:insert(2ULL, 'value1 v1.0', 'value2 v1.0')"
+exec admin "lua box.space[11]:insert(3ULL, 'value1 v1.0', 'value2 v1.0')"
+
+print """
+# delete by valid NUM keys
+"""
+exec admin "lua box.space[11]:delete(0)"
+exec admin "lua box.space[11]:delete(1)"
+exec admin "lua box.space[11]:delete(2)"
+exec admin "lua box.space[11]:delete(3)"
+exec admin "lua box.space[11]:delete(4)"
+exec admin "lua box.space[11]:delete(5)"
 
 print """
 # delete by invalid keys
diff --git a/test/big/iterator.result b/test/big/iterator.result
index 21609efd1f75cbdd77a41fc7e0513989b4c3f294..1c832db3fdf2f25c8526613911879a6a91c76c10 100644
--- a/test/big/iterator.result
+++ b/test/big/iterator.result
@@ -768,7 +768,7 @@ error: 'utils.lua:27: Key part count 4 is greater than index part count 2'
 ...
 
 #-----------------------------------------------------------------------------#
-# Iterator: hash multi-part non-unique
+# Iterator: hash single-part unique
 #-----------------------------------------------------------------------------#
 
 lua iterate(20, 4, 0, 1)
@@ -840,6 +840,68 @@ lua iterate(20, 4, 0, 1, box.index.GE, 'pid_999')
 sorted output
 ...
 
+#-----------------------------------------------------------------------------#
+# Iterator: hash multi-part unique
+#-----------------------------------------------------------------------------#
+
+lua iterate(20, 5, 1, 3, box.index.ALL)
+---
+sorted output
+$sid_001$tid_997$
+$sid_001$tid_998$
+$sid_002$tid_996$
+$sid_002$tid_997$
+$sid_003$tid_996$
+$sid_004$tid_996$
+$sid_005$tid_994$
+$sid_005$tid_995$
+$sid_005$tid_996$
+$sid_006$tid_996$
+...
+lua iterate(20, 5, 1, 3, box.index.EQ)
+---
+error: 'Invalid key part count in an exact match (expected 2, got 0)'
+...
+lua iterate(20, 5, 1, 3, box.index.EQ, 'sid_005')
+---
+error: 'Invalid key part count in an exact match (expected 2, got 1)'
+...
+lua iterate(20, 5, 1, 3, box.index.GE)
+---
+sorted output
+$sid_001$tid_997$
+$sid_001$tid_998$
+$sid_002$tid_996$
+$sid_002$tid_997$
+$sid_003$tid_996$
+$sid_004$tid_996$
+$sid_005$tid_994$
+$sid_005$tid_995$
+$sid_005$tid_996$
+$sid_006$tid_996$
+...
+lua iterate(20, 2, 1, 3, box.index.EQ, 'sid_005', 'tid_995')
+---
+$sid_005$tid_995$
+...
+lua iterate(20, 2, 1, 3, box.index.EQ, 'sid_005', 'tid_999')
+---
+...
+lua iterate(20, 2, 1, 3, box.index.EQ, 'sid_005', 'tid_995', 'a')
+---
+error: 'utils.lua:27: Key part count 3 is greater than index part count 2'
+...
+lua iterate(20, 2, 1, 3, box.index.GE, 'sid_005', 'tid_995')
+---
+$sid_005$tid_995$
+$sid_005$tid_996$
+$sid_006$tid_996$
+...
+lua iterate(20, 2, 1, 3, box.index.GE, 'sid_005', 'tid_999')
+---
+$sid_006$tid_996$
+...
+
 #-----------------------------------------------------------------------------#
 # Iterator: various
 #-----------------------------------------------------------------------------#
diff --git a/test/big/iterator.test b/test/big/iterator.test
index 53fd5531bce1051cd2e18a455fad100f1f20f723..2a737ee5b490526e48d84efa3574598ff8e0a045 100644
--- a/test/big/iterator.test
+++ b/test/big/iterator.test
@@ -135,7 +135,7 @@ exec admin "lua iterate(20, 3, 2, 4, box.index.LT, 'tid_996', 'to', 'many', 'key
 
 print """
 #-----------------------------------------------------------------------------#
-# Iterator: hash multi-part non-unique
+# Iterator: hash single-part unique
 #-----------------------------------------------------------------------------#
 """
 
@@ -148,6 +148,21 @@ exec admin "lua iterate(20, 4, 0, 1, box.index.EQ, 'pid_666')"
 exec admin "lua iterate(20, 4, 0, 1, box.index.GE, 'pid_001')"
 exec admin "lua iterate(20, 4, 0, 1, box.index.GE, 'pid_999')"
 
+print """
+#-----------------------------------------------------------------------------#
+# Iterator: hash multi-part unique
+#-----------------------------------------------------------------------------#
+"""
+exec admin "lua iterate(20, 5, 1, 3, box.index.ALL)"
+exec admin "lua iterate(20, 5, 1, 3, box.index.EQ)"
+exec admin "lua iterate(20, 5, 1, 3, box.index.EQ, 'sid_005')"
+exec admin "lua iterate(20, 5, 1, 3, box.index.GE)"
+exec admin "lua iterate(20, 2, 1, 3, box.index.EQ, 'sid_005', 'tid_995')"
+exec admin "lua iterate(20, 2, 1, 3, box.index.EQ, 'sid_005', 'tid_999')"
+exec admin "lua iterate(20, 2, 1, 3, box.index.EQ, 'sid_005', 'tid_995', 'a')"
+exec admin "lua iterate(20, 2, 1, 3, box.index.GE, 'sid_005', 'tid_995')"
+exec admin "lua iterate(20, 2, 1, 3, box.index.GE, 'sid_005', 'tid_999')"
+
 print """
 #-----------------------------------------------------------------------------#
 # Iterator: various
diff --git a/test/big/tarantool.cfg b/test/big/tarantool.cfg
index 0f9f416055bc0a9060f935ceca19c73b0fdb1141..73973d4782f41309bfd525a7ff09f66f32c9b34c 100644
--- a/test/big/tarantool.cfg
+++ b/test/big/tarantool.cfg
@@ -283,6 +283,14 @@ space[20].index[4].unique = 1
 space[20].index[4].key_field[0].fieldno = 0
 space[20].index[4].key_field[0].type = "STR"
 
+# Hash multi-part unique
+space[20].index[5].type = "HASH"
+space[20].index[5].unique = 1
+space[20].index[5].key_field[0].fieldno = 1
+space[20].index[5].key_field[0].type = "STR"
+space[20].index[5].key_field[1].fieldno = 2
+space[20].index[5].key_field[1].type = "STR"
+
 # hash::replace
 space[21].enabled = true