OK, let go by parts:
First: why is the database getting bigger with index?
It's expected the database gets bigger when adding indexes because you are adding a new support structure (the index). In this case you have:
| Structure |
size |
| The db |
1.8Mb |
| The product index |
1.2Mb |
| The customer index |
1.1 Mb |
Note this sizes are an estimation.
Why the query is getting slower with this support structures?
Maybe a bunch of reasons that justify you empirical results lets go over some possibilities
- There is a cache that is interfering with your metrics.
This is the first thing that came to my mind, and is the first I suggest you to discard. Maybe there is some cache between those trials, try deleting and recreating everything between each test.
In your particular case this is the reason. I test your script and if you need to move your commit just before all the inserts, and after the query.
Try:
import sqlite3, time, random, string
db = sqlite3.connect('test.db')
db.executescript("""CREATE TABLE transactions(id INTEGER PRIMARY KEY, dt INTEGER, product TEXT, customer TEXT);""")
for i in range(100*1000):
t = random.randint(1600000000, 1600010000) # random datetime
product = ''.join(random.choices(string.ascii_uppercase, k=2)) # random product among 676 products
customer = ''.join(random.choices(string.ascii_uppercase, k=2)) # random customer among 676 customers
db.execute("INSERT INTO transactions(dt, product, customer) VALUES (?, ?, ?)", (t, product, customer))
db.commit()
t0 = time.time()
for _ in db.execute("SELECT product, COUNT(customer) FROM transactions GROUP BY product"):
pass
print (time.time()-t0)
db.commit()
I will continue explaining some possible issues an improvements but your question was already answered.
Index suggestion
Index are a complementary structure that fits in memory and improves the query by getting first the data you need (in the order you need).
In this case what you first need is to get all different products (the group By) and then you need to count the different customers for each product (the count), so in that case I would recommend to create a compose index like:
CREATE INDEX product_customer_idx
ON transactions(product, customer)
So the the database engine will not go to the table in disk, instead will load all the index in memory and with a simple scan of the index will bring your answer.
Why is this difference is no that significant in this case?
That is simple, and you can tell by looking at the table vs index size. The index as I explained before is created to avoid fit all the table in memory (If you can fit all the table in memory, you wont get much improvement) and or to get things in the right order. In this case the order doesn't matter (you are performing a full scan with the group by) and if you realize the only column that is missing in the index is [dt] (ids are always present). So the only things that you avoid to load in memory is an integer column. Try adding some long text values in a new column, I believe that will increase the difference between the table size and the index size, and also the speed difference.